From 73b2766131260c500ebcc3c49f6e0a171391be13 Mon Sep 17 00:00:00 2001 From: Francis Laniel Date: Tue, 21 Nov 2023 19:03:09 +0100 Subject: [PATCH] feat: Add Untag() to oci.Store. Signed-off-by: Francis Laniel --- content/oci/oci.go | 23 ++++++ content/oci/oci_test.go | 151 ++++++++++++++++++++++++++++++++++++++-- content/resolver.go | 6 ++ 3 files changed, 175 insertions(+), 5 deletions(-) diff --git a/content/oci/oci.go b/content/oci/oci.go index 5d4699a1..1deca875 100644 --- a/content/oci/oci.go +++ b/content/oci/oci.go @@ -238,6 +238,29 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript return desc, nil } +func (s *Store) Untag(ctx context.Context, reference string) error { + if reference == "" { + return errdef.ErrMissingReference + } + + s.sync.RLock() + defer s.sync.RUnlock() + + desc, err := s.tagResolver.Resolve(ctx, reference) + if err != nil { + return fmt.Errorf("resolving reference %q: %w", reference, err) + } + if reference == desc.Digest.String() { + return fmt.Errorf("reference %q is a digest and not a tag: %w", reference, errdef.ErrInvalidReference) + } + + s.tagResolver.Untag(reference) + if s.AutoSaveIndex { + return s.saveIndex() + } + return nil +} + // Predecessors returns the nodes directly pointing to the current node. // Predecessors returns nil without error if the node does not exists in the // store. diff --git a/content/oci/oci_test.go b/content/oci/oci_test.go index 01028b1a..bba9037a 100644 --- a/content/oci/oci_test.go +++ b/content/oci/oci_test.go @@ -575,7 +575,8 @@ func TestStore_DisableAutoSaveIndex(t *testing.T) { Digest: digest.FromBytes(content), Size: int64(len(content)), } - ref := "foobar" + ref0 := "foobar" + ref1 := "barfoo" tempDir := t.TempDir() s, err := New(tempDir) @@ -623,16 +624,20 @@ func TestStore_DisableAutoSaveIndex(t *testing.T) { } // test tag - err = s.Tag(ctx, desc, ref) + err = s.Tag(ctx, desc, ref0) if err != nil { t.Fatal("Store.Tag() error =", err) } - if got, want := len(internalResolver.Map()), 2; got != want { + err = s.Tag(ctx, desc, ref1) + if err != nil { + t.Fatal("Store.Tag() error =", err) + } + if got, want := len(internalResolver.Map()), 3; got != want { t.Errorf("resolver.Map() = %v, want %v", got, want) } // test resolving by digest - gotDesc, err = s.Resolve(ctx, ref) + gotDesc, err = s.Resolve(ctx, ref0) if err != nil { t.Fatal("Store.Resolve() error =", err) } @@ -648,12 +653,31 @@ func TestStore_DisableAutoSaveIndex(t *testing.T) { t.Fatal("Store.SaveIndex() error =", err) } // test index file again - if got, want := len(s.index.Manifests), 1; got != want { + if got, want := len(s.index.Manifests), 2; got != want { t.Errorf("len(index.Manifests) = %v, want %v", got, want) } if _, err := os.Stat(s.indexPath); err != nil { t.Errorf("error: %s does not exist", s.indexPath) } + + // test untag + err = s.Untag(ctx, ref0) + if err != nil { + t.Fatal("Store.Untag() error =", err) + } + if got, want := len(internalResolver.Map()), 2; got != want { + t.Errorf("resolver.Map() = %v, want %v", got, want) + } + if got, want := len(s.index.Manifests), 2; got != want { + t.Errorf("len(index.Manifests) = %v, want %v", got, want) + } + if err := s.saveIndex(); err != nil { + t.Fatal("Store.SaveIndex() error =", err) + } + // test index file again + if got, want := len(s.index.Manifests), 1; got != want { + t.Errorf("len(index.Manifests) = %v, want %v", got, want) + } } func TestStore_RepeatTag(t *testing.T) { @@ -2237,6 +2261,123 @@ func TestStore_PredecessorsAndDelete(t *testing.T) { } } +func TestStore_Untag(t *testing.T) { + content := []byte("test delete") + desc := ocispec.Descriptor{ + MediaType: "test-delete", + Digest: digest.FromBytes(content), + Size: int64(len(content)), + } + ref := "latest" + + tempDir := t.TempDir() + s, err := New(tempDir) + if err != nil { + t.Fatal("NewDeletableStore() error =", err) + } + ctx := context.Background() + + err = s.Push(ctx, desc, bytes.NewReader(content)) + if err != nil { + t.Errorf("Store.Push() error = %v, wantErr %v", err, false) + } + + err = s.Tag(ctx, desc, ref) + if err != nil { + t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) + } + + exists, err := s.Exists(ctx, desc) + if err != nil { + t.Fatal("Store.Exists() error =", err) + } + if !exists { + t.Errorf("Store.Exists() = %v, want %v", exists, true) + } + + resolvedDescr, err := s.Resolve(ctx, ref) + if err != nil { + t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) + } + + if !reflect.DeepEqual(resolvedDescr, desc) { + t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) + } + + err = s.Untag(ctx, ref) + if err != nil { + t.Errorf("Store.Untag() = %v, wantErr %v", err, nil) + } + + _, err = s.Resolve(ctx, ref) + if !errors.Is(err, errdef.ErrNotFound) { + t.Errorf("error resolving descriptor error = %v, wantErr %v", err, errdef.ErrNotFound) + } + + exists, err = s.Exists(ctx, desc) + if err != nil { + t.Fatal("Store.Exists() error =", err) + } + if !exists { + t.Errorf("Store.Exists() = %v, want %v", exists, true) + } +} + +func TestStore_UntagErrorPath(t *testing.T) { + content := []byte("test delete") + desc := ocispec.Descriptor{ + MediaType: "test-delete", + Digest: digest.FromBytes(content), + Size: int64(len(content)), + } + ref := "latest" + + tempDir := t.TempDir() + s, err := New(tempDir) + if err != nil { + t.Fatal("NewDeletableStore() error =", err) + } + ctx := context.Background() + + err = s.Untag(ctx, "") + if !errors.Is(err, errdef.ErrMissingReference) { + t.Errorf("Store.Untag() error = %v, wantErr %v", err, errdef.ErrMissingReference) + } + + err = s.Untag(ctx, "foobar") + if !errors.Is(err, errdef.ErrNotFound) { + t.Errorf("Store.Untag() error = %v, wantErr %v", err, errdef.ErrNotFound) + } + + err = s.Push(ctx, desc, bytes.NewReader(content)) + if err != nil { + t.Errorf("Store.Push() error = %v, wantErr %v", err, false) + } + + err = s.Tag(ctx, desc, ref) + if err != nil { + t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) + } + + exists, err := s.Exists(ctx, desc) + if err != nil { + t.Fatal("Store.Exists() error =", err) + } + if !exists { + t.Errorf("Store.Exists() = %v, want %v", exists, true) + } + + resolvedDescr, err := s.Resolve(ctx, ref) + if err != nil { + t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) + } + + err = s.Untag(ctx, resolvedDescr.Digest.String()) + if !errors.Is(err, errdef.ErrInvalidReference) { + t.Errorf("Store.Untag() error = %v, wantErr %v", err, errdef.ErrInvalidReference) + } +} + func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool { if len(actual) != len(expected) { return false diff --git a/content/resolver.go b/content/resolver.go index b536b5dd..bc0fd8df 100644 --- a/content/resolver.go +++ b/content/resolver.go @@ -39,3 +39,9 @@ type TagResolver interface { Tagger Resolver } + +// Untagger untags reference tags. +type Untagger interface { + // Untag untags the given reference string. + Untag(ctx context.Context, reference string) error +}