1 package schema1
2
3 import (
4 "bytes"
5 "compress/gzip"
6 "context"
7 "io"
8 "reflect"
9 "testing"
10
11 "github.com/distribution/reference"
12 "github.com/docker/distribution"
13 dcontext "github.com/docker/distribution/context"
14 "github.com/docker/libtrust"
15 "github.com/opencontainers/go-digest"
16 )
17
18 type mockBlobService struct {
19 descriptors map[digest.Digest]distribution.Descriptor
20 }
21
22 func (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
29 func (bs *mockBlobService) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
30 panic("not implemented")
31 }
32
33 func (bs *mockBlobService) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
34 panic("not implemented")
35 }
36
37 func (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
47 func (bs *mockBlobService) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
48 panic("not implemented")
49 }
50
51 func (bs *mockBlobService) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
52 panic("not implemented")
53 }
54
55 func TestEmptyTar(t *testing.T) {
56
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
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
87 func 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
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
View as plain text