Skip to content

Commit

Permalink
fix: handle dot-prefixed path for tarfs (#875)
Browse files Browse the repository at this point in the history
1. Handle dot-prefixed paths such as `./index.json` when indexing and
reading entries in tarfs
2. Add corresponding tests
3. Refactor original tests

Fixes: #851
Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
  • Loading branch information
Wwwsylvia authored Jan 20, 2025
1 parent a15da41 commit dff5628
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 289 deletions.
186 changes: 106 additions & 80 deletions content/oci/readonlyoci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,91 +416,117 @@ func TestReadOnlyStore_DirFS(t *testing.T) {
}

/*
testdata/hello-world.tar contains:
blobs/
sha256/
2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 // image layer
f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 // image manifest
faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af // manifest list
feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 // config
index.json
manifest.json
oci-layout
=== Contents of testdata/hello-world.tar ===
blobs/
blobs/sha256/
blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 // image layer
blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 // image manifest
blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af // manifest list
blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 // config
index.json
manifest.json
oci-layout
=== Contents of testdata/hello-world-prefixed-path.tar ===
./
./blobs/
./blobs/sha256/
./blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54 // image layer
./blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 // image manifest
./blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af // manifest list
./blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 // config
./index.json
./manifest.json
./oci-layout
*/

func TestReadOnlyStore_TarFS(t *testing.T) {
ctx := context.Background()
s, err := NewFromTar(ctx, "testdata/hello-world.tar")
if err != nil {
t.Fatal("New() error =", err)
}
tarPaths := []string{
"testdata/hello-world.tar",
"testdata/hello-world-prefixed-path.tar",
}
for _, tarPath := range tarPaths {
t.Run(tarPath, func(t *testing.T) {
ctx := context.Background()
s, err := NewFromTar(ctx, tarPath)
if err != nil {
t.Fatal("New() error =", err)
}

// test data in testdata/hello-world.tar
descs := []ocispec.Descriptor{
// desc 0: config
{
MediaType: "application/vnd.docker.container.image.v1+json",
Size: 1469,
Digest: "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412",
},
// desc 1: layer
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Size: 2479,
Digest: "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54",
},
// desc 2: image manifest
{
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
Digest: "sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4",
Size: 525,
Platform: &ocispec.Platform{
Architecture: "amd64",
OS: "linux",
},
},
// desc 3: manifest list
{
MediaType: docker.MediaTypeManifestList,
Size: 2561,
Digest: "sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af",
},
}
// test data in testdata/hello-world.tar
descs := []ocispec.Descriptor{
// desc 0: config
{
MediaType: "application/vnd.docker.container.image.v1+json",
Size: 1469,
Digest: "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412",
},
// desc 1: layer
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Size: 2479,
Digest: "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54",
},
// desc 2: image manifest
{
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
Digest: "sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4",
Size: 525,
Platform: &ocispec.Platform{
Architecture: "amd64",
OS: "linux",
},
},
// desc 3: manifest list
{
MediaType: docker.MediaTypeManifestList,
Size: 2561,
Digest: "sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af",
},
}

// test resolving by tag
for _, desc := range descs {
gotDesc, err := s.Resolve(ctx, desc.Digest.String())
if err != nil {
t.Fatal("ReadOnlyStore: Resolve() error =", err)
}
if want := desc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest {
t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want)
}
}
// test resolving by tag
gotDesc, err := s.Resolve(ctx, "latest")
if err != nil {
t.Fatal("ReadOnlyStore: Resolve() error =", err)
}
if want := descs[3]; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest {
t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want)
}
// test resolving by tag
for _, desc := range descs {
gotDesc, err := s.Resolve(ctx, desc.Digest.String())
if err != nil {
t.Fatal("ReadOnlyStore: Resolve() error =", err)
}
if want := desc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest {
t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want)
}
}
// test resolving by tag
gotDesc, err := s.Resolve(ctx, "latest")
if err != nil {
t.Fatal("ReadOnlyStore: Resolve() error =", err)
}
if want := descs[3]; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest {
t.Errorf("ReadOnlyStore.Resolve() = %v, want %v", gotDesc, want)
}

// test Predecessors
wantPredecessors := [][]ocispec.Descriptor{
{descs[2]}, // desc 0
{descs[2]}, // desc 1
{descs[3]}, // desc 2
{}, // desc 3
}
for i, want := range wantPredecessors {
predecessors, err := s.Predecessors(ctx, descs[i])
if err != nil {
t.Errorf("ReadOnlyStore.Predecessors(%d) error = %v", i, err)
}
if !equalDescriptorSet(predecessors, want) {
t.Errorf("ReadOnlyStore.Predecessors(%d) = %v, want %v", i, predecessors, want)
}
// test Predecessors
wantPredecessors := [][]ocispec.Descriptor{
{descs[2]}, // desc 0
{descs[2]}, // desc 1
{descs[3]}, // desc 2
{}, // desc 3
}
for i, want := range wantPredecessors {
predecessors, err := s.Predecessors(ctx, descs[i])
if err != nil {
t.Errorf("ReadOnlyStore.Predecessors(%d) error = %v", i, err)
}
if !equalDescriptorSet(predecessors, want) {
t.Errorf("ReadOnlyStore.Predecessors(%d) = %v, want %v", i, predecessors, want)
}
}
})
}
}

Expand Down
136 changes: 86 additions & 50 deletions content/oci/readonlystorage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,57 +161,93 @@ func TestReadOnlyStorage_DirFS(t *testing.T) {
}
}

func TestReadOnlyStorage_TarFS(t *testing.T) {
s, err := NewStorageFromTar("testdata/hello-world.tar")
if err != nil {
t.Fatal("NewStorageFromTar() error =", err)
}
ctx := context.Background()

// test data in testdata/hello-world.tar
blob := []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72","container_config":{"Hostname":"8746661ca3c2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2021-09-23T23:47:57.442225064Z","docker_version":"20.10.7","history":[{"created":"2021-09-23T23:47:57.098990892Z","created_by":"/bin/sh -c #(nop) COPY file:50563a97010fd7ce1ceebd1fa4f4891ac3decdf428333fb2683696f4358af6c2 in / "},{"created":"2021-09-23T23:47:57.442225064Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"]}}`)
desc := content.NewDescriptorFromBytes(docker.MediaTypeManifest, blob)

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

// test Fetch
rc, err := s.Fetch(ctx, desc)
if err != nil {
t.Fatal("ReadOnlyStorage.Fetch() error =", err)
}
got, err := io.ReadAll(rc)
if err != nil {
t.Fatal("ReadOnlyStorage.Fetch().Read() error =", err)
}
err = rc.Close()
if err != nil {
t.Error("ReadOnlyStorage.Fetch().Close() error =", err)
}
if !bytes.Equal(got, blob) {
t.Errorf("ReadOnlyStorage.Fetch() = %v, want %v", got, blob)
}
/*
=== Contents of testdata/hello-world.tar ===
blobs/
blobs/sha256/
blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54
blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4
blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
index.json
manifest.json
oci-layout
=== Contents of testdata/hello-world-prefixed-path.tar ===
./
./blobs/
./blobs/sha256/
./blobs/sha256/2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54
./blobs/sha256/f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4
./blobs/sha256/faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
./blobs/sha256/feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
./index.json
./manifest.json
./oci-layout
// test Exists against a non-existing content
blob = []byte("whatever")
desc = content.NewDescriptorFromBytes("", blob)
exists, err = s.Exists(ctx, desc)
if err != nil {
t.Fatal("ReadOnlyStorage.Exists() error =", err)
}
if want := false; exists != want {
t.Errorf("ReadOnlyStorage.Exists() = %v, want %v", exists, want)
}
*/

// test Fetch against a non-existing content
_, err = s.Fetch(ctx, desc)
if want := errdef.ErrNotFound; !errors.Is(err, want) {
t.Errorf("ReadOnlyStorage.Fetch() error = %v, wantErr %v", err, want)
func TestReadOnlyStorage_TarFS(t *testing.T) {
tarPaths := []string{
"testdata/hello-world.tar",
"testdata/hello-world-prefixed-path.tar",
}
for _, tarPath := range tarPaths {
t.Run(tarPath, func(t *testing.T) {
s, err := NewStorageFromTar(tarPath)
if err != nil {
t.Fatal("NewStorageFromTar() error =", err)
}
ctx := context.Background()

// test data in testdata/hello-world.tar and testdata/hello-world-prefixed-path.tar
blob := []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72","container_config":{"Hostname":"8746661ca3c2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2021-09-23T23:47:57.442225064Z","docker_version":"20.10.7","history":[{"created":"2021-09-23T23:47:57.098990892Z","created_by":"/bin/sh -c #(nop) COPY file:50563a97010fd7ce1ceebd1fa4f4891ac3decdf428333fb2683696f4358af6c2 in / "},{"created":"2021-09-23T23:47:57.442225064Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"]}}`)
desc := content.NewDescriptorFromBytes(docker.MediaTypeManifest, blob)

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

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

// test Exists against a non-existing content
blob = []byte("whatever")
desc = content.NewDescriptorFromBytes("", blob)
exists, err = s.Exists(ctx, desc)
if err != nil {
t.Fatal("ReadOnlyStorage.Exists() error =", err)
}
if want := false; exists != want {
t.Errorf("ReadOnlyStorage.Exists() = %v, want %v", exists, want)
}

// test Fetch against a non-existing content
_, err = s.Fetch(ctx, desc)
if want := errdef.ErrNotFound; !errors.Is(err, want) {
t.Errorf("ReadOnlyStorage.Fetch() error = %v, wantErr %v", err, want)
}
})
}
}
Binary file not shown.
6 changes: 4 additions & 2 deletions 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"
"path/filepath"

"oras.land/oras-go/v2/errdef"
Expand Down Expand Up @@ -143,12 +144,13 @@ func (tfs *TarFS) indexEntries() error {
if err != nil {
return err
}
tfs.entries[header.Name] = &entry{

name := path.Clean(header.Name)
tfs.entries[name] = &entry{
header: header,
pos: pos - blockSize,
}
}

return nil
}

Expand Down
Loading

0 comments on commit dff5628

Please sign in to comment.