undocker

extract docker archives
Log | Files | Refs | README | LICENSE

rootfs_test.go (5651B) - Raw


      1 package rootfs
      2 
      3 import (
      4 	"archive/tar"
      5 	"bytes"
      6 	"encoding/json"
      7 	"reflect"
      8 	"testing"
      9 
     10 	"git.jakstys.lt/motiejus/undocker/rootfs/internal/tartest"
     11 )
     12 
     13 type (
     14 	file        = tartest.File
     15 	dir         = tartest.Dir
     16 	hardlink    = tartest.Hardlink
     17 	extractable = tartest.Extractable
     18 	tarball     = tartest.Tarball
     19 )
     20 
     21 func TestRootFS(t *testing.T) {
     22 	layer0 := tarball{
     23 		dir{Name: "/", UID: 0},
     24 		file{Name: "/file", UID: 0, Contents: bytes.NewBufferString("from 0")},
     25 	}
     26 
     27 	layer1 := tarball{
     28 		file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
     29 	}
     30 
     31 	layer2 := tarball{
     32 		dir{Name: "/", UID: 2},
     33 	}
     34 
     35 	tests := []struct {
     36 		name    string
     37 		image   tarball
     38 		want    []extractable
     39 		wantErr string
     40 	}{
     41 		{
     42 			name:  "empty tarball",
     43 			image: tarball{manifest{}},
     44 			want:  []extractable{},
     45 		},
     46 		{
     47 			name:    "no manifest",
     48 			image:   tarball{},
     49 			wantErr: "empty or missing manifest",
     50 		},
     51 		{
     52 			name:    "missing layer",
     53 			image:   tarball{manifest{"layer0/layer.tar"}},
     54 			wantErr: "layer0/layer.tar defined in manifest, missing in tarball",
     55 		},
     56 		{
     57 			name: "basic file overwrite, layer order mixed",
     58 			image: tarball{
     59 				file{Name: "layer1/layer.tar", Contents: layer1.Buffer()},
     60 				file{Name: "layer0/layer.tar", Contents: layer0.Buffer()},
     61 				manifest{"layer0/layer.tar", "layer1/layer.tar"},
     62 			},
     63 			want: []extractable{
     64 				dir{Name: "/", UID: 0},
     65 				file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
     66 			},
     67 		},
     68 		{
     69 			name: "overwrite file with hardlink",
     70 			image: tarball{
     71 				file{Name: "layer0/layer.tar", Contents: tarball{
     72 					file{Name: "a"},
     73 				}.Buffer()},
     74 				file{Name: "layer1/layer.tar", Contents: tarball{
     75 					hardlink{Name: "a"},
     76 				}.Buffer()},
     77 				manifest{"layer0/layer.tar", "layer1/layer.tar"},
     78 			},
     79 			want: []extractable{
     80 				hardlink{Name: "a"},
     81 			},
     82 		},
     83 		{
     84 			name: "directory overwrite retains original dir",
     85 			image: tarball{
     86 				file{Name: "layer2/layer.tar", Contents: layer2.Buffer()},
     87 				file{Name: "layer0/layer.tar", Contents: layer0.Buffer()},
     88 				file{Name: "layer1/layer.tar", Contents: layer1.Buffer()},
     89 				manifest{"layer0/layer.tar", "layer1/layer.tar", "layer2/layer.tar"},
     90 			},
     91 			want: []extractable{
     92 				dir{Name: "/", UID: 0},
     93 				file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
     94 				dir{Name: "/", UID: 2},
     95 			},
     96 		},
     97 		{
     98 			name: "simple whiteout",
     99 			image: tarball{
    100 				file{Name: "layer0/layer.tar", Contents: tarball{
    101 					file{Name: "filea"},
    102 					file{Name: "fileb"},
    103 					dir{Name: "dira"},
    104 					dir{Name: "dirb"},
    105 				}.Buffer()},
    106 				file{Name: "layer1/layer.tar", Contents: tarball{
    107 					hardlink{Name: ".wh.filea"},
    108 					hardlink{Name: ".wh.dira"},
    109 				}.Buffer()},
    110 				manifest{"layer0/layer.tar", "layer1/layer.tar"},
    111 			},
    112 			want: []extractable{
    113 				file{Name: "fileb"},
    114 				dir{Name: "dirb"},
    115 			},
    116 		},
    117 		{
    118 			name: "whiteout with override",
    119 			image: tarball{
    120 				file{Name: "layer0/layer.tar", Contents: tarball{
    121 					file{Name: "file", Contents: bytes.NewBufferString("from 0")},
    122 				}.Buffer()},
    123 				file{Name: "layer1/layer.tar", Contents: tarball{
    124 					hardlink{Name: ".wh.file"},
    125 				}.Buffer()},
    126 				file{Name: "layer2/layer.tar", Contents: tarball{
    127 					file{Name: "file", Contents: bytes.NewBufferString("from 3")},
    128 				}.Buffer()},
    129 				manifest{
    130 					"layer0/layer.tar",
    131 					"layer1/layer.tar",
    132 					"layer2/layer.tar",
    133 				},
    134 			},
    135 			want: []extractable{
    136 				file{Name: "file", Contents: bytes.NewBufferString("from 3")},
    137 			},
    138 		},
    139 		{
    140 			name: "directories do not whiteout",
    141 			image: tarball{
    142 				file{Name: "layer0/layer.tar", Contents: tarball{
    143 					dir{Name: "dir"},
    144 				}.Buffer()},
    145 				file{Name: "layer1/layer.tar", Contents: tarball{
    146 					dir{Name: ".wh.dir"},
    147 				}.Buffer()},
    148 				manifest{"layer0/layer.tar", "layer1/layer.tar"},
    149 			},
    150 			want: []extractable{
    151 				dir{Name: "dir"},
    152 				dir{Name: ".wh.dir"},
    153 			},
    154 		},
    155 		{
    156 			name: "simple readdir whiteout",
    157 			image: tarball{
    158 				file{Name: "layer0/layer.tar", Contents: tarball{
    159 					dir{Name: "a"},
    160 					file{Name: "a/filea"},
    161 				}.Buffer()},
    162 				file{Name: "layer1/layer.tar", Contents: tarball{
    163 					dir{Name: "a"},
    164 					file{Name: "a/fileb"},
    165 					hardlink{Name: "a/.wh..wh..opq"},
    166 				}.Buffer()},
    167 				manifest{"layer0/layer.tar", "layer1/layer.tar"},
    168 			},
    169 			want: []extractable{
    170 				dir{Name: "a"},
    171 				file{Name: "a/fileb"},
    172 			},
    173 		},
    174 		{
    175 			name: "archived layer",
    176 			image: tarball{
    177 				file{Name: "layer1/layer.tar", Contents: layer1.Gzip()},
    178 				file{Name: "layer0/layer.tar", Contents: layer0.Gzip()},
    179 				manifest{"layer0/layer.tar", "layer1/layer.tar"},
    180 			},
    181 			want: []extractable{
    182 				dir{Name: "/", UID: 0},
    183 				file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")},
    184 			},
    185 		},
    186 	}
    187 
    188 	for _, tt := range tests {
    189 		t.Run(tt.name, func(t *testing.T) {
    190 			in := bytes.NewReader(tt.image.Buffer().Bytes())
    191 			out := bytes.Buffer{}
    192 
    193 			err := Flatten(in, &out)
    194 			if tt.wantErr != "" {
    195 				if err == nil {
    196 					t.Fatal("expected error, got nil")
    197 				}
    198 				if tt.wantErr != err.Error() {
    199 					t.Errorf("want != got: %s != %s", tt.wantErr, err.Error())
    200 				}
    201 				return
    202 			}
    203 			outb := out.Bytes()
    204 			if err != nil {
    205 				t.Fatal("expected error, got nil")
    206 			}
    207 			got := tartest.Extract(t, bytes.NewReader(outb))
    208 			if !reflect.DeepEqual(tt.want, got) {
    209 				t.Errorf("want != got: %v != %v", tt.want, got)
    210 			}
    211 		})
    212 	}
    213 }
    214 
    215 // Helpers
    216 type manifest []string
    217 
    218 func (m manifest) Tar(tw *tar.Writer) error {
    219 	b, err := json.Marshal(dockerManifestJSON{{Layers: m}})
    220 	if err != nil {
    221 		return err
    222 	}
    223 	return file{
    224 		Name:     "manifest.json",
    225 		Contents: bytes.NewBuffer(b),
    226 	}.Tar(tw)
    227 }