Content Store
The oras.content package provides the following two major content stores, which implement the Ingester and the Provider interfaces, in order to be used in oras.Pull() and oras.Push().
- FileStore to use the file system as the content store.
- MemoryStore to use the memory as the content store with the purpose of metadata cache or testing.
Most documentations are available at GoDoc. In this article, some best practices and advanced usage of this package is documented.
FileStore
FileStore
provides contents from the file system. Since its Add() method may require temporary file creation (e.g. add a directory other than a regular file), it is a good practice to call Close() after use.
store := content.NewFileStore(".")
defer store.Close()
Saving Files to Alternative Paths
By default, the files are saved to the relative path specified by its name (i.e. AnnotationTitle, which can be obtained by ResolveName() from a Descriptor). For example, a file of name hi.txt
is saved to hi.txt
. If the caller knows the file name in advance, it can invoke the thread-safe method MapPath() to relocate the path to store the file. For example, MapPath("hi.txt", "hello.txt")
will save the file of name hi.txt
to hello.txt
.
It is also possible to relocate the path when pulling with the oras.WithPullBaseHandler() option. For example:
_, _, err = oras.Pull(ctx, resolver, ref, store,
oras.WithPullBaseHandler(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
if desc.MediaType == "the.desired.media.type" {
name, _ := content.ResolveName(desc)
store.MapPath(name, "desired/path")
}
return nil, nil
})))
Hybrid Store
FileStore and MemoryStore can be combined to create many other advanced stores.
For instance, a layered store with a writable MemoryStore on the top and a read-only FileStore at the bottom is handy if the caller wants to push additional content (e.g. custom config or layers) to the remote without tampering the existing file system.
package example
import (
"context"
"github.com/containerd/containerd/content"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
orascontent "oras.land/oras-go/pkg/content"
)
// LayeredStore has a writable cache on the top and a provider at the bottom
type LayeredStore struct {
*orascontent.Memorystore
provider content.Provider
}
// NewLayeredStore create a new layered store
func NewLayeredStore(provider content.Provider) *LayeredStore {
return &LayeredStore{
Memorystore: orascontent.NewMemoryStore(),
provider: provider,
}
}
// ReaderAt reads from the cache first and fallback to the provider
func (s *LayeredStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
readerAt, err := s.Memorystore.ReaderAt(ctx, desc)
if err == nil {
return readerAt, nil
}
if s.provider != nil {
return s.provider.ReaderAt(ctx, desc)
}
return nil, err
}