From f3500a3b92fef6cd8097dc06214d8642fd98f4d9 Mon Sep 17 00:00:00 2001 From: Sylvia Lei Date: Fri, 2 Sep 2022 11:34:18 +0800 Subject: [PATCH 1/3] add manifestStore Signed-off-by: Sylvia Lei --- content/resolver.go | 15 ++++++++--- registry/remote/repository.go | 48 +++++++++++++++++++++-------------- registry/repository.go | 14 +++++++--- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/content/resolver.go b/content/resolver.go index 13cb0600..fec2c307 100644 --- a/content/resolver.go +++ b/content/resolver.go @@ -28,10 +28,19 @@ type Resolver interface { Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) } +// Tagger tags reference tags. +type Tagger interface { + // Tag tags a descriptor with a reference string. + Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error +} + // TagResolver provides reference tag indexing services. type TagResolver interface { + Tagger Resolver - - // Tag tags a descriptor with a reference string. - Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error } + +// type TagPusher interface { +// Tagger +// Pusher +// } diff --git a/registry/remote/repository.go b/registry/remote/repository.go index f385ae88..8853ee36 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -159,7 +159,7 @@ func (r *Repository) Blobs() registry.BlobStore { } // Manifests provides access to the manifest CAS only. -func (r *Repository) Manifests() registry.BlobStore { +func (r *Repository) Manifests() registry.ManifestStore { return &manifestStore{repo: r} } @@ -171,28 +171,12 @@ func (r *Repository) Resolve(ctx context.Context, reference string) (ocispec.Des // Tag tags a manifest descriptor with a reference string. func (r *Repository) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { - ref, err := r.parseReference(reference) - if err != nil { - return err - } - - ctx = withScopeHint(ctx, ref, auth.ActionPull, auth.ActionPush) - rc, err := r.Manifests().Fetch(ctx, desc) - if err != nil { - return err - } - defer rc.Close() - - return r.push(ctx, desc, rc, ref.Reference) + return r.Manifests().Tag(ctx, desc, reference) } // PushReference pushes the manifest with a reference tag. func (r *Repository) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { - ref, err := r.parseReference(reference) - if err != nil { - return err - } - return r.push(ctx, expected, content, ref.Reference) + return r.Manifests().PushReference(ctx, expected, content, reference) } // push pushes the manifest content, matching the expected descriptor. @@ -951,6 +935,32 @@ func (s *manifestStore) FetchReference(ctx context.Context, reference string) (d } } +// Tag tags a manifest descriptor with a reference string. +func (s *manifestStore) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { + ref, err := s.repo.parseReference(reference) + if err != nil { + return err + } + + ctx = withScopeHint(ctx, ref, auth.ActionPull, auth.ActionPush) + rc, err := s.Fetch(ctx, desc) + if err != nil { + return err + } + defer rc.Close() + + return s.repo.push(ctx, desc, rc, ref.Reference) +} + +// PushReference pushes the manifest with a reference tag. +func (s *manifestStore) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { + ref, err := s.repo.parseReference(reference) + if err != nil { + return err + } + return s.repo.push(ctx, expected, content, ref.Reference) +} + // generateDescriptor returns a descriptor generated from the response. // See the truth table at the top of `repository_test.go` func (s *manifestStore) generateDescriptor(resp *http.Response, ref registry.Reference, httpMethod string) (ocispec.Descriptor, error) { diff --git a/registry/repository.go b/registry/repository.go index 801ce35b..18e52351 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -32,11 +32,13 @@ import ( // Since a repository is an union of the blob and the manifest CASs, all // operations defined in the `BlobStore` are executed depending on the media // type of the given descriptor accordingly. -// Furthurmore, this interface also provides the ability to enforce the +// Furthermore, this interface also provides the ability to enforce the // separation of the blob and the manifests CASs. type Repository interface { - BlobStore + content.Storage + content.Deleter content.TagResolver + ReferenceFetcher ReferencePusher // Blobs provides access to the blob CAS only, which contains config blobs, @@ -44,7 +46,7 @@ type Repository interface { Blobs() BlobStore // Manifests provides access to the manifest CAS only. - Manifests() BlobStore + Manifests() ManifestStore // Tags lists the tags available in the repository. // Since the returned tag list may be paginated by the underlying @@ -72,6 +74,12 @@ type BlobStore interface { ReferenceFetcher } +type ManifestStore interface { + BlobStore + content.Tagger + ReferencePusher +} + // ReferencePusher provides advanced push with the tag service. type ReferencePusher interface { // PushReference pushes the manifest with a reference tag. From eb5884be6892803df94a79083a47e7728a3fb10b Mon Sep 17 00:00:00 2001 From: Sylvia Lei Date: Tue, 6 Sep 2022 15:16:26 +0800 Subject: [PATCH 2/3] add unit tests Signed-off-by: Sylvia Lei --- registry/remote/repository.go | 2 +- registry/remote/repository_test.go | 137 +++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index 8853ee36..6e66817e 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -241,7 +241,7 @@ func (r *Repository) FetchReference(ctx context.Context, reference string) (ocis return r.Manifests().FetchReference(ctx, reference) } -// TagReference retags the manifest identified by src to dst. +// TagReference re-tags the manifest identified by src to dst. func (r *Repository) TagReference(ctx context.Context, src, dst string) error { srcRef, err := r.parseReference(src) if err != nil { diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index aef35ed5..124929b1 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -3020,6 +3020,143 @@ func Test_ManifestStore_FetchReference(t *testing.T) { } } +func Test_ManifestStore_Tag(t *testing.T) { + blob := []byte("hello world") + blobDesc := ocispec.Descriptor{ + MediaType: "test", + Digest: digest.FromBytes(blob), + Size: int64(len(blob)), + } + index := []byte(`{"manifests":[]}`) + indexDesc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageIndex, + Digest: digest.FromBytes(index), + Size: int64(len(index)), + } + var gotIndex []byte + ref := "foobar" + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+blobDesc.Digest.String(): + w.WriteHeader(http.StatusNotFound) + case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String(): + if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) { + t.Errorf("manifest not convertable: %s", accept) + w.WriteHeader(http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", indexDesc.MediaType) + w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String()) + if _, err := w.Write(index); err != nil { + t.Errorf("failed to write %q: %v", r.URL, err) + } + case r.Method == http.MethodPut && + r.URL.Path == "/v2/test/manifests/"+ref || r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String(): + if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType { + w.WriteHeader(http.StatusBadRequest) + break + } + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(r.Body); err != nil { + t.Errorf("fail to read: %v", err) + } + gotIndex = buf.Bytes() + w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String()) + w.WriteHeader(http.StatusCreated) + return + default: + t.Errorf("unexpected access: %s %s", r.Method, r.URL) + w.WriteHeader(http.StatusForbidden) + } + })) + defer ts.Close() + uri, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("invalid test http server: %v", err) + } + + repo, err := NewRepository(uri.Host + "/test") + if err != nil { + t.Fatalf("NewRepository() error = %v", err) + } + store := repo.Manifests() + repo.PlainHTTP = true + ctx := context.Background() + + err = store.Tag(ctx, blobDesc, ref) + if err == nil { + t.Errorf("Repository.Tag() error = %v, wantErr %v", err, true) + } + + err = store.Tag(ctx, indexDesc, ref) + if err != nil { + t.Fatalf("Repository.Tag() error = %v", err) + } + if !bytes.Equal(gotIndex, index) { + t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index) + } + + gotIndex = nil + err = store.Tag(ctx, indexDesc, indexDesc.Digest.String()) + if err != nil { + t.Fatalf("Repository.Tag() error = %v", err) + } + if !bytes.Equal(gotIndex, index) { + t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index) + } +} + +func Test_ManifestStore_PushReference(t *testing.T) { + index := []byte(`{"manifests":[]}`) + indexDesc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageIndex, + Digest: digest.FromBytes(index), + Size: int64(len(index)), + } + var gotIndex []byte + ref := "foobar" + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+ref: + if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType { + w.WriteHeader(http.StatusBadRequest) + break + } + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(r.Body); err != nil { + t.Errorf("fail to read: %v", err) + } + gotIndex = buf.Bytes() + w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String()) + w.WriteHeader(http.StatusCreated) + return + default: + t.Errorf("unexpected access: %s %s", r.Method, r.URL) + w.WriteHeader(http.StatusForbidden) + } + })) + defer ts.Close() + uri, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("invalid test http server: %v", err) + } + + repo, err := NewRepository(uri.Host + "/test") + if err != nil { + t.Fatalf("NewRepository() error = %v", err) + } + store := repo.Manifests() + repo.PlainHTTP = true + ctx := context.Background() + err = store.PushReference(ctx, indexDesc, bytes.NewReader(index), ref) + if err != nil { + t.Fatalf("Repository.PushReference() error = %v", err) + } + if !bytes.Equal(gotIndex, index) { + t.Errorf("Repository.PushReference() = %v, want %v", gotIndex, index) + } +} + func Test_ManifestStore_generateDescriptorWithVariousDockerContentDigestHeaders(t *testing.T) { reference := registry.Reference{ Registry: "eastern.haan.com", From a9cee9c270ba1d4393ad60fe64d2facf043f1a53 Mon Sep 17 00:00:00 2001 From: Sylvia Lei Date: Tue, 6 Sep 2022 15:33:31 +0800 Subject: [PATCH 3/3] clean up Signed-off-by: Sylvia Lei --- content/resolver.go | 5 ----- registry/remote/repository.go | 42 +++++++++++++++++------------------ registry/repository.go | 2 ++ 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/content/resolver.go b/content/resolver.go index fec2c307..b536b5dd 100644 --- a/content/resolver.go +++ b/content/resolver.go @@ -39,8 +39,3 @@ type TagResolver interface { Tagger Resolver } - -// type TagPusher interface { -// Tagger -// Pusher -// } diff --git a/registry/remote/repository.go b/registry/remote/repository.go index 6e66817e..4e64cc83 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -171,12 +171,28 @@ func (r *Repository) Resolve(ctx context.Context, reference string) (ocispec.Des // Tag tags a manifest descriptor with a reference string. func (r *Repository) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { - return r.Manifests().Tag(ctx, desc, reference) + ref, err := r.parseReference(reference) + if err != nil { + return err + } + + ctx = withScopeHint(ctx, ref, auth.ActionPull, auth.ActionPush) + rc, err := r.Manifests().Fetch(ctx, desc) + if err != nil { + return err + } + defer rc.Close() + + return r.push(ctx, desc, rc, ref.Reference) } // PushReference pushes the manifest with a reference tag. func (r *Repository) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { - return r.Manifests().PushReference(ctx, expected, content, reference) + ref, err := r.parseReference(reference) + if err != nil { + return err + } + return r.push(ctx, expected, content, ref.Reference) } // push pushes the manifest content, matching the expected descriptor. @@ -241,7 +257,7 @@ func (r *Repository) FetchReference(ctx context.Context, reference string) (ocis return r.Manifests().FetchReference(ctx, reference) } -// TagReference re-tags the manifest identified by src to dst. +// TagReference retags the manifest identified by src to dst. func (r *Repository) TagReference(ctx context.Context, src, dst string) error { srcRef, err := r.parseReference(src) if err != nil { @@ -937,28 +953,12 @@ func (s *manifestStore) FetchReference(ctx context.Context, reference string) (d // Tag tags a manifest descriptor with a reference string. func (s *manifestStore) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { - ref, err := s.repo.parseReference(reference) - if err != nil { - return err - } - - ctx = withScopeHint(ctx, ref, auth.ActionPull, auth.ActionPush) - rc, err := s.Fetch(ctx, desc) - if err != nil { - return err - } - defer rc.Close() - - return s.repo.push(ctx, desc, rc, ref.Reference) + return s.repo.Tag(ctx, desc, reference) } // PushReference pushes the manifest with a reference tag. func (s *manifestStore) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { - ref, err := s.repo.parseReference(reference) - if err != nil { - return err - } - return s.repo.push(ctx, expected, content, ref.Reference) + return s.repo.PushReference(ctx, expected, content, reference) } // generateDescriptor returns a descriptor generated from the response. diff --git a/registry/repository.go b/registry/repository.go index 18e52351..2389e835 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -74,6 +74,8 @@ type BlobStore interface { ReferenceFetcher } +// ManifestStore is a CAS with the ability to stat and delete its content. +// Besides, ManifestStore provides reference tagging. type ManifestStore interface { BlobStore content.Tagger