Skip to content

Commit

Permalink
fix!: use absolute path for OCI root (#412)
Browse files Browse the repository at this point in the history
Fixes: #404 
BREAKING CHANGE: `oci.NewStorage()` now returns an error

Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
  • Loading branch information
Wwwsylvia authored Jan 17, 2023
1 parent 9867c6a commit 05595eb
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 21 deletions.
17 changes: 13 additions & 4 deletions content/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,25 @@ func New(root string) (*Store, error) {

// NewWithContext creates a new OCI store.
func NewWithContext(ctx context.Context, root string) (*Store, error) {
rootAbs, err := filepath.Abs(root)
if err != nil {
return nil, fmt.Errorf("failed to resolve absolute path for %s: %w", root, err)
}
storage, err := NewStorage(rootAbs)
if err != nil {
return nil, fmt.Errorf("failed to create storage: %w", err)
}

store := &Store{
AutoSaveIndex: true,
root: root,
indexPath: filepath.Join(root, ociImageIndexFile),
storage: NewStorage(root),
root: rootAbs,
indexPath: filepath.Join(rootAbs, ociImageIndexFile),
storage: storage,
tagResolver: resolver.NewMemory(),
graph: graph.NewMemory(),
}

if err := ensureDir(root); err != nil {
if err := ensureDir(rootAbs); err != nil {
return nil, err
}
if err := store.ensureOCILayoutFile(); err != nil {
Expand Down
126 changes: 126 additions & 0 deletions content/oci/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,132 @@ func TestStore_Success(t *testing.T) {
}
}

func TestStore_RelativeRoot_Success(t *testing.T) {
blob := []byte("test")
blobDesc := content.NewDescriptorFromBytes("test", blob)
manifest := []byte(`{"layers":[]}`)
manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest)
ref := "foobar"

tempDir := t.TempDir()
currDir, err := os.Getwd()
if err != nil {
t.Fatal("error calling Getwd(), error=", err)
}
if err := os.Chdir(tempDir); err != nil {
t.Fatal("error calling Chdir(), error=", err)
}
s, err := New(".")
if err != nil {
t.Fatal("New() error =", err)
}
if want := tempDir; s.root != want {
t.Errorf("Store.root = %s, want %s", s.root, want)
}
// cd back to allow the temp directory to be removed
if err := os.Chdir(currDir); err != nil {
t.Fatal("error calling Chdir(), error=", err)
}
ctx := context.Background()

// validate layout
layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile)
layoutFile, err := os.Open(layoutFilePath)
if err != nil {
t.Errorf("error opening layout file, error = %v", err)
}
defer layoutFile.Close()

var layout *ocispec.ImageLayout
err = json.NewDecoder(layoutFile).Decode(&layout)
if err != nil {
t.Fatal("error decoding layout, error =", err)
}
if want := ocispec.ImageLayoutVersion; layout.Version != want {
t.Errorf("layout.Version = %s, want %s", layout.Version, want)
}

// test push blob
err = s.Push(ctx, blobDesc, bytes.NewReader(blob))
if err != nil {
t.Fatal("Store.Push() error =", err)
}
internalResolver := s.tagResolver
if got, want := len(internalResolver.Map()), 0; got != want {
t.Errorf("resolver.Map() = %v, want %v", got, want)
}

// test push manifest
err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest))
if err != nil {
t.Fatal("Store.Push() error =", err)
}
if got, want := len(internalResolver.Map()), 1; got != want {
t.Errorf("resolver.Map() = %v, want %v", got, want)
}

// test resolving blob by digest
gotDesc, err := s.Resolve(ctx, blobDesc.Digest.String())
if err != nil {
t.Fatal("Store.Resolve() error =", err)
}
if want := blobDesc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest {
t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc)
}

// test resolving manifest by digest
gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String())
if err != nil {
t.Fatal("Store.Resolve() error =", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc)
}

// test tag
err = s.Tag(ctx, manifestDesc, ref)
if err != nil {
t.Fatal("Store.Tag() error =", err)
}
if got, want := len(internalResolver.Map()), 2; got != want {
t.Errorf("resolver.Map() = %v, want %v", got, want)
}

// test resolving manifest by tag
gotDesc, err = s.Resolve(ctx, ref)
if err != nil {
t.Fatal("Store.Resolve() error =", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc)
}

// test fetch
exists, err := s.Exists(ctx, manifestDesc)
if err != nil {
t.Fatal("Store.Exists() error =", err)
}
if !exists {
t.Errorf("Store.Exists() = %v, want %v", exists, true)
}

rc, err := s.Fetch(ctx, manifestDesc)
if err != nil {
t.Fatal("Store.Fetch() error =", err)
}
got, err := io.ReadAll(rc)
if err != nil {
t.Fatal("Store.Fetch().Read() error =", err)
}
err = rc.Close()
if err != nil {
t.Error("Store.Fetch().Close() error =", err)
}
if !bytes.Equal(got, manifest) {
t.Errorf("Store.Fetch() = %v, want %v", got, manifest)
}
}

func TestStore_NotExistingRoot(t *testing.T) {
tempDir := t.TempDir()
root := filepath.Join(tempDir, "rootDir")
Expand Down
15 changes: 10 additions & 5 deletions content/oci/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ type Storage struct {
}

// NewStorage creates a new CAS based on file system with the OCI-Image layout.
func NewStorage(root string) *Storage {
return &Storage{
ReadOnlyStorage: NewStorageFromFS(os.DirFS(root)),
root: root,
ingestRoot: filepath.Join(root, "ingest"),
func NewStorage(root string) (*Storage, error) {
rootAbs, err := filepath.Abs(root)
if err != nil {
return nil, err
}

return &Storage{
ReadOnlyStorage: NewStorageFromFS(os.DirFS(rootAbs)),
root: rootAbs,
ingestRoot: filepath.Join(rootAbs, "ingest"),
}, nil
}

// Push pushes the content, matching the expected descriptor.
Expand Down
102 changes: 92 additions & 10 deletions content/oci/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,75 @@ func TestStorage_Success(t *testing.T) {
}

tempDir := t.TempDir()
s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

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

// test fetch
exists, err := s.Exists(ctx, desc)
if err != nil {
t.Fatal("Storage.Exists() error =", err)
}
if !exists {
t.Errorf("Storage.Exists() = %v, want %v", exists, true)
}

rc, err := s.Fetch(ctx, desc)
if err != nil {
t.Fatal("Storage.Fetch() error =", err)
}
got, err := io.ReadAll(rc)
if err != nil {
t.Fatal("Storage.Fetch().Read() error =", err)
}
err = rc.Close()
if err != nil {
t.Error("Storage.Fetch().Close() error =", err)
}
if !bytes.Equal(got, content) {
t.Errorf("Storage.Fetch() = %v, want %v", got, content)
}
}

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

tempDir := t.TempDir()
currDir, err := os.Getwd()
if err != nil {
t.Fatal("error calling Getwd(), error=", err)
}
if err := os.Chdir(tempDir); err != nil {
t.Fatal("error calling Chdir(), error=", err)
}
s, err := NewStorage(".")
if err != nil {
t.Fatal("New() error =", err)
}
if want := tempDir; s.root != want {
t.Errorf("Storage.root = %s, want %s", s.root, want)
}
// cd back to allow the temp directory to be removed
if err := os.Chdir(currDir); err != nil {
t.Fatal("error calling Chdir(), error=", err)
}
ctx := context.Background()

// test push
err = s.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Storage.Push() error =", err)
}
Expand Down Expand Up @@ -85,7 +149,10 @@ func TestStorage_NotFound(t *testing.T) {
}

tempDir := t.TempDir()
s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

exists, err := s.Exists(ctx, desc)
Expand All @@ -111,10 +178,13 @@ func TestStorage_AlreadyExists(t *testing.T) {
}

tempDir := t.TempDir()
s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

err := s.Push(ctx, desc, bytes.NewReader(content))
err = s.Push(ctx, desc, bytes.NewReader(content))
if err != nil {
t.Fatal("Storage.Push() error =", err)
}
Expand All @@ -134,10 +204,13 @@ func TestStorage_BadPush(t *testing.T) {
}

tempDir := t.TempDir()
s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

err := s.Push(ctx, desc, strings.NewReader("foobar"))
err = s.Push(ctx, desc, strings.NewReader("foobar"))
if err == nil {
t.Errorf("Storage.Push() error = %v, wantErr %v", err, true)
}
Expand All @@ -152,7 +225,10 @@ func TestStorage_Push_Concurrent(t *testing.T) {
}

tempDir := t.TempDir()
s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

concurrency := 64
Expand Down Expand Up @@ -218,7 +294,10 @@ func TestStorage_Fetch_ExistingBlobs(t *testing.T) {
t.Fatal("error calling WriteFile(), error =", err)
}

s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

exists, err := s.Exists(ctx, desc)
Expand Down Expand Up @@ -255,7 +334,10 @@ func TestStorage_Fetch_Concurrent(t *testing.T) {
}

tempDir := t.TempDir()
s := NewStorage(tempDir)
s, err := NewStorage(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

if err := s.Push(ctx, desc, bytes.NewReader(content)); err != nil {
Expand Down
7 changes: 6 additions & 1 deletion internal/fs/tarfs/tarfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io"
"io/fs"
"os"
"path/filepath"

"oras.land/oras-go/v2/errdef"
)
Expand All @@ -43,8 +44,12 @@ type entry struct {

// New returns a file system (an fs.FS) for a tar archive located at path.
func New(path string) (*TarFS, error) {
pathAbs, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("failed to resolve absolute path for %s: %w", path, err)
}
tarfs := &TarFS{
path: path,
path: pathAbs,
entries: make(map[string]*entry),
}
if err := tarfs.indexEntries(); err != nil {
Expand Down
12 changes: 11 additions & 1 deletion internal/fs/tarfs/tarfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"io"
"io/fs"
"path/filepath"
"testing"

"oras.land/oras-go/v2/errdef"
Expand All @@ -42,10 +43,19 @@ func TestTarFS_Open_Success(t *testing.T) {
"dir/hello": []byte("hello"),
"dir/subdir/world": []byte("world"),
}
tfs, err := New("testdata/test.tar")
tarPath := "testdata/test.tar"
tfs, err := New(tarPath)
if err != nil {
t.Fatalf("New() error = %v, wantErr %v", err, nil)
}
tarPathAbs, err := filepath.Abs(tarPath)
if err != nil {
t.Fatal("error calling filepath.Abs(), error =", err)
}
if tfs.path != tarPathAbs {
t.Fatalf("TarFS.path = %s, want %s", tfs.path, tarPathAbs)
}

for name, data := range testFiles {
f, err := tfs.Open(name)
if err != nil {
Expand Down

0 comments on commit 05595eb

Please sign in to comment.