Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: add read-only interfaces #283

Merged
merged 7 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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