diff --git a/content.go b/content.go index 08382cf6..126b411f 100644 --- a/content.go +++ b/content.go @@ -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) } diff --git a/content/graph.go b/content/graph.go index d84eb38a..a9e7e5ae 100644 --- a/content/graph.go +++ b/content/graph.go @@ -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) { diff --git a/content/storage.go b/content/storage.go index 2c2687fd..971142cb 100644 --- a/content/storage.go +++ b/content/storage.go @@ -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) diff --git a/copy.go b/copy.go index 74202e12..96bfad80 100644 --- a/copy.go +++ b/copy.go @@ -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) } @@ -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) @@ -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 @@ -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") } @@ -208,7 +208,7 @@ 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) @@ -216,7 +216,7 @@ func CopyGraph(ctx context.Context, src, dst content.Storage, root ocispec.Descr // 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() @@ -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 @@ -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 { @@ -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) diff --git a/copy_test.go b/copy_test.go index 89206515..9f7960e3 100644 --- a/copy_test.go +++ b/copy_test.go @@ -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 { @@ -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 @@ -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 { @@ -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 { diff --git a/extendedcopy.go b/extendedcopy.go index 2830bc21..0ffadc03 100644 --- a/extendedcopy.go +++ b/extendedcopy.go @@ -56,7 +56,7 @@ 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 @@ -64,7 +64,7 @@ type ExtendedCopyGraphOptions struct { // 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") } @@ -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 @@ -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) { @@ -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) } } @@ -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 { @@ -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 { diff --git a/extendedcopy_test.go b/extendedcopy_test.go index e9960eca..d2835d06 100644 --- a/extendedcopy_test.go +++ b/extendedcopy_test.go @@ -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 diff --git a/internal/cas/proxy.go b/internal/cas/proxy.go index 6de66de8..a1df36b4 100644 --- a/internal/cas/proxy.go +++ b/internal/cas/proxy.go @@ -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, } } @@ -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 } @@ -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. @@ -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) } diff --git a/internal/cas/proxy_test.go b/internal/cas/proxy_test.go index 6ca65da6..e9860bf4 100644 --- a/internal/cas/proxy_test.go +++ b/internal/cas/proxy_test.go @@ -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) @@ -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 { @@ -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{ @@ -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) @@ -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) @@ -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 { @@ -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) diff --git a/internal/registryutil/proxy.go b/internal/registryutil/proxy.go index af6074f7..bf01fd14 100644 --- a/internal/registryutil/proxy.go +++ b/internal/registryutil/proxy.go @@ -29,7 +29,7 @@ import ( // ReferenceStorage represents a CAS that supports registry.ReferenceFetcher. type ReferenceStorage interface { - content.Storage + content.ReadOnlyStorage registry.ReferenceFetcher } diff --git a/internal/registryutil/proxy_test.go b/internal/registryutil/proxy_test.go index 7d9adab8..eb1ac618 100644 --- a/internal/registryutil/proxy_test.go +++ b/internal/registryutil/proxy_test.go @@ -115,7 +115,7 @@ func TestProxy_FetchReference(t *testing.T) { // the subsequent 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 { diff --git a/target.go b/target.go index f565f7bd..c6dcaef9 100644 --- a/target.go +++ b/target.go @@ -29,3 +29,15 @@ type GraphTarget interface { content.GraphStorage content.TagResolver } + +// ReadOnlyTarget represents a read-only Target. +type ReadOnlyTarget interface { + content.ReadOnlyStorage + content.Resolver +} + +// ReadOnlyGraphTarget represents a read-only GraphTarget. +type ReadOnlyGraphTarget interface { + content.ReadOnlyGraphStorage + content.Resolver +}