Skip to content

Commit

Permalink
Add Delete to storages
Browse files Browse the repository at this point in the history
Signed-off-by: carabasdaniel <[email protected]>
  • Loading branch information
carabasdaniel committed Mar 29, 2023
1 parent e8225cb commit 8c25175
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 2 deletions.
14 changes: 13 additions & 1 deletion content/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (

// Store represents a memory based store, which implements `oras.Target`.
type Store struct {
storage content.Storage
storage content.DeleteStorage
resolver content.TagResolver
graph *graph.Memory
}
Expand Down Expand Up @@ -72,6 +72,18 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript
return s.resolver.Resolve(ctx, reference)
}

// Delete a target descriptor for storage.
func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error {
exists, err := s.storage.Exists(ctx, target)
if err != nil {
return err
}
if !exists {
return errdef.ErrNotFound
}
return s.storage.Delete(ctx, target)
}

// Tag tags a descriptor with a reference string.
// Returns ErrNotFound if the tagged content does not exist.
func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error {
Expand Down
45 changes: 45 additions & 0 deletions content/memory/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,51 @@ func TestStorePredecessors(t *testing.T) {
}
}

func TestStoreDelete(t *testing.T) {
content := []byte("hello world")
desc := ocispec.Descriptor{
MediaType: "test",
Digest: digest.FromBytes(content),
Size: int64(len(content)),
}

s := New()
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Store.Push() error =", err)
}
ref := "foobar"
err = s.Tag(ctx, desc, ref)
if err != nil {
t.Fatal("Store.Tag() error =", err)
}

internalResolver := s.resolver.(*resolver.Memory)
if got := len(internalResolver.Map()); got != 1 {
t.Errorf("resolver.Map() = %v, want %v", got, 1)
}

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)
}

err = s.Delete(ctx, desc)
if err != nil {
t.Fatal("Store.Delete() error =", err)
}

internalStorage := s.storage.(*cas.Memory)
if got := len(internalStorage.Map()); got != 0 {
t.Errorf("storage.Map() = %v, want %v", got, 0)
}
}

func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool {
if len(actual) != len(expected) {
return false
Expand Down
41 changes: 40 additions & 1 deletion content/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type Store struct {
index *ocispec.Index
indexLock sync.Mutex

storage content.Storage
storage content.DeleteStorage
tagResolver *resolver.Memory
graph *graph.Memory
}
Expand All @@ -76,6 +76,7 @@ func NewWithContext(ctx context.Context, root string) (*Store, error) {
if err != nil {
return nil, fmt.Errorf("failed to resolve absolute path for %s: %w", root, err)
}

storage, err := NewStorage(rootAbs)
if err != nil {
return nil, fmt.Errorf("failed to create storage: %w", err)
Expand Down Expand Up @@ -192,6 +193,44 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript
return desc, nil
}

// Untag removes a reference string from index.
// reference should be a valid tag (e.g. "latest").
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md#indexjson-file
func (s *Store) Untag(ctx context.Context, descr ocispec.Descriptor, reference string) error {
if err := validateReference(reference); err != nil {
return err
}

s.tagResolver.Delete((reference))
s.tagResolver.Delete(descr.Digest.String())

if s.AutoSaveIndex {
err := s.SaveIndex()
if err != nil {
return err
}
}

return nil
}

// Delete removed a target descriptor from index and storage.
func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error {
resolvers := s.tagResolver.Map()
for reference, desc := range resolvers {
if content.Equal(desc, target) {
s.tagResolver.Delete(reference)
}
}
if s.AutoSaveIndex {
err := s.SaveIndex()
if err != nil {
return err
}
}
return s.storage.Delete(ctx, target)
}

// Predecessors returns the nodes directly pointing to the current node.
// Predecessors returns nil without error if the node does not exists in the
// store.
Expand Down
160 changes: 160 additions & 0 deletions content/oci/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,55 @@ func TestStore_TagByDigest(t *testing.T) {
}
}

func TestStore_Untag(t *testing.T) {
content := []byte("hello world")
ref := "hello-world:0.0.1"
desc := ocispec.Descriptor{
MediaType: "test",
Digest: digest.FromBytes(content),
Size: int64(len(content)),
}

tempDir := t.TempDir()
s, err := New(tempDir)
if err != nil {
t.Fatal("New() 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)
}

if len(s.tagResolver.Map()) == 0 {
t.Error("tagresolver map should not be empty")
}

resolvedDescr, err := s.Resolve(ctx, string(desc.Digest))
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, resolvedDescr, ref)
if err != nil {
t.Errorf("error untagging descriptor error = %v, wantErr %v", err, false)
}

if len(s.tagResolver.Map()) > 0 {
t.Error("tagresolver map should be empty")
}
}

func TestStore_BadIndex(t *testing.T) {
tempDir := t.TempDir()
content := []byte("whatever")
Expand Down Expand Up @@ -1993,3 +2042,114 @@ func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descript
}
return true
}

func TestStore_Delete(t *testing.T) {
content := []byte("hello world")
ref := "hello-world:0.0.1"
desc := ocispec.Descriptor{
MediaType: "test",
Digest: digest.FromBytes(content),
Size: int64(len(content)),
}

tempDir := t.TempDir()
s, err := New(tempDir)
if err != nil {
t.Fatal("New() 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)
}

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.Delete(ctx, resolvedDescr)
if err != nil {
t.Errorf("Store.Delete() = %v, wantErr %v", err, true)
}

_, err = s.Resolve(ctx, ref)
if !errors.Is(err, errdef.ErrNotFound) {
t.Errorf("descriptor should no longer exist in store = %v, wantErr %v", err, errdef.ErrNotFound)
}
}

func TestStore_DeleteDescriptoMultipleRefs(t *testing.T) {
content := []byte("hello world")
ref1 := "hello-world:0.0.1"
ref2 := "hello-world:0.0.2"
desc := ocispec.Descriptor{
MediaType: "test",
Digest: digest.FromBytes(content),
Size: int64(len(content)),
}

tempDir := t.TempDir()
s, err := New(tempDir)
s.AutoSaveIndex = true
if err != nil {
t.Fatal("New() 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)
}

if len(s.index.Manifests) != 0 {
t.Errorf("manifest should be empty but has %d elements", len(s.index.Manifests))
}

err = s.Tag(ctx, desc, ref1)
if err != nil {
t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false)
}

err = s.Tag(ctx, desc, ref2)
if err != nil {
t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false)
}

if len(s.index.Manifests) != 2 {
t.Errorf("manifest should have %d, but has %d", len(s.index.Manifests), 0)
}

resolvedDescr, err := s.Resolve(ctx, ref1)
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.Delete(ctx, resolvedDescr)
if err != nil {
t.Errorf("Store.Delete() = %v, wantErr %v", err, true)
}

if len(s.index.Manifests) != 0 {
t.Errorf("manifest should be empty after delete but has %d", len(s.index.Manifests))
}

_, err = s.Resolve(ctx, ref2)
if !errors.Is(err, errdef.ErrNotFound) {
t.Errorf("descriptor should no longer exist in store = %v, wantErr %v", err, errdef.ErrNotFound)
}
}
11 changes: 11 additions & 0 deletions content/oci/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ func (s *Storage) Push(_ context.Context, expected ocispec.Descriptor, content i
return nil
}

// Delete removes the target blob.
func (s *Storage) Delete(ctx context.Context, target ocispec.Descriptor) error {
path, err := blobPath(target.Digest)
if err != nil {
return fmt.Errorf("%s: %s: %w", target.Digest, target.MediaType, errdef.ErrInvalidDigest)
}
targetpath := filepath.Join(s.root, path)

return os.Remove(targetpath)
}

// ingest write the content into a temporary ingest file.
func (s *Storage) ingest(expected ocispec.Descriptor, content io.Reader) (path string, ingestErr error) {
if err := ensureDir(s.ingestRoot); err != nil {
Expand Down
24 changes: 24 additions & 0 deletions content/oci/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,27 @@ func TestStorage_Fetch_Concurrent(t *testing.T) {
t.Fatal(err)
}
}

func TestStorage_Delete(t *testing.T) {
content := []byte("hello world")
desc := ocispec.Descriptor{
MediaType: "test",
Digest: digest.FromBytes(content),
Size: int64(len(content)),
}

tempDir := t.TempDir()
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

if err := s.Push(ctx, desc, bytes.NewReader(content)); err != nil {
t.Fatal("Storage.Push() error =", err)
}
err = s.Delete(ctx, desc)
if err != nil {
t.Fatal("Storage.Delete() error =", err)
}
}
6 changes: 6 additions & 0 deletions content/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type Storage interface {
Pusher
}

// DeleteStorage represents an extension of the Storage interface that includes the Deleter.
type DeleteStorage interface {
Storage
Deleter
}

// ReadOnlyStorage represents a read-only Storage.
type ReadOnlyStorage interface {
Fetcher
Expand Down
11 changes: 11 additions & 0 deletions internal/cas/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,14 @@ func (m *Memory) Map() map[descriptor.Descriptor][]byte {
})
return res
}

// Delete removes a target descriptor from content map.
func (m *Memory) Delete(ctx context.Context, target ocispec.Descriptor) error {
key := descriptor.FromOCI(target)

_, deleted := m.content.LoadAndDelete(key)
if !deleted {
return errdef.ErrNotFound
}
return nil
}
Loading

0 comments on commit 8c25175

Please sign in to comment.