Skip to content

Commit 75d200e

Browse files
feat: extend oci.Store with Delete() (#614)
Part 4/4 of #454 This PR should be reviewed and merged after PR #606 #607 #608 --------- Signed-off-by: Xiaoxuan Wang <[email protected]>
1 parent 9febd7b commit 75d200e

File tree

2 files changed

+342
-15
lines changed

2 files changed

+342
-15
lines changed

content/oci/oci.go

+79-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ limitations under the License.
1414
*/
1515

1616
// Package oci provides access to an OCI content store.
17-
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md
17+
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md
1818
package oci
1919

2020
import (
@@ -40,24 +40,31 @@ import (
4040

4141
// Store implements `oras.Target`, and represents a content store
4242
// based on file system with the OCI-Image layout.
43-
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md
43+
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md
4444
type Store struct {
4545
// AutoSaveIndex controls if the OCI store will automatically save the index
46-
// file on each Tag() call.
47-
// - If AutoSaveIndex is set to true, the OCI store will automatically call
48-
// this method on each Tag() call.
46+
// file when needed.
47+
// - If AutoSaveIndex is set to true, the OCI store will automatically save
48+
// the changes to `index.json` when
49+
// 1. pushing a manifest
50+
// 2. calling Tag() or Delete()
4951
// - If AutoSaveIndex is set to false, it's the caller's responsibility
5052
// to manually call SaveIndex() when needed.
5153
// - Default value: true.
5254
AutoSaveIndex bool
5355
root string
5456
indexPath string
5557
index *ocispec.Index
56-
indexLock sync.Mutex
57-
58-
storage content.Storage
59-
tagResolver *resolver.Memory
60-
graph *graph.Memory
58+
storage *Storage
59+
tagResolver *resolver.Memory
60+
graph *graph.Memory
61+
62+
// sync ensures that most operations can be done concurrently, while Delete
63+
// has the exclusive access to Store if a delete operation is underway. Operations
64+
// such as Fetch, Push use sync.RLock(), while Delete uses sync.Lock().
65+
sync sync.RWMutex
66+
// indexLock ensures that only one go-routine is writing to the index.
67+
indexLock sync.Mutex
6168
}
6269

6370
// New creates a new OCI store with context.Background().
@@ -98,13 +105,21 @@ func NewWithContext(ctx context.Context, root string) (*Store, error) {
98105
return store, nil
99106
}
100107

101-
// Fetch fetches the content identified by the descriptor.
108+
// Fetch fetches the content identified by the descriptor. It returns an io.ReadCloser.
109+
// It's recommended to close the io.ReadCloser before a Delete operation, otherwise
110+
// Delete may fail (for example on NTFS file systems).
102111
func (s *Store) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
112+
s.sync.RLock()
113+
defer s.sync.RUnlock()
114+
103115
return s.storage.Fetch(ctx, target)
104116
}
105117

106118
// Push pushes the content, matching the expected descriptor.
107119
func (s *Store) Push(ctx context.Context, expected ocispec.Descriptor, reader io.Reader) error {
120+
s.sync.RLock()
121+
defer s.sync.RUnlock()
122+
108123
if err := s.storage.Push(ctx, expected, reader); err != nil {
109124
return err
110125
}
@@ -120,13 +135,46 @@ func (s *Store) Push(ctx context.Context, expected ocispec.Descriptor, reader io
120135

121136
// Exists returns true if the described content exists.
122137
func (s *Store) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
138+
s.sync.RLock()
139+
defer s.sync.RUnlock()
140+
123141
return s.storage.Exists(ctx, target)
124142
}
125143

144+
// Delete deletes the content matching the descriptor from the store. Delete may
145+
// fail on certain systems (i.e. NTFS), if there is a process (i.e. an unclosed
146+
// Reader) using target.
147+
func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error {
148+
s.sync.Lock()
149+
defer s.sync.Unlock()
150+
151+
resolvers := s.tagResolver.Map()
152+
untagged := false
153+
for reference, desc := range resolvers {
154+
if content.Equal(desc, target) {
155+
s.tagResolver.Untag(reference)
156+
untagged = true
157+
}
158+
}
159+
if err := s.graph.Remove(ctx, target); err != nil {
160+
return err
161+
}
162+
if untagged && s.AutoSaveIndex {
163+
err := s.saveIndex()
164+
if err != nil {
165+
return err
166+
}
167+
}
168+
return s.storage.Delete(ctx, target)
169+
}
170+
126171
// Tag tags a descriptor with a reference string.
127172
// reference should be a valid tag (e.g. "latest").
128-
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md#indexjson-file
173+
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md#indexjson-file
129174
func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error {
175+
s.sync.RLock()
176+
defer s.sync.RUnlock()
177+
130178
if err := validateReference(reference); err != nil {
131179
return err
132180
}
@@ -155,7 +203,7 @@ func (s *Store) tag(ctx context.Context, desc ocispec.Descriptor, reference stri
155203
return err
156204
}
157205
if s.AutoSaveIndex {
158-
return s.SaveIndex()
206+
return s.saveIndex()
159207
}
160208
return nil
161209
}
@@ -166,6 +214,9 @@ func (s *Store) tag(ctx context.Context, desc ocispec.Descriptor, reference stri
166214
// digest the returned descriptor will be a plain descriptor (containing only
167215
// the digest, media type and size).
168216
func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) {
217+
s.sync.RLock()
218+
defer s.sync.RUnlock()
219+
169220
if reference == "" {
170221
return ocispec.Descriptor{}, errdef.ErrMissingReference
171222
}
@@ -191,6 +242,9 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript
191242
// Predecessors returns nil without error if the node does not exists in the
192243
// store.
193244
func (s *Store) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
245+
s.sync.RLock()
246+
defer s.sync.RUnlock()
247+
194248
return s.graph.Predecessors(ctx, node)
195249
}
196250

@@ -202,6 +256,9 @@ func (s *Store) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]oc
202256
//
203257
// See also `Tags()` in the package `registry`.
204258
func (s *Store) Tags(ctx context.Context, last string, fn func(tags []string) error) error {
259+
s.sync.RLock()
260+
defer s.sync.RUnlock()
261+
205262
return listTags(ctx, s.tagResolver, last, fn)
206263
}
207264

@@ -263,10 +320,18 @@ func (s *Store) loadIndexFile(ctx context.Context) error {
263320

264321
// SaveIndex writes the `index.json` file to the file system.
265322
// - If AutoSaveIndex is set to true (default value),
266-
// the OCI store will automatically call this method on each Tag() call.
323+
// the OCI store will automatically save the changes to `index.json`
324+
// on Tag() and Delete() calls, and when pushing a manifest.
267325
// - If AutoSaveIndex is set to false, it's the caller's responsibility
268326
// to manually call this method when needed.
269327
func (s *Store) SaveIndex() error {
328+
s.sync.RLock()
329+
defer s.sync.RUnlock()
330+
331+
return s.saveIndex()
332+
}
333+
334+
func (s *Store) saveIndex() error {
270335
s.indexLock.Lock()
271336
defer s.indexLock.Unlock()
272337

0 commit comments

Comments
 (0)