2021-05-24 00:11:57 +03:00
|
|
|
package rootfs
|
|
|
|
|
|
|
|
import (
|
2021-05-24 00:11:58 +03:00
|
|
|
"archive/tar"
|
2021-05-24 00:11:57 +03:00
|
|
|
"bytes"
|
2021-05-24 00:11:58 +03:00
|
|
|
"encoding/json"
|
2021-05-24 00:11:58 +03:00
|
|
|
"reflect"
|
2021-05-24 00:11:57 +03:00
|
|
|
"testing"
|
2021-05-24 00:11:57 +03:00
|
|
|
|
2021-05-31 21:45:15 +03:00
|
|
|
"git.sr.ht/~motiejus/undocker/rootfs/internal/tartest"
|
2021-05-24 00:11:57 +03:00
|
|
|
)
|
|
|
|
|
2021-05-24 00:11:58 +03:00
|
|
|
type (
|
2021-05-24 00:11:58 +03:00
|
|
|
file = tartest.File
|
|
|
|
dir = tartest.Dir
|
|
|
|
hardlink = tartest.Hardlink
|
|
|
|
extractable = tartest.Extractable
|
|
|
|
tarball = tartest.Tarball
|
2021-05-24 00:11:58 +03:00
|
|
|
)
|
|
|
|
|
2021-05-24 00:11:58 +03:00
|
|
|
func TestRootFS(t *testing.T) {
|
|
|
|
layer0 := tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
dir{Name: "/", UID: 0},
|
|
|
|
file{Name: "/file", UID: 0, Contents: bytes.NewBufferString("from 0")},
|
2021-05-24 00:11:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
layer1 := tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
|
2021-05-24 00:11:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
layer2 := tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
dir{Name: "/", UID: 2},
|
2021-05-24 00:11:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
image tarball
|
|
|
|
want []extractable
|
|
|
|
wantErr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "empty tarball",
|
|
|
|
image: tarball{manifest{}},
|
|
|
|
want: []extractable{},
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
{
|
|
|
|
name: "no manifest",
|
|
|
|
image: tarball{},
|
|
|
|
wantErr: "empty or missing manifest",
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
{
|
|
|
|
name: "missing layer",
|
|
|
|
image: tarball{manifest{"layer0/layer.tar"}},
|
2021-05-24 00:11:58 +03:00
|
|
|
wantErr: "layer0/layer.tar defined in manifest, missing in tarball",
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic file overwrite, layer order mixed",
|
|
|
|
image: tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer1/layer.tar", Contents: layer1.Buffer()},
|
|
|
|
file{Name: "layer0/layer.tar", Contents: layer0.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{"layer0/layer.tar", "layer1/layer.tar"},
|
|
|
|
},
|
|
|
|
want: []extractable{
|
2021-05-24 00:11:58 +03:00
|
|
|
dir{Name: "/", UID: 0},
|
|
|
|
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
{
|
|
|
|
name: "overwrite file with hardlink",
|
|
|
|
image: tarball{
|
|
|
|
file{Name: "layer0/layer.tar", Contents: tarball{
|
|
|
|
file{Name: "a"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer1/layer.tar", Contents: tarball{
|
|
|
|
hardlink{Name: "a"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{"layer0/layer.tar", "layer1/layer.tar"},
|
|
|
|
},
|
|
|
|
want: []extractable{
|
|
|
|
hardlink{Name: "a"},
|
|
|
|
},
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
{
|
|
|
|
name: "directory overwrite retains original dir",
|
|
|
|
image: tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer2/layer.tar", Contents: layer2.Buffer()},
|
|
|
|
file{Name: "layer0/layer.tar", Contents: layer0.Buffer()},
|
|
|
|
file{Name: "layer1/layer.tar", Contents: layer1.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{"layer0/layer.tar", "layer1/layer.tar", "layer2/layer.tar"},
|
|
|
|
},
|
|
|
|
want: []extractable{
|
2021-05-24 00:11:58 +03:00
|
|
|
dir{Name: "/", UID: 0},
|
|
|
|
file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
|
|
|
|
dir{Name: "/", UID: 2},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
{
|
|
|
|
name: "simple whiteout",
|
|
|
|
image: tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer0/layer.tar", Contents: tarball{
|
|
|
|
file{Name: "filea"},
|
|
|
|
file{Name: "fileb"},
|
|
|
|
dir{Name: "dira"},
|
|
|
|
dir{Name: "dirb"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer1/layer.tar", Contents: tarball{
|
|
|
|
hardlink{Name: ".wh.filea"},
|
|
|
|
hardlink{Name: ".wh.dira"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{"layer0/layer.tar", "layer1/layer.tar"},
|
|
|
|
},
|
|
|
|
want: []extractable{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "fileb"},
|
|
|
|
dir{Name: "dirb"},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "whiteout with override",
|
|
|
|
image: tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer0/layer.tar", Contents: tarball{
|
|
|
|
file{Name: "file", Contents: bytes.NewBufferString("from 0")},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer1/layer.tar", Contents: tarball{
|
|
|
|
hardlink{Name: ".wh.file"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer2/layer.tar", Contents: tarball{
|
|
|
|
file{Name: "file", Contents: bytes.NewBufferString("from 3")},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{
|
|
|
|
"layer0/layer.tar",
|
|
|
|
"layer1/layer.tar",
|
|
|
|
"layer2/layer.tar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []extractable{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "file", Contents: bytes.NewBufferString("from 3")},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2021-05-24 00:11:58 +03:00
|
|
|
name: "directories do not whiteout",
|
2021-05-24 00:11:58 +03:00
|
|
|
image: tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer0/layer.tar", Contents: tarball{
|
|
|
|
dir{Name: "dir"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer1/layer.tar", Contents: tarball{
|
|
|
|
dir{Name: ".wh.dir"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{"layer0/layer.tar", "layer1/layer.tar"},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
want: []extractable{
|
2021-05-24 00:11:58 +03:00
|
|
|
dir{Name: "dir"},
|
|
|
|
dir{Name: ".wh.dir"},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "simple readdir whiteout",
|
|
|
|
image: tarball{
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer0/layer.tar", Contents: tarball{
|
|
|
|
dir{Name: "a"},
|
|
|
|
file{Name: "a/filea"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
file{Name: "layer1/layer.tar", Contents: tarball{
|
|
|
|
dir{Name: "a"},
|
|
|
|
file{Name: "a/fileb"},
|
|
|
|
hardlink{Name: "a/.wh..wh..opq"},
|
2021-05-24 00:11:58 +03:00
|
|
|
}.Buffer()},
|
2021-05-24 00:11:58 +03:00
|
|
|
manifest{"layer0/layer.tar", "layer1/layer.tar"},
|
|
|
|
},
|
|
|
|
want: []extractable{
|
2021-05-24 00:11:58 +03:00
|
|
|
dir{Name: "a"},
|
|
|
|
file{Name: "a/fileb"},
|
2021-05-24 00:11:58 +03:00
|
|
|
},
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
{
|
|
|
|
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")},
|
|
|
|
},
|
|
|
|
},
|
2021-05-24 00:11:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2021-05-24 00:11:58 +03:00
|
|
|
in := bytes.NewReader(tt.image.Buffer().Bytes())
|
2021-05-24 00:11:58 +03:00
|
|
|
out := bytes.Buffer{}
|
|
|
|
|
2021-05-24 00:11:58 +03:00
|
|
|
err := Flatten(in, &out)
|
2021-05-24 00:11:58 +03:00
|
|
|
if tt.wantErr != "" {
|
2021-05-24 00:11:58 +03:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error, got nil")
|
|
|
|
}
|
|
|
|
if tt.wantErr != err.Error() {
|
|
|
|
t.Errorf("want != got: %s != %s", tt.wantErr, err.Error())
|
|
|
|
}
|
2021-05-24 00:11:58 +03:00
|
|
|
return
|
|
|
|
}
|
2021-05-24 00:11:58 +03:00
|
|
|
outb := out.Bytes()
|
2021-05-24 00:11:58 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal("expected error, got nil")
|
|
|
|
}
|
2021-05-24 00:11:58 +03:00
|
|
|
got := tartest.Extract(t, bytes.NewReader(outb))
|
2021-05-24 00:11:58 +03:00
|
|
|
if !reflect.DeepEqual(tt.want, got) {
|
|
|
|
t.Errorf("want != got: %v != %v", tt.want, got)
|
|
|
|
}
|
2021-05-24 00:11:58 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-05-24 00:11:58 +03:00
|
|
|
|
|
|
|
// Helpers
|
2021-05-24 00:11:58 +03:00
|
|
|
type manifest []string
|
2021-05-24 00:11:58 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|