@@ -20,16 +20,19 @@ package oci
20
20
import (
21
21
"context"
22
22
"encoding/json"
23
+ "errors"
23
24
"fmt"
24
25
"io"
25
26
"io/ioutil"
26
27
"os"
27
28
"path/filepath"
29
+ "sync"
28
30
29
31
specs "github.com/opencontainers/image-spec/specs-go"
30
32
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
31
33
"oras.land/oras-go/v2/content"
32
34
"oras.land/oras-go/v2/errdef"
35
+ "oras.land/oras-go/v2/internal/descriptor"
33
36
"oras.land/oras-go/v2/internal/graph"
34
37
"oras.land/oras-go/v2/internal/resolver"
35
38
)
@@ -53,11 +56,12 @@ type Store struct {
53
56
AutoSaveIndex bool
54
57
root string
55
58
indexPath string
59
+ index * ocispec.Index
60
+ indexLock sync.Mutex
56
61
57
- storage content.Storage
58
- resolver * resolver.Memory
59
- graph * graph.Memory
60
- index * ocispec.Index
62
+ storage content.Storage
63
+ tagResolver * resolver.Memory
64
+ graph * graph.Memory
61
65
}
62
66
63
67
// New creates a new OCI store with context.Background().
@@ -72,19 +76,17 @@ func NewWithContext(ctx context.Context, root string) (*Store, error) {
72
76
root : root ,
73
77
indexPath : filepath .Join (root , ociImageIndexFile ),
74
78
storage : NewStorage (root ),
75
- resolver : resolver .NewMemory (),
79
+ tagResolver : resolver .NewMemory (),
76
80
graph : graph .NewMemory (),
77
81
}
78
82
79
83
if err := ensureDir (root ); err != nil {
80
84
return nil , err
81
85
}
82
-
83
86
if err := store .ensureOCILayoutFile (); err != nil {
84
87
return nil , err
85
88
}
86
-
87
- if err := store .loadIndex (ctx ); err != nil {
89
+ if err := store .loadIndexFile (ctx ); err != nil {
88
90
return nil , err
89
91
}
90
92
@@ -101,8 +103,14 @@ func (s *Store) Push(ctx context.Context, expected ocispec.Descriptor, reader io
101
103
if err := s .storage .Push (ctx , expected , reader ); err != nil {
102
104
return err
103
105
}
104
-
105
- return s .graph .Index (ctx , s .storage , expected )
106
+ if err := s .graph .Index (ctx , s .storage , expected ); err != nil {
107
+ return err
108
+ }
109
+ if descriptor .IsManifest (expected ) {
110
+ // tag by digest
111
+ return s .tag (ctx , expected , expected .Digest .String ())
112
+ }
113
+ return nil
106
114
}
107
115
108
116
// Exists returns true if the described content exists.
@@ -111,8 +119,7 @@ func (s *Store) Exists(ctx context.Context, target ocispec.Descriptor) (bool, er
111
119
}
112
120
113
121
// Tag tags a descriptor with a reference string.
114
- // A reference should be either a valid tag (e.g. "latest"),
115
- // or a digest matching the descriptor (e.g. "@sha256:abc123").
122
+ // reference should be a valid tag (e.g. "latest").
116
123
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md#indexjson-file
117
124
func (s * Store ) Tag (ctx context.Context , desc ocispec.Descriptor , reference string ) error {
118
125
if err := validateReference (reference ); err != nil {
@@ -131,11 +138,21 @@ func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference stri
131
138
desc .Annotations = map [string ]string {}
132
139
}
133
140
desc .Annotations [ocispec .AnnotationRefName ] = reference
141
+ return s .tag (ctx , desc , reference )
142
+ }
134
143
135
- if err := s .resolver .Tag (ctx , desc , reference ); err != nil {
144
+ // tag tags a descriptor with a reference string.
145
+ func (s * Store ) tag (ctx context.Context , desc ocispec.Descriptor , reference string ) error {
146
+ dgst := desc .Digest .String ()
147
+ if reference != dgst {
148
+ // mark desc for deduplication in SaveIndex()
149
+ if err := s .tagResolver .Tag (ctx , desc , dgst ); err != nil {
150
+ return err
151
+ }
152
+ }
153
+ if err := s .tagResolver .Tag (ctx , desc , reference ); err != nil {
136
154
return err
137
155
}
138
-
139
156
if s .AutoSaveIndex {
140
157
return s .SaveIndex ()
141
158
}
@@ -148,7 +165,16 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript
148
165
return ocispec.Descriptor {}, errdef .ErrMissingReference
149
166
}
150
167
151
- return s .resolver .Resolve (ctx , reference )
168
+ // attempt resolving manifest
169
+ desc , err := s .tagResolver .Resolve (ctx , reference )
170
+ if err != nil {
171
+ if errors .Is (err , errdef .ErrNotFound ) {
172
+ // attempt resolving blob
173
+ return resolveBlob (os .DirFS (s .root ), reference )
174
+ }
175
+ return ocispec.Descriptor {}, err
176
+ }
177
+ return descriptor .Plain (desc ), nil
152
178
}
153
179
154
180
// Predecessors returns the nodes directly pointing to the current node.
@@ -179,20 +205,16 @@ func (s *Store) ensureOCILayoutFile() error {
179
205
}
180
206
defer layoutFile .Close ()
181
207
182
- var layout * ocispec.ImageLayout
208
+ var layout ocispec.ImageLayout
183
209
err = json .NewDecoder (layoutFile ).Decode (& layout )
184
210
if err != nil {
185
211
return fmt .Errorf ("failed to decode OCI layout file: %w" , err )
186
212
}
187
- if layout .Version != ocispec .ImageLayoutVersion {
188
- return errdef .ErrUnsupportedVersion
189
- }
190
-
191
- return nil
213
+ return validateOCILayout (& layout )
192
214
}
193
215
194
- // loadIndex reads the index.json from the file system.
195
- func (s * Store ) loadIndex (ctx context.Context ) error {
216
+ // loadIndexFile reads index.json from the file system.
217
+ func (s * Store ) loadIndexFile (ctx context.Context ) error {
196
218
indexFile , err := os .Open (s .indexPath )
197
219
if err != nil {
198
220
if ! os .IsNotExist (err ) {
@@ -207,24 +229,12 @@ func (s *Store) loadIndex(ctx context.Context) error {
207
229
}
208
230
defer indexFile .Close ()
209
231
210
- if err := json .NewDecoder (indexFile ).Decode (& s .index ); err != nil {
232
+ var index ocispec.Index
233
+ if err := json .NewDecoder (indexFile ).Decode (& index ); err != nil {
211
234
return fmt .Errorf ("failed to decode index file: %w" , err )
212
235
}
213
-
214
- for _ , desc := range s .index .Manifests {
215
- if ref := desc .Annotations [ocispec .AnnotationRefName ]; ref != "" {
216
- if err = s .resolver .Tag (ctx , desc , ref ); err != nil {
217
- return err
218
- }
219
- }
220
-
221
- // traverse the whole DAG and index predecessors for all the nodes.
222
- if err := s .graph .IndexAll (ctx , s .storage , desc ); err != nil {
223
- return err
224
- }
225
- }
226
-
227
- return nil
236
+ s .index = & index
237
+ return loadIndex (ctx , s .index , s .storage , s .tagResolver , s .graph )
228
238
}
229
239
230
240
// SaveIndex writes the `index.json` file to the file system.
@@ -233,10 +243,16 @@ func (s *Store) loadIndex(ctx context.Context) error {
233
243
// If AutoSaveIndex is set to false, it's the caller's responsibility
234
244
// to manually call this method when needed.
235
245
func (s * Store ) SaveIndex () error {
236
- // first need to update the index.
246
+ s .indexLock .Lock ()
247
+ defer s .indexLock .Unlock ()
248
+
237
249
var manifests []ocispec.Descriptor
238
- refMap := s .resolver .Map ()
239
- for _ , desc := range refMap {
250
+ refMap := s .tagResolver .Map ()
251
+ for ref , desc := range refMap {
252
+ if ref == desc .Digest .String () && desc .Annotations [ocispec .AnnotationRefName ] != "" {
253
+ // skip saving desc if ref is a digest and desc is tagged
254
+ continue
255
+ }
240
256
manifests = append (manifests , desc )
241
257
}
242
258
0 commit comments