Skip to content

Commit

Permalink
refactor!: add read-only interfaces (#283)
Browse files Browse the repository at this point in the history
Signed-off-by: Sylvia Lei <lixlei@microsoft.com>
  • Loading branch information
Wwwsylvia authored Aug 29, 2022
1 parent 98e72a2 commit 2946dfc
Show file tree
Hide file tree
Showing 12 changed files with 74 additions and 68 deletions.
2 changes: 1 addition & 1 deletion content.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type ResolveOptions struct {
}

// Resolve resolves a descriptor with provided reference from the target.
func Resolve(ctx context.Context, target Target, ref string, opts ResolveOptions) (ocispec.Descriptor, error) {
func Resolve(ctx context.Context, target ReadOnlyTarget, ref string, opts ResolveOptions) (ocispec.Descriptor, error) {
if opts.TargetPlatform == nil {
return target.Resolve(ctx, ref)
}
Expand Down
6 changes: 6 additions & 0 deletions content/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ type GraphStorage interface {
PredecessorFinder
}

// ReadOnlyGraphStorage represents a read-only GraphStorage.
type ReadOnlyGraphStorage interface {
ReadOnlyStorage
PredecessorFinder
}

// Successors returns the nodes directly pointed by the current node.
// In other words, returns the "children" of the current descriptor.
func Successors(ctx context.Context, fetcher Fetcher, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
Expand Down
7 changes: 6 additions & 1 deletion content/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ type Pusher interface {
// accessed via Descriptors.
// The storage is designed to handle blobs of large sizes.
type Storage interface {
Fetcher
ReadOnlyStorage
Pusher
}

// ReadOnlyStorage represents a read-only Storage.
type ReadOnlyStorage interface {
Fetcher

// Exists returns true if the described content exists.
Exists(ctx context.Context, target ocispec.Descriptor) (bool, error)
Expand Down
20 changes: 10 additions & 10 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ type CopyOptions struct {
// When MapRoot is provided, the descriptor resolved from the source
// reference will be passed to MapRoot, and the mapped descriptor will be
// used as the root node for copy.
MapRoot func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error)
MapRoot func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error)
}

// getPlatformFromConfig returns a platform object which is made up from the
// fields in config blob.
func getPlatformFromConfig(ctx context.Context, src content.Storage, desc ocispec.Descriptor, targetConfigMediaType string) (*ocispec.Platform, error) {
func getPlatformFromConfig(ctx context.Context, src content.ReadOnlyStorage, desc ocispec.Descriptor, targetConfigMediaType string) (*ocispec.Platform, error) {
if desc.MediaType != targetConfigMediaType {
return nil, fmt.Errorf("mismatch MediaType %s: expect %s", desc.MediaType, targetConfigMediaType)
}
Expand All @@ -81,7 +81,7 @@ func getPlatformFromConfig(ctx context.Context, src content.Storage, desc ocispe
// selectPlatform implements platform filter and returns the descriptor of the
// first matched manifest if the root is a manifest list. If the root is a
// manifest, then return the root descriptor if platform matches.
func selectPlatform(ctx context.Context, src content.Storage, root ocispec.Descriptor, p *ocispec.Platform) (ocispec.Descriptor, error) {
func selectPlatform(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor, p *ocispec.Platform) (ocispec.Descriptor, error) {
switch root.MediaType {
case docker.MediaTypeManifestList, ocispec.MediaTypeImageIndex:
manifests, err := content.Successors(ctx, src, root)
Expand Down Expand Up @@ -131,7 +131,7 @@ func selectPlatform(ctx context.Context, src content.Storage, root ocispec.Descr
// - Otherwise ErrUnsupported will be returned.
func (opts *CopyOptions) WithTargetPlatform(p *ocispec.Platform) {
mapRoot := opts.MapRoot
opts.MapRoot = func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (desc ocispec.Descriptor, err error) {
opts.MapRoot = func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (desc ocispec.Descriptor, err error) {
if mapRoot != nil {
if root, err = mapRoot(ctx, src, root); err != nil {
return ocispec.Descriptor{}, err
Expand Down Expand Up @@ -168,7 +168,7 @@ type CopyGraphOptions struct {
// The destination reference will be the same as the source reference if the
// destination reference is left blank.
// Returns the descriptor of the root node on successful copy.
func Copy(ctx context.Context, src Target, srcRef string, dst Target, dstRef string, opts CopyOptions) (ocispec.Descriptor, error) {
func Copy(ctx context.Context, src ReadOnlyTarget, srcRef string, dst Target, dstRef string, opts CopyOptions) (ocispec.Descriptor, error) {
if src == nil {
return ocispec.Descriptor{}, errors.New("nil source target")
}
Expand Down Expand Up @@ -208,15 +208,15 @@ func Copy(ctx context.Context, src Target, srcRef string, dst Target, dstRef str

// CopyGraph copies a rooted directed acyclic graph (DAG) from the source CAS to
// the destination CAS.
func CopyGraph(ctx context.Context, src, dst content.Storage, root ocispec.Descriptor, opts CopyGraphOptions) error {
func CopyGraph(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, root ocispec.Descriptor, opts CopyGraphOptions) error {
// use caching proxy on non-leaf nodes
proxy := cas.NewProxy(src, cas.NewMemory())
return copyGraph(ctx, src, dst, proxy, root, opts)
}

// copyGraph copies a rooted directed acyclic graph (DAG) from the source CAS to
// the destination CAS with specified caching.
func copyGraph(ctx context.Context, src, dst content.Storage, proxy *cas.Proxy, root ocispec.Descriptor, opts CopyGraphOptions) error {
func copyGraph(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, proxy *cas.Proxy, root ocispec.Descriptor, opts CopyGraphOptions) error {
// track content status
tracker := status.NewTracker()

Expand Down Expand Up @@ -301,7 +301,7 @@ func copyGraph(ctx context.Context, src, dst content.Storage, proxy *cas.Proxy,
}

// doCopyNode copies a single content from the source CAS to the destination CAS.
func doCopyNode(ctx context.Context, src, dst content.Storage, desc ocispec.Descriptor) error {
func doCopyNode(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, desc ocispec.Descriptor) error {
rc, err := src.Fetch(ctx, desc)
if err != nil {
return err
Expand All @@ -316,7 +316,7 @@ func doCopyNode(ctx context.Context, src, dst content.Storage, desc ocispec.Desc

// copyNode copies a single content from the source CAS to the destination CAS,
// and apply the given options.
func copyNode(ctx context.Context, src, dst content.Storage, desc ocispec.Descriptor, opts CopyGraphOptions) error {
func copyNode(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, desc ocispec.Descriptor, opts CopyGraphOptions) error {
if opts.PreCopy != nil {
if err := opts.PreCopy(ctx, desc); err != nil {
if err == graph.ErrSkipDesc {
Expand Down Expand Up @@ -353,7 +353,7 @@ func copyCachedNodeWithReference(ctx context.Context, src *cas.Proxy, dst regist
}

// resolveRoot resolves the source reference to the root node.
func resolveRoot(ctx context.Context, src Target, srcRef string, proxy *cas.Proxy) (ocispec.Descriptor, error) {
func resolveRoot(ctx context.Context, src ReadOnlyTarget, srcRef string, proxy *cas.Proxy) (ocispec.Descriptor, error) {
refFetcher, ok := src.(registry.ReferenceFetcher)
if !ok {
return src.Resolve(ctx, srcRef)
Expand Down
8 changes: 4 additions & 4 deletions copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ func TestCopy_WithOptions(t *testing.T) {
// test copy with media type filter
dst := memory.New()
opts := oras.DefaultCopyOptions
opts.MapRoot = func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
opts.MapRoot = func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
if root.MediaType == ocispec.MediaTypeImageIndex {
return root, nil
} else {
Expand Down Expand Up @@ -580,7 +580,7 @@ func TestCopy_WithOptions(t *testing.T) {
preCopyCount := int64(0)
postCopyCount := int64(0)
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
manifests, err := content.Successors(ctx, src, root)
if err != nil {
return ocispec.Descriptor{}, errdef.ErrNotFound
Expand Down Expand Up @@ -645,7 +645,7 @@ func TestCopy_WithOptions(t *testing.T) {
// test copy with root filter, but no matching node can be found
dst = memory.New()
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
if root.MediaType == "test" {
return root, nil
} else {
Expand Down Expand Up @@ -826,7 +826,7 @@ func TestCopy_WithTargetPlatformOptions(t *testing.T) {
// index, but there is no matching node. Should return not found error.
dst = memory.New()
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
if root.MediaType == ocispec.MediaTypeImageIndex {
return root, nil
} else {
Expand Down
14 changes: 7 additions & 7 deletions extendedcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ type ExtendedCopyGraphOptions struct {
Depth int
// FindPredecessors finds the predecessors of the current node.
// If FindPredecessors is nil, src.Predecessors will be adapted and used.
FindPredecessors func(ctx context.Context, src content.GraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error)
FindPredecessors func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error)
}

// ExtendedCopy copies the directed acyclic graph (DAG) that are reachable from
// the given tagged node from the source GraphTarget to the destination Target.
// The destination reference will be the same as the source reference if the
// destination reference is left blank.
// Returns the descriptor of the tagged node on successful copy.
func ExtendedCopy(ctx context.Context, src GraphTarget, srcRef string, dst Target, dstRef string, opts ExtendedCopyOptions) (ocispec.Descriptor, error) {
func ExtendedCopy(ctx context.Context, src ReadOnlyGraphTarget, srcRef string, dst Target, dstRef string, opts ExtendedCopyOptions) (ocispec.Descriptor, error) {
if src == nil {
return ocispec.Descriptor{}, errors.New("nil source graph target")
}
Expand Down Expand Up @@ -93,7 +93,7 @@ func ExtendedCopy(ctx context.Context, src GraphTarget, srcRef string, dst Targe

// ExtendedCopyGraph copies the directed acyclic graph (DAG) that are reachable
// from the given node from the source GraphStorage to the destination Storage.
func ExtendedCopyGraph(ctx context.Context, src content.GraphStorage, dst content.Storage, node ocispec.Descriptor, opts ExtendedCopyGraphOptions) error {
func ExtendedCopyGraph(ctx context.Context, src content.ReadOnlyGraphStorage, dst content.Storage, node ocispec.Descriptor, opts ExtendedCopyGraphOptions) error {
roots, err := findRoots(ctx, src, node, opts)
if err != nil {
return err
Expand All @@ -111,7 +111,7 @@ func ExtendedCopyGraph(ctx context.Context, src content.GraphStorage, dst conten

// findRoots finds the root nodes reachable from the given node through a
// depth-first search.
func findRoots(ctx context.Context, storage content.GraphStorage, node ocispec.Descriptor, opts ExtendedCopyGraphOptions) (map[descriptor.Descriptor]ocispec.Descriptor, error) {
func findRoots(ctx context.Context, storage content.ReadOnlyGraphStorage, node ocispec.Descriptor, opts ExtendedCopyGraphOptions) (map[descriptor.Descriptor]ocispec.Descriptor, error) {
visited := make(map[descriptor.Descriptor]bool)
roots := make(map[descriptor.Descriptor]ocispec.Descriptor)
addRoot := func(key descriptor.Descriptor, val ocispec.Descriptor) {
Expand All @@ -122,7 +122,7 @@ func findRoots(ctx context.Context, storage content.GraphStorage, node ocispec.D

// if FindPredecessors is not provided, use the default one
if opts.FindPredecessors == nil {
opts.FindPredecessors = func(ctx context.Context, src content.GraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
opts.FindPredecessors = func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
return src.Predecessors(ctx, desc)
}
}
Expand Down Expand Up @@ -182,7 +182,7 @@ func findRoots(ctx context.Context, storage content.GraphStorage, node ocispec.D
// FilterArtifactType first.
func (opts *ExtendedCopyGraphOptions) FilterAnnotation(key string, regex *regexp.Regexp) {
fp := opts.FindPredecessors
opts.FindPredecessors = func(ctx context.Context, src content.GraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
opts.FindPredecessors = func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
var predecessors []ocispec.Descriptor
var err error
if fp == nil {
Expand Down Expand Up @@ -237,7 +237,7 @@ func (opts *ExtendedCopyGraphOptions) FilterAnnotation(key string, regex *regexp
// FilterArtifactType first.
func (opts *ExtendedCopyGraphOptions) FilterArtifactType(regex *regexp.Regexp) {
fp := opts.FindPredecessors
opts.FindPredecessors = func(ctx context.Context, src content.GraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
opts.FindPredecessors = func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
var predecessors []ocispec.Descriptor
var err error
if fp == nil {
Expand Down
2 changes: 1 addition & 1 deletion extendedcopy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ func TestExtendedCopyGraph_WithFindPredecessorsOption(t *testing.T) {
// test extended copy by descs[3] with media type filter
dst := memory.New()
opts := oras.ExtendedCopyGraphOptions{
FindPredecessors: func(ctx context.Context, src content.GraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
FindPredecessors: func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
predecessors, err := src.Predecessors(ctx, desc)
if err != nil {
return nil, err
Expand Down
14 changes: 7 additions & 7 deletions internal/cas/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ import (
// cache the fetched content.
// The subsequent fetch call will read from the local cache.
type Proxy struct {
content.Storage
content.ReadOnlyStorage
Cache content.Storage
StopCaching bool
}

// NewProxy creates a proxy for the `base` storage, using the `cache` storage as
// the cache.
func NewProxy(base, cache content.Storage) *Proxy {
func NewProxy(base content.ReadOnlyStorage, cache content.Storage) *Proxy {
return &Proxy{
Storage: base,
Cache: cache,
ReadOnlyStorage: base,
Cache: cache,
}
}

Expand All @@ -55,7 +55,7 @@ func (p *Proxy) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCl
return rc, nil
}

rc, err = p.Storage.Fetch(ctx, target)
rc, err = p.ReadOnlyStorage.Fetch(ctx, target)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -99,7 +99,7 @@ func (p *Proxy) FetchCached(ctx context.Context, target ocispec.Descriptor) (io.
if exists {
return p.Cache.Fetch(ctx, target)
}
return p.Storage.Fetch(ctx, target)
return p.ReadOnlyStorage.Fetch(ctx, target)
}

// Exists returns true if the described content exists.
Expand All @@ -108,5 +108,5 @@ func (p *Proxy) Exists(ctx context.Context, target ocispec.Descriptor) (bool, er
if err == nil && exists {
return true, nil
}
return p.Storage.Exists(ctx, target)
return p.ReadOnlyStorage.Exists(ctx, target)
}
53 changes: 18 additions & 35 deletions internal/cas/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ func TestProxyCache(t *testing.T) {
Size: int64(len(content)),
}

s := NewProxy(NewMemory(), NewMemory())
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
base := NewMemory()
err := base.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Proxy.Push() error =", err)
t.Fatal("Memory.Push() error =", err)
}
s := NewProxy(base, NewMemory())

// first fetch
exists, err := s.Exists(ctx, desc)
Expand Down Expand Up @@ -68,7 +68,7 @@ func TestProxyCache(t *testing.T) {

// repeated fetch should not touch base CAS
// nil base will generate panic if the base CAS is touched
s.Storage = nil
s.ReadOnlyStorage = nil

exists, err = s.Exists(ctx, desc)
if err != nil {
Expand All @@ -94,23 +94,6 @@ func TestProxyCache(t *testing.T) {
}
}

func TestProxyPushPassThrough(t *testing.T) {
content := []byte("hello world")
desc := ocispec.Descriptor{
MediaType: "test",
Digest: digest.FromBytes(content),
Size: int64(len(content)),
}

s := NewProxy(NewMemory(), nil)
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Proxy.Push() error =", err)
}
}

func TestProxy_FetchCached_NotCachedContent(t *testing.T) {
content := []byte("hello world")
desc := ocispec.Descriptor{
Expand All @@ -119,13 +102,13 @@ func TestProxy_FetchCached_NotCachedContent(t *testing.T) {
Size: int64(len(content)),
}

s := NewProxy(NewMemory(), NewMemory())
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
base := NewMemory()
err := base.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Proxy.Push() error =", err)
t.Fatal("Memory.Push() error =", err)
}
s := NewProxy(base, NewMemory())

// FetchCached should fetch from the base CAS
exists, err := s.Exists(ctx, desc)
Expand Down Expand Up @@ -170,13 +153,13 @@ func TestProxy_FetchCached_CachedContent(t *testing.T) {
Size: int64(len(content)),
}

s := NewProxy(NewMemory(), NewMemory())
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
base := NewMemory()
err := base.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Proxy.Push() error =", err)
t.Fatal("Memory.Push() error =", err)
}
s := NewProxy(base, NewMemory())

// first fetch
exists, err := s.Exists(ctx, desc)
Expand Down Expand Up @@ -204,7 +187,7 @@ func TestProxy_FetchCached_CachedContent(t *testing.T) {

// the subsequent FetchCached should not touch base CAS
// nil base will generate panic if the base CAS is touched
s.Storage = nil
s.ReadOnlyStorage = nil

exists, err = s.Exists(ctx, desc)
if err != nil {
Expand Down Expand Up @@ -238,13 +221,13 @@ func TestProxy_StopCaching(t *testing.T) {
Size: int64(len(content)),
}

s := NewProxy(NewMemory(), NewMemory())
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
base := NewMemory()
err := base.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Proxy.Push() error =", err)
t.Fatal("Memory.Push() error =", err)
}
s := NewProxy(base, NewMemory())

// FetchCached should fetch from the base CAS
exists, err := s.Exists(ctx, desc)
Expand Down
Loading

0 comments on commit 2946dfc

Please sign in to comment.