1package schema1 2 3import ( 4 "bytes" 5 "compress/gzip" 6 "context" 7 "io" 8 "reflect" 9 "testing" 10 11 "github.com/docker/distribution" 12 dcontext "github.com/docker/distribution/context" 13 "github.com/docker/distribution/reference" 14 "github.com/docker/libtrust" 15 "github.com/opencontainers/go-digest" 16) 17 18type mockBlobService struct { 19 descriptors map[digest.Digest]distribution.Descriptor 20} 21 22func (bs *mockBlobService) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 23 if descriptor, ok := bs.descriptors[dgst]; ok { 24 return descriptor, nil 25 } 26 return distribution.Descriptor{}, distribution.ErrBlobUnknown 27} 28 29func (bs *mockBlobService) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 30 panic("not implemented") 31} 32 33func (bs *mockBlobService) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 34 panic("not implemented") 35} 36 37func (bs *mockBlobService) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 38 d := distribution.Descriptor{ 39 Digest: digest.FromBytes(p), 40 Size: int64(len(p)), 41 MediaType: mediaType, 42 } 43 bs.descriptors[d.Digest] = d 44 return d, nil 45} 46 47func (bs *mockBlobService) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 48 panic("not implemented") 49} 50 51func (bs *mockBlobService) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 52 panic("not implemented") 53} 54 55func TestEmptyTar(t *testing.T) { 56 // Confirm that gzippedEmptyTar expands to 1024 NULL bytes. 57 var decompressed [2048]byte 58 gzipReader, err := gzip.NewReader(bytes.NewReader(gzippedEmptyTar)) 59 if err != nil { 60 t.Fatalf("NewReader returned error: %v", err) 61 } 62 n, _ := gzipReader.Read(decompressed[:]) 63 if n != 1024 { 64 t.Fatalf("read returned %d bytes; expected 1024", n) 65 } 66 n, err = gzipReader.Read(decompressed[1024:]) 67 if n != 0 { 68 t.Fatalf("read returned %d bytes; expected 0", n) 69 } 70 if err != io.EOF { 71 t.Fatal("read did not return io.EOF") 72 } 73 gzipReader.Close() 74 for _, b := range decompressed[:1024] { 75 if b != 0 { 76 t.Fatal("nonzero byte in decompressed tar") 77 } 78 } 79 80 // Confirm that digestSHA256EmptyTar is the digest of gzippedEmptyTar. 81 dgst := digest.FromBytes(gzippedEmptyTar) 82 if dgst != digestSHA256GzippedEmptyTar { 83 t.Fatalf("digest mismatch for empty tar: expected %s got %s", digestSHA256GzippedEmptyTar, dgst) 84 } 85} 86 87func TestConfigBuilder(t *testing.T) { 88 imgJSON := `{ 89 "architecture": "amd64", 90 "config": { 91 "AttachStderr": false, 92 "AttachStdin": false, 93 "AttachStdout": false, 94 "Cmd": [ 95 "/bin/sh", 96 "-c", 97 "echo hi" 98 ], 99 "Domainname": "", 100 "Entrypoint": null, 101 "Env": [ 102 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 103 "derived=true", 104 "asdf=true" 105 ], 106 "Hostname": "23304fc829f9", 107 "Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246", 108 "Labels": {}, 109 "OnBuild": [], 110 "OpenStdin": false, 111 "StdinOnce": false, 112 "Tty": false, 113 "User": "", 114 "Volumes": null, 115 "WorkingDir": "" 116 }, 117 "container": "e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001", 118 "container_config": { 119 "AttachStderr": false, 120 "AttachStdin": false, 121 "AttachStdout": false, 122 "Cmd": [ 123 "/bin/sh", 124 "-c", 125 "#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]" 126 ], 127 "Domainname": "", 128 "Entrypoint": null, 129 "Env": [ 130 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 131 "derived=true", 132 "asdf=true" 133 ], 134 "Hostname": "23304fc829f9", 135 "Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246", 136 "Labels": {}, 137 "OnBuild": [], 138 "OpenStdin": false, 139 "StdinOnce": false, 140 "Tty": false, 141 "User": "", 142 "Volumes": null, 143 "WorkingDir": "" 144 }, 145 "created": "2015-11-04T23:06:32.365666163Z", 146 "docker_version": "1.9.0-dev", 147 "history": [ 148 { 149 "created": "2015-10-31T22:22:54.690851953Z", 150 "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /" 151 }, 152 { 153 "created": "2015-10-31T22:22:55.613815829Z", 154 "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]" 155 }, 156 { 157 "created": "2015-11-04T23:06:30.934316144Z", 158 "created_by": "/bin/sh -c #(nop) ENV derived=true", 159 "empty_layer": true 160 }, 161 { 162 "created": "2015-11-04T23:06:31.192097572Z", 163 "created_by": "/bin/sh -c #(nop) ENV asdf=true", 164 "empty_layer": true 165 }, 166 { 167 "author": "Alyssa P. Hacker \u003calyspdev@example.com\u003e", 168 "created": "2015-11-04T23:06:32.083868454Z", 169 "created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024" 170 }, 171 { 172 "created": "2015-11-04T23:06:32.365666163Z", 173 "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]", 174 "empty_layer": true 175 } 176 ], 177 "os": "linux", 178 "rootfs": { 179 "diff_ids": [ 180 "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1", 181 "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", 182 "sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49" 183 ], 184 "type": "layers" 185 } 186}` 187 188 descriptors := []distribution.Descriptor{ 189 {Digest: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 190 {Digest: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")}, 191 {Digest: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 192 } 193 194 pk, err := libtrust.GenerateECP256PrivateKey() 195 if err != nil { 196 t.Fatalf("could not generate key for testing: %v", err) 197 } 198 199 bs := &mockBlobService{descriptors: make(map[digest.Digest]distribution.Descriptor)} 200 201 ref, err := reference.WithName("testrepo") 202 if err != nil { 203 t.Fatalf("could not parse reference: %v", err) 204 } 205 ref, err = reference.WithTag(ref, "testtag") 206 if err != nil { 207 t.Fatalf("could not add tag: %v", err) 208 } 209 210 builder := NewConfigManifestBuilder(bs, pk, ref, []byte(imgJSON)) 211 212 for _, d := range descriptors { 213 if err := builder.AppendReference(d); err != nil { 214 t.Fatalf("AppendReference returned error: %v", err) 215 } 216 } 217 218 signed, err := builder.Build(dcontext.Background()) 219 if err != nil { 220 t.Fatalf("Build returned error: %v", err) 221 } 222 223 // Check that the gzipped empty layer tar was put in the blob store 224 _, err = bs.Stat(dcontext.Background(), digestSHA256GzippedEmptyTar) 225 if err != nil { 226 t.Fatal("gzipped empty tar was not put in the blob store") 227 } 228 229 manifest := signed.(*SignedManifest).Manifest 230 231 if manifest.Versioned.SchemaVersion != 1 { 232 t.Fatal("SchemaVersion != 1") 233 } 234 if manifest.Name != "testrepo" { 235 t.Fatal("incorrect name in manifest") 236 } 237 if manifest.Tag != "testtag" { 238 t.Fatal("incorrect tag in manifest") 239 } 240 if manifest.Architecture != "amd64" { 241 t.Fatal("incorrect arch in manifest") 242 } 243 244 expectedFSLayers := []FSLayer{ 245 {BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 246 {BlobSum: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 247 {BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 248 {BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 249 {BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")}, 250 {BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")}, 251 } 252 253 if len(manifest.FSLayers) != len(expectedFSLayers) { 254 t.Fatalf("wrong number of FSLayers: %d", len(manifest.FSLayers)) 255 } 256 if !reflect.DeepEqual(manifest.FSLayers, expectedFSLayers) { 257 t.Fatal("wrong FSLayers list") 258 } 259 260 expectedV1Compatibility := []string{ 261 `{"architecture":"amd64","config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","echo hi"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"container":"e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001","container_config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"created":"2015-11-04T23:06:32.365666163Z","docker_version":"1.9.0-dev","id":"69e5c1bfadad697fdb6db59f6326648fa119e0c031a0eda33b8cfadcab54ba7f","os":"linux","parent":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","throwaway":true}`, 262 `{"id":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","parent":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","created":"2015-11-04T23:06:32.083868454Z","container_config":{"Cmd":["/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"]},"author":"Alyssa P. Hacker \u003calyspdev@example.com\u003e"}`, 263 `{"id":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","parent":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","created":"2015-11-04T23:06:31.192097572Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV asdf=true"]},"throwaway":true}`, 264 `{"id":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","parent":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","created":"2015-11-04T23:06:30.934316144Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV derived=true"]},"throwaway":true}`, 265 `{"id":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","parent":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:55.613815829Z","container_config":{"Cmd":["/bin/sh -c #(nop) CMD [\"sh\"]"]}}`, 266 `{"id":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:54.690851953Z","container_config":{"Cmd":["/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"]}}`, 267 } 268 269 if len(manifest.History) != len(expectedV1Compatibility) { 270 t.Fatalf("wrong number of history entries: %d", len(manifest.History)) 271 } 272 for i := range expectedV1Compatibility { 273 if manifest.History[i].V1Compatibility != expectedV1Compatibility[i] { 274 t.Errorf("wrong V1Compatibility %d. expected:\n%s\ngot:\n%s", i, expectedV1Compatibility[i], manifest.History[i].V1Compatibility) 275 } 276 } 277} 278