Skip to content

Commit

Permalink
add multireader (oras-project#199)
Browse files Browse the repository at this point in the history
Signed-off-by: Avi Deitcher <[email protected]>

Co-authored-by: Josh Dolitsky <[email protected]>
  • Loading branch information
deitch and jdolitsky authored Nov 17, 2020
1 parent 0bffdbc commit 9555bb8
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 0 deletions.
40 changes: 40 additions & 0 deletions pkg/content/multireader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package content

import (
"context"
"fmt"

"github.com/containerd/containerd/content"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

// MultiReader store to read content from multiple stores. It finds the content by asking each underlying
// store to find the content, which it does based on the hash.
//
// Example:
// fileStore := NewFileStore(rootPath)
// memoryStore := NewMemoryStore()
// // load up content in fileStore and memoryStore
// multiStore := MultiReader([]content.Provider{fileStore, memoryStore})
//
// You now can use multiStore anywhere that content.Provider is accepted
type MultiReader struct {
stores []content.Provider
}

// AddStore add a store to read from
func (m *MultiReader) AddStore(store ...content.Provider) {
m.stores = append(m.stores, store...)
}

// ReaderAt get a reader
func (m MultiReader) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
for _, store := range m.stores {
r, err := store.ReaderAt(ctx, desc)
if r != nil && err == nil {
return r, nil
}
}
// we did not find any
return nil, fmt.Errorf("not found")
}
62 changes: 62 additions & 0 deletions pkg/content/multireader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package content_test

import (
"context"
"io/ioutil"
"testing"

ctrcontent "github.com/containerd/containerd/content"
"github.com/deislabs/oras/pkg/content"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

var (
testContentA = []byte("Hello World!")
testContentHashA = digest.FromBytes(testContentA)
testContentB = []byte("So long and thanks for all the fish!")
testContentHashB = digest.FromBytes(testContentB)
testDescriptorA = ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageConfig,
Digest: testContentHashA,
Size: int64(len(testContentA)),
}
testDescriptorB = ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageConfig,
Digest: testContentHashB,
Size: int64(len(testContentB)),
}
)

func TestMultiReader(t *testing.T) {
mem1, mem2 := content.NewMemoryStore(), content.NewMemoryStore()
mem1.Add("a", ocispec.MediaTypeImageConfig, testContentA)
mem2.Add("b", ocispec.MediaTypeImageConfig, testContentB)
multiReader := content.MultiReader{}
multiReader.AddStore(mem1, mem2)

ctx := context.Background()
contentA, err := multiReader.ReaderAt(ctx, testDescriptorA)
if err != nil {
t.Fatalf("failed to get a reader for descriptor A: %v", err)
}
outputA, err := ioutil.ReadAll(ctrcontent.NewReader(contentA))
if err != nil {
t.Fatalf("failed to read content for descriptor A: %v", err)
}
if string(outputA) != string(testContentA) {
t.Errorf("mismatched content for A, actual '%s', expected '%s'", outputA, testContentA)
}

contentB, err := multiReader.ReaderAt(ctx, testDescriptorB)
if err != nil {
t.Fatalf("failed to get a reader for descriptor B: %v", err)
}
outputB, err := ioutil.ReadAll(ctrcontent.NewReader(contentB))
if err != nil {
t.Fatalf("failed to read content for descriptor B: %v", err)
}
if string(outputB) != string(testContentB) {
t.Errorf("mismatched content for B, actual '%s', expected '%s'", outputB, testContentB)
}
}

0 comments on commit 9555bb8

Please sign in to comment.