@@ -14,7 +14,7 @@ limitations under the License.
14
14
*/
15
15
16
16
// 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
18
18
package oci
19
19
20
20
import (
@@ -40,24 +40,31 @@ import (
40
40
41
41
// Store implements `oras.Target`, and represents a content store
42
42
// 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
44
44
type Store struct {
45
45
// 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()
49
51
// - If AutoSaveIndex is set to false, it's the caller's responsibility
50
52
// to manually call SaveIndex() when needed.
51
53
// - Default value: true.
52
54
AutoSaveIndex bool
53
55
root string
54
56
indexPath string
55
57
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
61
68
}
62
69
63
70
// New creates a new OCI store with context.Background().
@@ -98,13 +105,21 @@ func NewWithContext(ctx context.Context, root string) (*Store, error) {
98
105
return store , nil
99
106
}
100
107
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).
102
111
func (s * Store ) Fetch (ctx context.Context , target ocispec.Descriptor ) (io.ReadCloser , error ) {
112
+ s .sync .RLock ()
113
+ defer s .sync .RUnlock ()
114
+
103
115
return s .storage .Fetch (ctx , target )
104
116
}
105
117
106
118
// Push pushes the content, matching the expected descriptor.
107
119
func (s * Store ) Push (ctx context.Context , expected ocispec.Descriptor , reader io.Reader ) error {
120
+ s .sync .RLock ()
121
+ defer s .sync .RUnlock ()
122
+
108
123
if err := s .storage .Push (ctx , expected , reader ); err != nil {
109
124
return err
110
125
}
@@ -120,13 +135,46 @@ func (s *Store) Push(ctx context.Context, expected ocispec.Descriptor, reader io
120
135
121
136
// Exists returns true if the described content exists.
122
137
func (s * Store ) Exists (ctx context.Context , target ocispec.Descriptor ) (bool , error ) {
138
+ s .sync .RLock ()
139
+ defer s .sync .RUnlock ()
140
+
123
141
return s .storage .Exists (ctx , target )
124
142
}
125
143
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
+
126
171
// Tag tags a descriptor with a reference string.
127
172
// 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
129
174
func (s * Store ) Tag (ctx context.Context , desc ocispec.Descriptor , reference string ) error {
175
+ s .sync .RLock ()
176
+ defer s .sync .RUnlock ()
177
+
130
178
if err := validateReference (reference ); err != nil {
131
179
return err
132
180
}
@@ -155,7 +203,7 @@ func (s *Store) tag(ctx context.Context, desc ocispec.Descriptor, reference stri
155
203
return err
156
204
}
157
205
if s .AutoSaveIndex {
158
- return s .SaveIndex ()
206
+ return s .saveIndex ()
159
207
}
160
208
return nil
161
209
}
@@ -166,6 +214,9 @@ func (s *Store) tag(ctx context.Context, desc ocispec.Descriptor, reference stri
166
214
// digest the returned descriptor will be a plain descriptor (containing only
167
215
// the digest, media type and size).
168
216
func (s * Store ) Resolve (ctx context.Context , reference string ) (ocispec.Descriptor , error ) {
217
+ s .sync .RLock ()
218
+ defer s .sync .RUnlock ()
219
+
169
220
if reference == "" {
170
221
return ocispec.Descriptor {}, errdef .ErrMissingReference
171
222
}
@@ -191,6 +242,9 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript
191
242
// Predecessors returns nil without error if the node does not exists in the
192
243
// store.
193
244
func (s * Store ) Predecessors (ctx context.Context , node ocispec.Descriptor ) ([]ocispec.Descriptor , error ) {
245
+ s .sync .RLock ()
246
+ defer s .sync .RUnlock ()
247
+
194
248
return s .graph .Predecessors (ctx , node )
195
249
}
196
250
@@ -202,6 +256,9 @@ func (s *Store) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]oc
202
256
//
203
257
// See also `Tags()` in the package `registry`.
204
258
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
+
205
262
return listTags (ctx , s .tagResolver , last , fn )
206
263
}
207
264
@@ -263,10 +320,18 @@ func (s *Store) loadIndexFile(ctx context.Context) error {
263
320
264
321
// SaveIndex writes the `index.json` file to the file system.
265
322
// - 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.
267
325
// - If AutoSaveIndex is set to false, it's the caller's responsibility
268
326
// to manually call this method when needed.
269
327
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 {
270
335
s .indexLock .Lock ()
271
336
defer s .indexLock .Unlock ()
272
337
0 commit comments