Skip to content

Commit 4e58192

Browse files
authored
refactor: refactor OCI store to fully support Predecessors() and Resolve() (#385)
1. Support discovering predecessors that are not tagged 2. Support resolving a manifest by its digest 3. Support resolving a blob by its digest Resolves: #342 Signed-off-by: Lixia (Sylvia) Lei <lixlei@microsoft.com>
1 parent 4b1d016 commit 4e58192

8 files changed

+480
-504
lines changed

content/descriptor.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,14 @@ package content
1818
import (
1919
"github.com/opencontainers/go-digest"
2020
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
21+
"oras.land/oras-go/v2/internal/descriptor"
2122
)
2223

23-
// defaultMediaType is the media type used when no media type is specified.
24-
const defaultMediaType string = "application/octet-stream"
25-
2624
// NewDescriptorFromBytes returns a descriptor, given the content and media type.
2725
// If no media type is specified, "application/octet-stream" will be used.
2826
func NewDescriptorFromBytes(mediaType string, content []byte) ocispec.Descriptor {
2927
if mediaType == "" {
30-
mediaType = defaultMediaType
28+
mediaType = descriptor.DefaultMediaType
3129
}
3230
return ocispec.Descriptor{
3331
MediaType: mediaType,

content/descriptor_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/opencontainers/go-digest"
2323
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24+
"oras.land/oras-go/v2/internal/descriptor"
2425
)
2526

2627
func TestGenerateDescriptor(t *testing.T) {
@@ -56,7 +57,7 @@ func TestGenerateDescriptor(t *testing.T) {
5657
name: "missing media type",
5758
args: args{contentBar, ""},
5859
want: ocispec.Descriptor{
59-
MediaType: defaultMediaType,
60+
MediaType: descriptor.DefaultMediaType,
6061
Digest: digest.FromBytes(contentBar),
6162
Size: int64(len(contentBar))},
6263
},

content/oci/oci.go

+58-42
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@ package oci
2020
import (
2121
"context"
2222
"encoding/json"
23+
"errors"
2324
"fmt"
2425
"io"
2526
"io/ioutil"
2627
"os"
2728
"path/filepath"
29+
"sync"
2830

2931
specs "github.com/opencontainers/image-spec/specs-go"
3032
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3133
"oras.land/oras-go/v2/content"
3234
"oras.land/oras-go/v2/errdef"
35+
"oras.land/oras-go/v2/internal/descriptor"
3336
"oras.land/oras-go/v2/internal/graph"
3437
"oras.land/oras-go/v2/internal/resolver"
3538
)
@@ -53,11 +56,12 @@ type Store struct {
5356
AutoSaveIndex bool
5457
root string
5558
indexPath string
59+
index *ocispec.Index
60+
indexLock sync.Mutex
5661

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
6165
}
6266

6367
// New creates a new OCI store with context.Background().
@@ -72,19 +76,17 @@ func NewWithContext(ctx context.Context, root string) (*Store, error) {
7276
root: root,
7377
indexPath: filepath.Join(root, ociImageIndexFile),
7478
storage: NewStorage(root),
75-
resolver: resolver.NewMemory(),
79+
tagResolver: resolver.NewMemory(),
7680
graph: graph.NewMemory(),
7781
}
7882

7983
if err := ensureDir(root); err != nil {
8084
return nil, err
8185
}
82-
8386
if err := store.ensureOCILayoutFile(); err != nil {
8487
return nil, err
8588
}
86-
87-
if err := store.loadIndex(ctx); err != nil {
89+
if err := store.loadIndexFile(ctx); err != nil {
8890
return nil, err
8991
}
9092

@@ -101,8 +103,14 @@ func (s *Store) Push(ctx context.Context, expected ocispec.Descriptor, reader io
101103
if err := s.storage.Push(ctx, expected, reader); err != nil {
102104
return err
103105
}
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
106114
}
107115

108116
// Exists returns true if the described content exists.
@@ -111,8 +119,7 @@ func (s *Store) Exists(ctx context.Context, target ocispec.Descriptor) (bool, er
111119
}
112120

113121
// 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").
116123
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md#indexjson-file
117124
func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error {
118125
if err := validateReference(reference); err != nil {
@@ -131,11 +138,21 @@ func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference stri
131138
desc.Annotations = map[string]string{}
132139
}
133140
desc.Annotations[ocispec.AnnotationRefName] = reference
141+
return s.tag(ctx, desc, reference)
142+
}
134143

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 {
136154
return err
137155
}
138-
139156
if s.AutoSaveIndex {
140157
return s.SaveIndex()
141158
}
@@ -148,7 +165,16 @@ func (s *Store) Resolve(ctx context.Context, reference string) (ocispec.Descript
148165
return ocispec.Descriptor{}, errdef.ErrMissingReference
149166
}
150167

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
152178
}
153179

154180
// Predecessors returns the nodes directly pointing to the current node.
@@ -179,20 +205,16 @@ func (s *Store) ensureOCILayoutFile() error {
179205
}
180206
defer layoutFile.Close()
181207

182-
var layout *ocispec.ImageLayout
208+
var layout ocispec.ImageLayout
183209
err = json.NewDecoder(layoutFile).Decode(&layout)
184210
if err != nil {
185211
return fmt.Errorf("failed to decode OCI layout file: %w", err)
186212
}
187-
if layout.Version != ocispec.ImageLayoutVersion {
188-
return errdef.ErrUnsupportedVersion
189-
}
190-
191-
return nil
213+
return validateOCILayout(&layout)
192214
}
193215

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 {
196218
indexFile, err := os.Open(s.indexPath)
197219
if err != nil {
198220
if !os.IsNotExist(err) {
@@ -207,24 +229,12 @@ func (s *Store) loadIndex(ctx context.Context) error {
207229
}
208230
defer indexFile.Close()
209231

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 {
211234
return fmt.Errorf("failed to decode index file: %w", err)
212235
}
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)
228238
}
229239

230240
// SaveIndex writes the `index.json` file to the file system.
@@ -233,10 +243,16 @@ func (s *Store) loadIndex(ctx context.Context) error {
233243
// If AutoSaveIndex is set to false, it's the caller's responsibility
234244
// to manually call this method when needed.
235245
func (s *Store) SaveIndex() error {
236-
// first need to update the index.
246+
s.indexLock.Lock()
247+
defer s.indexLock.Unlock()
248+
237249
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+
}
240256
manifests = append(manifests, desc)
241257
}
242258

0 commit comments

Comments
 (0)