undocker/rootfs/rootfs_test.go

228 lines
5.5 KiB
Go

package rootfs
import (
"archive/tar"
"bytes"
"encoding/json"
"reflect"
"testing"
"git.sr.ht/~motiejus/undocker/rootfs/internal/tartest"
)
type (
file = tartest.File
dir = tartest.Dir
hardlink = tartest.Hardlink
extractable = tartest.Extractable
tarball = tartest.Tarball
)
func TestRootFS(t *testing.T) {
layer0 := tarball{
dir{Name: "/", UID: 0},
file{Name: "/file", UID: 0, Contents: bytes.NewBufferString("from 0")},
}
layer1 := tarball{
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
}
layer2 := tarball{
dir{Name: "/", UID: 2},
}
tests := []struct {
name string
image tarball
want []extractable
wantErr string
}{
{
name: "empty tarball",
image: tarball{manifest{}},
want: []extractable{},
},
{
name: "no manifest",
image: tarball{},
wantErr: "empty or missing manifest",
},
{
name: "missing layer",
image: tarball{manifest{"layer0/layer.tar"}},
wantErr: "layer0/layer.tar defined in manifest, missing in tarball",
},
{
name: "basic file overwrite, layer order mixed",
image: tarball{
file{Name: "layer1/layer.tar", Contents: layer1.Buffer()},
file{Name: "layer0/layer.tar", Contents: layer0.Buffer()},
manifest{"layer0/layer.tar", "layer1/layer.tar"},
},
want: []extractable{
dir{Name: "/", UID: 0},
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
},
},
{
name: "overwrite file with hardlink",
image: tarball{
file{Name: "layer0/layer.tar", Contents: tarball{
file{Name: "a"},
}.Buffer()},
file{Name: "layer1/layer.tar", Contents: tarball{
hardlink{Name: "a"},
}.Buffer()},
manifest{"layer0/layer.tar", "layer1/layer.tar"},
},
want: []extractable{
hardlink{Name: "a"},
},
},
{
name: "directory overwrite retains original dir",
image: tarball{
file{Name: "layer2/layer.tar", Contents: layer2.Buffer()},
file{Name: "layer0/layer.tar", Contents: layer0.Buffer()},
file{Name: "layer1/layer.tar", Contents: layer1.Buffer()},
manifest{"layer0/layer.tar", "layer1/layer.tar", "layer2/layer.tar"},
},
want: []extractable{
dir{Name: "/", UID: 0},
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
dir{Name: "/", UID: 2},
},
},
{
name: "simple whiteout",
image: tarball{
file{Name: "layer0/layer.tar", Contents: tarball{
file{Name: "filea"},
file{Name: "fileb"},
dir{Name: "dira"},
dir{Name: "dirb"},
}.Buffer()},
file{Name: "layer1/layer.tar", Contents: tarball{
hardlink{Name: ".wh.filea"},
hardlink{Name: ".wh.dira"},
}.Buffer()},
manifest{"layer0/layer.tar", "layer1/layer.tar"},
},
want: []extractable{
file{Name: "fileb"},
dir{Name: "dirb"},
},
},
{
name: "whiteout with override",
image: tarball{
file{Name: "layer0/layer.tar", Contents: tarball{
file{Name: "file", Contents: bytes.NewBufferString("from 0")},
}.Buffer()},
file{Name: "layer1/layer.tar", Contents: tarball{
hardlink{Name: ".wh.file"},
}.Buffer()},
file{Name: "layer2/layer.tar", Contents: tarball{
file{Name: "file", Contents: bytes.NewBufferString("from 3")},
}.Buffer()},
manifest{
"layer0/layer.tar",
"layer1/layer.tar",
"layer2/layer.tar",
},
},
want: []extractable{
file{Name: "file", Contents: bytes.NewBufferString("from 3")},
},
},
{
name: "directories do not whiteout",
image: tarball{
file{Name: "layer0/layer.tar", Contents: tarball{
dir{Name: "dir"},
}.Buffer()},
file{Name: "layer1/layer.tar", Contents: tarball{
dir{Name: ".wh.dir"},
}.Buffer()},
manifest{"layer0/layer.tar", "layer1/layer.tar"},
},
want: []extractable{
dir{Name: "dir"},
dir{Name: ".wh.dir"},
},
},
{
name: "simple readdir whiteout",
image: tarball{
file{Name: "layer0/layer.tar", Contents: tarball{
dir{Name: "a"},
file{Name: "a/filea"},
}.Buffer()},
file{Name: "layer1/layer.tar", Contents: tarball{
dir{Name: "a"},
file{Name: "a/fileb"},
hardlink{Name: "a/.wh..wh..opq"},
}.Buffer()},
manifest{"layer0/layer.tar", "layer1/layer.tar"},
},
want: []extractable{
dir{Name: "a"},
file{Name: "a/fileb"},
},
},
{
name: "archived layer",
image: tarball{
file{Name: "layer1/layer.tar", Contents: layer1.Gzip()},
file{Name: "layer0/layer.tar", Contents: layer0.Gzip()},
manifest{"layer0/layer.tar", "layer1/layer.tar"},
},
want: []extractable{
dir{Name: "/", UID: 0},
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
in := bytes.NewReader(tt.image.Buffer().Bytes())
out := bytes.Buffer{}
err := Flatten(in, &out)
if tt.wantErr != "" {
if err == nil {
t.Fatal("expected error, got nil")
}
if tt.wantErr != err.Error() {
t.Errorf("want != got: %s != %s", tt.wantErr, err.Error())
}
return
}
outb := out.Bytes()
if err != nil {
t.Fatal("expected error, got nil")
}
got := tartest.Extract(t, bytes.NewReader(outb))
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("want != got: %v != %v", tt.want, got)
}
})
}
}
// Helpers
type manifest []string
func (m manifest) Tar(tw *tar.Writer) error {
b, err := json.Marshal(dockerManifestJSON{{Layers: m}})
if err != nil {
return err
}
return file{
Name: "manifest.json",
Contents: bytes.NewBuffer(b),
}.Tar(tw)
}