1 package handlers
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "net/http"
12 "net/http/httptest"
13 "net/http/httputil"
14 "net/url"
15 "os"
16 "path"
17 "reflect"
18 "regexp"
19 "strconv"
20 "strings"
21 "testing"
22
23 "github.com/distribution/reference"
24 "github.com/docker/distribution"
25 "github.com/docker/distribution/configuration"
26 "github.com/docker/distribution/manifest"
27 "github.com/docker/distribution/manifest/manifestlist"
28 "github.com/docker/distribution/manifest/schema1"
29 "github.com/docker/distribution/manifest/schema2"
30 "github.com/docker/distribution/registry/api/errcode"
31 v2 "github.com/docker/distribution/registry/api/v2"
32 storagedriver "github.com/docker/distribution/registry/storage/driver"
33 "github.com/docker/distribution/registry/storage/driver/factory"
34 _ "github.com/docker/distribution/registry/storage/driver/testdriver"
35 "github.com/docker/distribution/testutil"
36 "github.com/docker/libtrust"
37 "github.com/gorilla/handlers"
38 "github.com/opencontainers/go-digest"
39 )
40
41 var headerConfig = http.Header{
42 "X-Content-Type-Options": []string{"nosniff"},
43 }
44
45 const (
46
47 digestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
48 )
49
50
51
52 func TestCheckAPI(t *testing.T) {
53 env := newTestEnv(t, false)
54 defer env.Shutdown()
55 baseURL, err := env.builder.BuildBaseURL()
56 if err != nil {
57 t.Fatalf("unexpected error building base url: %v", err)
58 }
59
60 resp, err := http.Get(baseURL)
61 if err != nil {
62 t.Fatalf("unexpected error issuing request: %v", err)
63 }
64 defer resp.Body.Close()
65
66 checkResponse(t, "issuing api base check", resp, http.StatusOK)
67 checkHeaders(t, resp, http.Header{
68 "Content-Type": []string{"application/json; charset=utf-8"},
69 "Content-Length": []string{"2"},
70 })
71
72 p, err := ioutil.ReadAll(resp.Body)
73 if err != nil {
74 t.Fatalf("unexpected error reading response body: %v", err)
75 }
76
77 if string(p) != "{}" {
78 t.Fatalf("unexpected response body: %v", string(p))
79 }
80 }
81
82
83 func TestCatalogAPI(t *testing.T) {
84 env := newTestEnv(t, false)
85 defer env.Shutdown()
86
87 maxEntries := env.config.Catalog.MaxEntries
88 allCatalog := []string{
89 "foo/aaaa", "foo/bbbb", "foo/cccc", "foo/dddd", "foo/eeee", "foo/ffff",
90 }
91
92 chunkLen := maxEntries - 1
93
94 catalogURL, err := env.builder.BuildCatalogURL()
95 if err != nil {
96 t.Fatalf("unexpected error building catalog url: %v", err)
97 }
98
99
100
101 resp, err := http.Get(catalogURL)
102 if err != nil {
103 t.Fatalf("unexpected error issuing request: %v", err)
104 }
105 defer resp.Body.Close()
106
107 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
108
109 var ctlg struct {
110 Repositories []string `json:"repositories"`
111 }
112
113 dec := json.NewDecoder(resp.Body)
114 if err := dec.Decode(&ctlg); err != nil {
115 t.Fatalf("error decoding fetched manifest: %v", err)
116 }
117
118
119 if len(ctlg.Repositories) != 0 {
120 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", 0, len(ctlg.Repositories))
121 }
122
123
124 if resp.Header.Get("Link") != "" {
125 t.Fatalf("repositories has more data when none expected")
126 }
127
128 for _, image := range allCatalog {
129 createRepository(env, t, image, "sometag")
130 }
131
132
133
134 resp, err = http.Get(catalogURL)
135 if err != nil {
136 t.Fatalf("unexpected error issuing request: %v", err)
137 }
138 defer resp.Body.Close()
139
140 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
141
142 dec = json.NewDecoder(resp.Body)
143 if err = dec.Decode(&ctlg); err != nil {
144 t.Fatalf("error decoding fetched manifest: %v", err)
145 }
146
147
148 if len(ctlg.Repositories) != maxEntries {
149 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", maxEntries, len(ctlg.Repositories))
150 }
151
152
153 for _, image := range allCatalog[:maxEntries] {
154 if !contains(ctlg.Repositories, image) {
155 t.Fatalf("didn't find our repository '%s' in the catalog", image)
156 }
157 }
158
159
160 link := resp.Header.Get("Link")
161 if link == "" {
162 t.Fatalf("repositories has less data than expected")
163 }
164
165
166
167
168 values := checkLink(t, link, maxEntries, ctlg.Repositories[len(ctlg.Repositories)-1])
169
170 catalogURL, err = env.builder.BuildCatalogURL(values)
171 if err != nil {
172 t.Fatalf("unexpected error building catalog url: %v", err)
173 }
174
175 resp, err = http.Get(catalogURL)
176 if err != nil {
177 t.Fatalf("unexpected error issuing request: %v", err)
178 }
179 defer resp.Body.Close()
180
181 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
182
183 dec = json.NewDecoder(resp.Body)
184 if err = dec.Decode(&ctlg); err != nil {
185 t.Fatalf("error decoding fetched manifest: %v", err)
186 }
187
188 expectedRemainder := len(allCatalog) - maxEntries
189 if len(ctlg.Repositories) != expectedRemainder {
190 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", expectedRemainder, len(ctlg.Repositories))
191 }
192
193
194
195 values = url.Values{
196 "last": []string{""},
197 "n": []string{strconv.Itoa(maxEntries)},
198 }
199
200 catalogURL, err = env.builder.BuildCatalogURL(values)
201 if err != nil {
202 t.Fatalf("unexpected error building catalog url: %v", err)
203 }
204
205 resp, err = http.Get(catalogURL)
206 if err != nil {
207 t.Fatalf("unexpected error issuing request: %v", err)
208 }
209 defer resp.Body.Close()
210
211 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
212
213 dec = json.NewDecoder(resp.Body)
214 if err = dec.Decode(&ctlg); err != nil {
215 t.Fatalf("error decoding fetched manifest: %v", err)
216 }
217
218 if len(ctlg.Repositories) != maxEntries {
219 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", maxEntries, len(ctlg.Repositories))
220 }
221
222
223 link = resp.Header.Get("Link")
224 if link == "" {
225 t.Fatalf("repositories has less data than expected")
226 }
227
228
229
230
231
232 values = checkLink(t, link, maxEntries, ctlg.Repositories[len(ctlg.Repositories)-1])
233
234 catalogURL, err = env.builder.BuildCatalogURL(values)
235 if err != nil {
236 t.Fatalf("unexpected error building catalog url: %v", err)
237 }
238
239 resp, err = http.Get(catalogURL)
240 if err != nil {
241 t.Fatalf("unexpected error issuing request: %v", err)
242 }
243 defer resp.Body.Close()
244
245 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
246
247 dec = json.NewDecoder(resp.Body)
248 if err = dec.Decode(&ctlg); err != nil {
249 t.Fatalf("error decoding fetched manifest: %v", err)
250 }
251
252 expectedRemainder = len(allCatalog) - maxEntries
253 if len(ctlg.Repositories) != expectedRemainder {
254 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", expectedRemainder, len(ctlg.Repositories))
255 }
256
257
258
259 values = url.Values{
260 "n": []string{strconv.Itoa(chunkLen)},
261 }
262
263 catalogURL, err = env.builder.BuildCatalogURL(values)
264 if err != nil {
265 t.Fatalf("unexpected error building catalog url: %v", err)
266 }
267
268 resp, err = http.Get(catalogURL)
269 if err != nil {
270 t.Fatalf("unexpected error issuing request: %v", err)
271 }
272 defer resp.Body.Close()
273
274 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
275
276 dec = json.NewDecoder(resp.Body)
277 if err = dec.Decode(&ctlg); err != nil {
278 t.Fatalf("error decoding fetched manifest: %v", err)
279 }
280
281
282 if len(ctlg.Repositories) != chunkLen {
283 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", expectedRemainder, len(ctlg.Repositories))
284 }
285
286
287 link = resp.Header.Get("Link")
288 if link == "" {
289 t.Fatalf("repositories has less data than expected")
290 }
291
292
293
294
295
296 values = checkLink(t, link, chunkLen, ctlg.Repositories[len(ctlg.Repositories)-1])
297
298 catalogURL, err = env.builder.BuildCatalogURL(values)
299 if err != nil {
300 t.Fatalf("unexpected error building catalog url: %v", err)
301 }
302
303 resp, err = http.Get(catalogURL)
304 if err != nil {
305 t.Fatalf("unexpected error issuing request: %v", err)
306 }
307 defer resp.Body.Close()
308
309 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
310
311 dec = json.NewDecoder(resp.Body)
312 if err = dec.Decode(&ctlg); err != nil {
313 t.Fatalf("error decoding fetched manifest: %v", err)
314 }
315
316 expectedRemainder = len(allCatalog) - chunkLen
317 if len(ctlg.Repositories) != expectedRemainder {
318 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", expectedRemainder, len(ctlg.Repositories))
319 }
320
321
322
323 values = url.Values{
324 "n": []string{strconv.Itoa(maxEntries + 10)},
325 }
326
327 catalogURL, err = env.builder.BuildCatalogURL(values)
328 if err != nil {
329 t.Fatalf("unexpected error building catalog url: %v", err)
330 }
331
332 resp, err = http.Get(catalogURL)
333 if err != nil {
334 t.Fatalf("unexpected error issuing request: %v", err)
335 }
336 defer resp.Body.Close()
337
338 checkResponse(t, "issuing catalog api check", resp, http.StatusBadRequest)
339 checkBodyHasErrorCodes(t, "invalid number of results requested", resp, v2.ErrorCodePaginationNumberInvalid)
340
341
342
343 values = url.Values{
344 "n": []string{strconv.Itoa(len(allCatalog))},
345 }
346
347 catalogURL, err = env.builder.BuildCatalogURL(values)
348 if err != nil {
349 t.Fatalf("unexpected error building catalog url: %v", err)
350 }
351
352 resp, err = http.Get(catalogURL)
353 if err != nil {
354 t.Fatalf("unexpected error issuing request: %v", err)
355 }
356 defer resp.Body.Close()
357
358 checkResponse(t, "issuing catalog api check", resp, http.StatusBadRequest)
359 checkBodyHasErrorCodes(t, "invalid number of results requested", resp, v2.ErrorCodePaginationNumberInvalid)
360
361
362
363 values = url.Values{
364 "n": []string{"0"},
365 }
366
367 catalogURL, err = env.builder.BuildCatalogURL(values)
368 if err != nil {
369 t.Fatalf("unexpected error building catalog url: %v", err)
370 }
371
372 resp, err = http.Get(catalogURL)
373 if err != nil {
374 t.Fatalf("unexpected error issuing request: %v", err)
375 }
376 defer resp.Body.Close()
377
378 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
379
380 dec = json.NewDecoder(resp.Body)
381 if err = dec.Decode(&ctlg); err != nil {
382 t.Fatalf("error decoding fetched manifest: %v", err)
383 }
384
385
386 if len(ctlg.Repositories) != 0 {
387 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", 0, len(ctlg.Repositories))
388 }
389
390
391
392 values = url.Values{
393 "n": []string{"-1"},
394 }
395
396 catalogURL, err = env.builder.BuildCatalogURL(values)
397 if err != nil {
398 t.Fatalf("unexpected error building catalog url: %v", err)
399 }
400
401 resp, err = http.Get(catalogURL)
402 if err != nil {
403 t.Fatalf("unexpected error issuing request: %v", err)
404 }
405 defer resp.Body.Close()
406
407 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
408
409 dec = json.NewDecoder(resp.Body)
410 if err = dec.Decode(&ctlg); err != nil {
411 t.Fatalf("error decoding fetched manifest: %v", err)
412 }
413
414
415 if len(ctlg.Repositories) != maxEntries {
416 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", expectedRemainder, len(ctlg.Repositories))
417 }
418
419
420
421 values = url.Values{
422 "n": []string{strconv.Itoa(maxEntries)},
423 }
424
425 envWithLessImages := newTestEnv(t, false)
426 for _, image := range allCatalog[0:(maxEntries - 1)] {
427 createRepository(envWithLessImages, t, image, "sometag")
428 }
429
430 catalogURL, err = envWithLessImages.builder.BuildCatalogURL(values)
431 if err != nil {
432 t.Fatalf("unexpected error building catalog url: %v", err)
433 }
434
435 resp, err = http.Get(catalogURL)
436 if err != nil {
437 t.Fatalf("unexpected error issuing request: %v", err)
438 }
439 defer resp.Body.Close()
440
441 checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
442
443 dec = json.NewDecoder(resp.Body)
444 if err = dec.Decode(&ctlg); err != nil {
445 t.Fatalf("error decoding fetched manifest: %v", err)
446 }
447
448
449 if len(ctlg.Repositories) != maxEntries-1 {
450 t.Fatalf("repositories returned unexpected entries (expected: %d, returned: %d)", maxEntries-1, len(ctlg.Repositories))
451 }
452 }
453
454 func checkLink(t *testing.T, urlStr string, numEntries int, last string) url.Values {
455 re := regexp.MustCompile("<(/v2/_catalog.*)>; rel=\"next\"")
456 matches := re.FindStringSubmatch(urlStr)
457
458 if len(matches) != 2 {
459 t.Fatalf("Catalog link address response was incorrect")
460 }
461 linkURL, _ := url.Parse(matches[1])
462 urlValues := linkURL.Query()
463
464 if urlValues.Get("n") != strconv.Itoa(numEntries) {
465 t.Fatalf("Catalog link entry size is incorrect (expected: %v, returned: %v)", urlValues.Get("n"), strconv.Itoa(numEntries))
466 }
467
468 if urlValues.Get("last") != last {
469 t.Fatal("Catalog link last entry is incorrect")
470 }
471
472 return urlValues
473 }
474
475 func contains(elems []string, e string) bool {
476 for _, elem := range elems {
477 if elem == e {
478 return true
479 }
480 }
481 return false
482 }
483
484 func TestURLPrefix(t *testing.T) {
485 config := configuration.Configuration{
486 Storage: configuration.Storage{
487 "testdriver": configuration.Parameters{},
488 "maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
489 "enabled": false,
490 }},
491 },
492 }
493 config.HTTP.Prefix = "/test/"
494 config.HTTP.Headers = headerConfig
495
496 env := newTestEnvWithConfig(t, &config)
497 defer env.Shutdown()
498
499 baseURL, err := env.builder.BuildBaseURL()
500 if err != nil {
501 t.Fatalf("unexpected error building base url: %v", err)
502 }
503
504 parsed, _ := url.Parse(baseURL)
505 if !strings.HasPrefix(parsed.Path, config.HTTP.Prefix) {
506 t.Fatalf("Prefix %v not included in test url %v", config.HTTP.Prefix, baseURL)
507 }
508
509 resp, err := http.Get(baseURL)
510 if err != nil {
511 t.Fatalf("unexpected error issuing request: %v", err)
512 }
513 defer resp.Body.Close()
514
515 checkResponse(t, "issuing api base check", resp, http.StatusOK)
516 checkHeaders(t, resp, http.Header{
517 "Content-Type": []string{"application/json; charset=utf-8"},
518 "Content-Length": []string{"2"},
519 })
520 }
521
522 type blobArgs struct {
523 imageName reference.Named
524 layerFile io.ReadSeeker
525 layerDigest digest.Digest
526 }
527
528 func makeBlobArgs(t *testing.T) blobArgs {
529 layerFile, layerDigest, err := testutil.CreateRandomTarFile()
530 if err != nil {
531 t.Fatalf("error creating random layer file: %v", err)
532 }
533
534 args := blobArgs{
535 layerFile: layerFile,
536 layerDigest: layerDigest,
537 }
538 args.imageName, _ = reference.WithName("foo/bar")
539 return args
540 }
541
542
543 func TestBlobAPI(t *testing.T) {
544 deleteEnabled := false
545 env1 := newTestEnv(t, deleteEnabled)
546 defer env1.Shutdown()
547 args := makeBlobArgs(t)
548 testBlobAPI(t, env1, args)
549
550 deleteEnabled = true
551 env2 := newTestEnv(t, deleteEnabled)
552 defer env2.Shutdown()
553 args = makeBlobArgs(t)
554 testBlobAPI(t, env2, args)
555
556 }
557
558 func TestBlobDelete(t *testing.T) {
559 deleteEnabled := true
560 env := newTestEnv(t, deleteEnabled)
561 defer env.Shutdown()
562
563 args := makeBlobArgs(t)
564 env = testBlobAPI(t, env, args)
565 testBlobDelete(t, env, args)
566 }
567
568 func TestRelativeURL(t *testing.T) {
569 config := configuration.Configuration{
570 Storage: configuration.Storage{
571 "testdriver": configuration.Parameters{},
572 "maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
573 "enabled": false,
574 }},
575 },
576 }
577 config.HTTP.Headers = headerConfig
578 config.HTTP.RelativeURLs = false
579 env := newTestEnvWithConfig(t, &config)
580 defer env.Shutdown()
581 ref, _ := reference.WithName("foo/bar")
582 uploadURLBaseAbs, _ := startPushLayer(t, env, ref)
583
584 u, err := url.Parse(uploadURLBaseAbs)
585 if err != nil {
586 t.Fatal(err)
587 }
588 if !u.IsAbs() {
589 t.Fatal("Relative URL returned from blob upload chunk with non-relative configuration")
590 }
591
592 args := makeBlobArgs(t)
593 resp, err := doPushLayer(t, env.builder, ref, args.layerDigest, uploadURLBaseAbs, args.layerFile)
594 if err != nil {
595 t.Fatalf("unexpected error doing layer push relative url: %v", err)
596 }
597 checkResponse(t, "relativeurl blob upload", resp, http.StatusCreated)
598 u, err = url.Parse(resp.Header.Get("Location"))
599 if err != nil {
600 t.Fatal(err)
601 }
602 if !u.IsAbs() {
603 t.Fatal("Relative URL returned from blob upload with non-relative configuration")
604 }
605
606 config.HTTP.RelativeURLs = true
607 args = makeBlobArgs(t)
608 uploadURLBaseRelative, _ := startPushLayer(t, env, ref)
609 u, err = url.Parse(uploadURLBaseRelative)
610 if err != nil {
611 t.Fatal(err)
612 }
613 if u.IsAbs() {
614 t.Fatal("Absolute URL returned from blob upload chunk with relative configuration")
615 }
616
617
618 config.HTTP.RelativeURLs = false
619 uploadURLBaseAbs, _ = startPushLayer(t, env, ref)
620 u, err = url.Parse(uploadURLBaseAbs)
621 if err != nil {
622 t.Fatal(err)
623 }
624 if !u.IsAbs() {
625 t.Fatal("Relative URL returned from blob upload chunk with non-relative configuration")
626 }
627
628
629 config.HTTP.RelativeURLs = true
630 resp, err = doPushLayer(t, env.builder, ref, args.layerDigest, uploadURLBaseAbs, args.layerFile)
631 if err != nil {
632 t.Fatalf("unexpected error doing layer push relative url: %v", err)
633 }
634
635 checkResponse(t, "relativeurl blob upload", resp, http.StatusCreated)
636 u, err = url.Parse(resp.Header.Get("Location"))
637 if err != nil {
638 t.Fatal(err)
639 }
640 if u.IsAbs() {
641 t.Fatal("Relative URL returned from blob upload with non-relative configuration")
642 }
643 }
644
645 func TestBlobDeleteDisabled(t *testing.T) {
646 deleteEnabled := false
647 env := newTestEnv(t, deleteEnabled)
648 defer env.Shutdown()
649 args := makeBlobArgs(t)
650
651 imageName := args.imageName
652 layerDigest := args.layerDigest
653 ref, _ := reference.WithDigest(imageName, layerDigest)
654 layerURL, err := env.builder.BuildBlobURL(ref)
655 if err != nil {
656 t.Fatalf("error building url: %v", err)
657 }
658
659 resp, err := httpDelete(layerURL)
660 if err != nil {
661 t.Fatalf("unexpected error deleting when disabled: %v", err)
662 }
663
664 checkResponse(t, "status of disabled delete", resp, http.StatusMethodNotAllowed)
665 }
666
667 func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv {
668
669
670
671 imageName := args.imageName
672 layerFile := args.layerFile
673 layerDigest := args.layerDigest
674
675
676
677 ref, _ := reference.WithDigest(imageName, layerDigest)
678 layerURL, err := env.builder.BuildBlobURL(ref)
679 if err != nil {
680 t.Fatalf("error building url: %v", err)
681 }
682
683 resp, err := http.Get(layerURL)
684 if err != nil {
685 t.Fatalf("unexpected error fetching non-existent layer: %v", err)
686 }
687
688 checkResponse(t, "fetching non-existent content", resp, http.StatusNotFound)
689
690
691
692 resp, err = http.Head(layerURL)
693 if err != nil {
694 t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
695 }
696
697 checkResponse(t, "checking head on non-existent layer", resp, http.StatusNotFound)
698
699
700
701 uploadURLBase, uploadUUID := startPushLayer(t, env, imageName)
702
703
704 resp, err = http.Get(uploadURLBase)
705 if err != nil {
706 t.Fatalf("unexpected error getting upload status: %v", err)
707 }
708 checkResponse(t, "status of deleted upload", resp, http.StatusNoContent)
709 checkHeaders(t, resp, http.Header{
710 "Location": []string{"*"},
711 "Range": []string{"0-0"},
712 "Docker-Upload-UUID": []string{uploadUUID},
713 })
714
715 req, err := http.NewRequest("DELETE", uploadURLBase, nil)
716 if err != nil {
717 t.Fatalf("unexpected error creating delete request: %v", err)
718 }
719
720 resp, err = http.DefaultClient.Do(req)
721 if err != nil {
722 t.Fatalf("unexpected error sending delete request: %v", err)
723 }
724
725 checkResponse(t, "deleting upload", resp, http.StatusNoContent)
726
727
728 resp, err = http.Get(uploadURLBase)
729 if err != nil {
730 t.Fatalf("unexpected error getting upload status: %v", err)
731 }
732 checkResponse(t, "status of deleted upload", resp, http.StatusNotFound)
733
734
735
736 uploadURLBase, _ = startPushLayer(t, env, imageName)
737 resp, err = doPushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, bytes.NewReader([]byte{}))
738 if err != nil {
739 t.Fatalf("unexpected error doing bad layer push: %v", err)
740 }
741
742 checkResponse(t, "bad layer push", resp, http.StatusBadRequest)
743 checkBodyHasErrorCodes(t, "bad layer push", resp, v2.ErrorCodeDigestInvalid)
744
745
746
747 zeroDigest, err := digest.FromReader(bytes.NewReader([]byte{}))
748 if err != nil {
749 t.Fatalf("unexpected error digesting empty buffer: %v", err)
750 }
751
752 uploadURLBase, _ = startPushLayer(t, env, imageName)
753 pushLayer(t, env.builder, imageName, zeroDigest, uploadURLBase, bytes.NewReader([]byte{}))
754
755
756
757
758
759 emptyTar := bytes.Repeat([]byte("\x00"), 1024)
760 emptyDigest, err := digest.FromReader(bytes.NewReader(emptyTar))
761 if err != nil {
762 t.Fatalf("unexpected error digesting empty tar: %v", err)
763 }
764
765 uploadURLBase, _ = startPushLayer(t, env, imageName)
766 pushLayer(t, env.builder, imageName, emptyDigest, uploadURLBase, bytes.NewReader(emptyTar))
767
768
769
770 layerLength, _ := layerFile.Seek(0, io.SeekEnd)
771 layerFile.Seek(0, io.SeekStart)
772
773 uploadURLBase, _ = startPushLayer(t, env, imageName)
774 pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
775
776
777
778 layerFile.Seek(0, 0)
779
780 canonicalDigester := digest.Canonical.Digester()
781 if _, err := io.Copy(canonicalDigester.Hash(), layerFile); err != nil {
782 t.Fatalf("error copying to digest: %v", err)
783 }
784 canonicalDigest := canonicalDigester.Digest()
785
786 layerFile.Seek(0, 0)
787 uploadURLBase, _ = startPushLayer(t, env, imageName)
788 uploadURLBase, dgst := pushChunk(t, env.builder, imageName, uploadURLBase, layerFile, layerLength)
789 finishUpload(t, env.builder, imageName, uploadURLBase, dgst)
790
791
792
793 resp, err = http.Head(layerURL)
794 if err != nil {
795 t.Fatalf("unexpected error checking head on existing layer: %v", err)
796 }
797
798 checkResponse(t, "checking head on existing layer", resp, http.StatusOK)
799 checkHeaders(t, resp, http.Header{
800 "Content-Length": []string{fmt.Sprint(layerLength)},
801 "Docker-Content-Digest": []string{canonicalDigest.String()},
802 })
803
804
805
806 resp, err = http.Get(layerURL)
807 if err != nil {
808 t.Fatalf("unexpected error fetching layer: %v", err)
809 }
810
811 checkResponse(t, "fetching layer", resp, http.StatusOK)
812 checkHeaders(t, resp, http.Header{
813 "Content-Length": []string{fmt.Sprint(layerLength)},
814 "Docker-Content-Digest": []string{canonicalDigest.String()},
815 })
816
817
818 verifier := layerDigest.Verifier()
819 io.Copy(verifier, resp.Body)
820
821 if !verifier.Verified() {
822 t.Fatalf("response body did not pass verification")
823 }
824
825
826
827 badURL := strings.Replace(layerURL, "sha256", "sha257", 1)
828 resp, err = http.Get(badURL)
829 if err != nil {
830 t.Fatalf("unexpected error fetching layer: %v", err)
831 }
832
833 checkResponse(t, "fetching layer bad digest", resp, http.StatusBadRequest)
834
835
836 resp, err = http.Get(layerURL)
837 if err != nil {
838 t.Fatalf("unexpected error fetching layer: %v", err)
839 }
840
841 checkResponse(t, "fetching layer", resp, http.StatusOK)
842 checkHeaders(t, resp, http.Header{
843 "Content-Length": []string{fmt.Sprint(layerLength)},
844 "Docker-Content-Digest": []string{canonicalDigest.String()},
845 "ETag": []string{fmt.Sprintf(`"%s"`, canonicalDigest)},
846 "Cache-Control": []string{"max-age=31536000"},
847 })
848
849
850 etag := resp.Header.Get("Etag")
851 req, err = http.NewRequest("GET", layerURL, nil)
852 if err != nil {
853 t.Fatalf("Error constructing request: %s", err)
854 }
855 req.Header.Set("If-None-Match", etag)
856
857 resp, err = http.DefaultClient.Do(req)
858 if err != nil {
859 t.Fatalf("Error constructing request: %s", err)
860 }
861
862 checkResponse(t, "fetching layer with etag", resp, http.StatusNotModified)
863
864
865 req, err = http.NewRequest("GET", layerURL, nil)
866 if err != nil {
867 t.Fatalf("Error constructing request: %s", err)
868 }
869 req.Header.Set("If-None-Match", "")
870 resp, _ = http.DefaultClient.Do(req)
871 checkResponse(t, "fetching layer with invalid etag", resp, http.StatusOK)
872
873
874
875
876 return env
877 }
878
879 func testBlobDelete(t *testing.T, env *testEnv, args blobArgs) {
880
881 imageName := args.imageName
882 layerFile := args.layerFile
883 layerDigest := args.layerDigest
884
885 ref, _ := reference.WithDigest(imageName, layerDigest)
886 layerURL, err := env.builder.BuildBlobURL(ref)
887 if err != nil {
888 t.Fatalf(err.Error())
889 }
890
891
892 resp, err := httpDelete(layerURL)
893 if err != nil {
894 t.Fatalf("unexpected error deleting layer: %v", err)
895 }
896
897 checkResponse(t, "deleting layer", resp, http.StatusAccepted)
898 checkHeaders(t, resp, http.Header{
899 "Content-Length": []string{"0"},
900 })
901
902
903
904
905 resp, err = http.Head(layerURL)
906 if err != nil {
907 t.Fatalf("unexpected error checking head on existing layer: %v", err)
908 }
909
910 checkResponse(t, "checking existence of deleted layer", resp, http.StatusNotFound)
911
912
913 resp, err = httpDelete(layerURL)
914 if err != nil {
915 t.Fatalf("unexpected error deleting layer: %v", err)
916 }
917
918 checkResponse(t, "deleting layer", resp, http.StatusNotFound)
919
920
921
922 badURL := strings.Replace(layerURL, "sha256", "sha257", 1)
923 resp, err = httpDelete(badURL)
924 if err != nil {
925 t.Fatalf("unexpected error fetching layer: %v", err)
926 }
927
928 checkResponse(t, "deleting layer bad digest", resp, http.StatusBadRequest)
929
930
931
932 layerFile.Seek(0, io.SeekStart)
933
934 uploadURLBase, _ := startPushLayer(t, env, imageName)
935 pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
936
937 layerFile.Seek(0, io.SeekStart)
938 canonicalDigester := digest.Canonical.Digester()
939 if _, err := io.Copy(canonicalDigester.Hash(), layerFile); err != nil {
940 t.Fatalf("error copying to digest: %v", err)
941 }
942 canonicalDigest := canonicalDigester.Digest()
943
944
945
946 resp, err = http.Head(layerURL)
947 if err != nil {
948 t.Fatalf("unexpected error checking head on existing layer: %v", err)
949 }
950
951 layerLength, _ := layerFile.Seek(0, io.SeekEnd)
952 checkResponse(t, "checking head on reuploaded layer", resp, http.StatusOK)
953 checkHeaders(t, resp, http.Header{
954 "Content-Length": []string{fmt.Sprint(layerLength)},
955 "Docker-Content-Digest": []string{canonicalDigest.String()},
956 })
957 }
958
959 func TestDeleteDisabled(t *testing.T) {
960 env := newTestEnv(t, false)
961 defer env.Shutdown()
962
963 imageName, _ := reference.WithName("foo/bar")
964
965 layerFile, layerDigest, err := testutil.CreateRandomTarFile()
966 if err != nil {
967 t.Fatalf("error creating random layer file: %v", err)
968 }
969
970 ref, _ := reference.WithDigest(imageName, layerDigest)
971 layerURL, err := env.builder.BuildBlobURL(ref)
972 if err != nil {
973 t.Fatalf("Error building blob URL")
974 }
975 uploadURLBase, _ := startPushLayer(t, env, imageName)
976 pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
977
978 resp, err := httpDelete(layerURL)
979 if err != nil {
980 t.Fatalf("unexpected error deleting layer: %v", err)
981 }
982
983 checkResponse(t, "deleting layer with delete disabled", resp, http.StatusMethodNotAllowed)
984 }
985
986 func TestDeleteReadOnly(t *testing.T) {
987 env := newTestEnv(t, true)
988 defer env.Shutdown()
989
990 imageName, _ := reference.WithName("foo/bar")
991
992 layerFile, layerDigest, err := testutil.CreateRandomTarFile()
993 if err != nil {
994 t.Fatalf("error creating random layer file: %v", err)
995 }
996
997 ref, _ := reference.WithDigest(imageName, layerDigest)
998 layerURL, err := env.builder.BuildBlobURL(ref)
999 if err != nil {
1000 t.Fatalf("Error building blob URL")
1001 }
1002 uploadURLBase, _ := startPushLayer(t, env, imageName)
1003 pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
1004
1005 env.app.readOnly = true
1006
1007 resp, err := httpDelete(layerURL)
1008 if err != nil {
1009 t.Fatalf("unexpected error deleting layer: %v", err)
1010 }
1011
1012 checkResponse(t, "deleting layer in read-only mode", resp, http.StatusMethodNotAllowed)
1013 }
1014
1015 func TestStartPushReadOnly(t *testing.T) {
1016 env := newTestEnv(t, true)
1017 defer env.Shutdown()
1018 env.app.readOnly = true
1019
1020 imageName, _ := reference.WithName("foo/bar")
1021
1022 layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
1023 if err != nil {
1024 t.Fatalf("unexpected error building layer upload url: %v", err)
1025 }
1026
1027 resp, err := http.Post(layerUploadURL, "", nil)
1028 if err != nil {
1029 t.Fatalf("unexpected error starting layer push: %v", err)
1030 }
1031 defer resp.Body.Close()
1032
1033 checkResponse(t, "starting push in read-only mode", resp, http.StatusMethodNotAllowed)
1034 }
1035
1036 func httpDelete(url string) (*http.Response, error) {
1037 req, err := http.NewRequest("DELETE", url, nil)
1038 if err != nil {
1039 return nil, err
1040 }
1041
1042 resp, err := http.DefaultClient.Do(req)
1043 if err != nil {
1044 return nil, err
1045 }
1046
1047 return resp, err
1048 }
1049
1050 type manifestArgs struct {
1051 imageName reference.Named
1052 mediaType string
1053 manifest distribution.Manifest
1054 dgst digest.Digest
1055 }
1056
1057 func TestManifestAPI(t *testing.T) {
1058 schema1Repo, _ := reference.WithName("foo/schema1")
1059 schema2Repo, _ := reference.WithName("foo/schema2")
1060
1061 deleteEnabled := false
1062 env1 := newTestEnv(t, deleteEnabled)
1063 defer env1.Shutdown()
1064 testManifestAPISchema1(t, env1, schema1Repo)
1065 schema2Args := testManifestAPISchema2(t, env1, schema2Repo)
1066 testManifestAPIManifestList(t, env1, schema2Args)
1067
1068 deleteEnabled = true
1069 env2 := newTestEnv(t, deleteEnabled)
1070 defer env2.Shutdown()
1071 testManifestAPISchema1(t, env2, schema1Repo)
1072 schema2Args = testManifestAPISchema2(t, env2, schema2Repo)
1073 testManifestAPIManifestList(t, env2, schema2Args)
1074 }
1075
1076
1077 type storageManifestErrDriverFactory struct{}
1078
1079 const (
1080 repositoryWithManifestNotFound = "manifesttagnotfound"
1081 repositoryWithManifestInvalidPath = "manifestinvalidpath"
1082 repositoryWithManifestBadLink = "manifestbadlink"
1083 repositoryWithGenericStorageError = "genericstorageerr"
1084 )
1085
1086 func (factory *storageManifestErrDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) {
1087
1088 var errGenericStorage = errors.New("generic storage error")
1089 return &mockErrorDriver{
1090 returnErrs: []mockErrorMapping{
1091 {
1092 pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithManifestNotFound),
1093 content: nil,
1094 err: storagedriver.PathNotFoundError{},
1095 },
1096 {
1097 pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithManifestInvalidPath),
1098 content: nil,
1099 err: storagedriver.InvalidPathError{},
1100 },
1101 {
1102 pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithManifestBadLink),
1103 content: []byte("this is a bad sha"),
1104 err: nil,
1105 },
1106 {
1107 pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithGenericStorageError),
1108 content: nil,
1109 err: errGenericStorage,
1110 },
1111 },
1112 }, nil
1113 }
1114
1115 type mockErrorMapping struct {
1116 pathMatch string
1117 content []byte
1118 err error
1119 }
1120
1121
1122 type mockErrorDriver struct {
1123 storagedriver.StorageDriver
1124 returnErrs []mockErrorMapping
1125 }
1126
1127 func (dr *mockErrorDriver) GetContent(ctx context.Context, path string) ([]byte, error) {
1128 for _, returns := range dr.returnErrs {
1129 if strings.Contains(path, returns.pathMatch) {
1130 return returns.content, returns.err
1131 }
1132 }
1133 return nil, errors.New("Unknown storage error")
1134 }
1135
1136 func TestGetManifestWithStorageError(t *testing.T) {
1137 factory.Register("storagemanifesterror", &storageManifestErrDriverFactory{})
1138 config := configuration.Configuration{
1139 Storage: configuration.Storage{
1140 "storagemanifesterror": configuration.Parameters{},
1141 "maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
1142 "enabled": false,
1143 }},
1144 },
1145 }
1146 config.HTTP.Headers = headerConfig
1147 env1 := newTestEnvWithConfig(t, &config)
1148 defer env1.Shutdown()
1149
1150 repo, _ := reference.WithName(repositoryWithManifestNotFound)
1151 testManifestWithStorageError(t, env1, repo, http.StatusNotFound, v2.ErrorCodeManifestUnknown)
1152
1153 repo, _ = reference.WithName(repositoryWithGenericStorageError)
1154 testManifestWithStorageError(t, env1, repo, http.StatusInternalServerError, errcode.ErrorCodeUnknown)
1155
1156 repo, _ = reference.WithName(repositoryWithManifestInvalidPath)
1157 testManifestWithStorageError(t, env1, repo, http.StatusInternalServerError, errcode.ErrorCodeUnknown)
1158
1159 repo, _ = reference.WithName(repositoryWithManifestBadLink)
1160 testManifestWithStorageError(t, env1, repo, http.StatusInternalServerError, errcode.ErrorCodeUnknown)
1161 }
1162
1163 func TestManifestDelete(t *testing.T) {
1164 schema1Repo, _ := reference.WithName("foo/schema1")
1165 schema2Repo, _ := reference.WithName("foo/schema2")
1166
1167 deleteEnabled := true
1168 env := newTestEnv(t, deleteEnabled)
1169 defer env.Shutdown()
1170 schema1Args := testManifestAPISchema1(t, env, schema1Repo)
1171 testManifestDelete(t, env, schema1Args)
1172 schema2Args := testManifestAPISchema2(t, env, schema2Repo)
1173 testManifestDelete(t, env, schema2Args)
1174 }
1175
1176 func TestManifestDeleteDisabled(t *testing.T) {
1177 schema1Repo, _ := reference.WithName("foo/schema1")
1178 deleteEnabled := false
1179 env := newTestEnv(t, deleteEnabled)
1180 defer env.Shutdown()
1181 testManifestDeleteDisabled(t, env, schema1Repo)
1182 }
1183
1184 func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.Named) {
1185 ref, _ := reference.WithDigest(imageName, digestSha256EmptyTar)
1186 manifestURL, err := env.builder.BuildManifestURL(ref)
1187 if err != nil {
1188 t.Fatalf("unexpected error getting manifest url: %v", err)
1189 }
1190
1191 resp, err := httpDelete(manifestURL)
1192 if err != nil {
1193 t.Fatalf("unexpected error deleting manifest %v", err)
1194 }
1195 defer resp.Body.Close()
1196
1197 checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
1198 }
1199
1200 func testManifestWithStorageError(t *testing.T, env *testEnv, imageName reference.Named, expectedStatusCode int, expectedErrorCode errcode.ErrorCode) {
1201 tag := "latest"
1202 tagRef, _ := reference.WithTag(imageName, tag)
1203 manifestURL, err := env.builder.BuildManifestURL(tagRef)
1204 if err != nil {
1205 t.Fatalf("unexpected error getting manifest url: %v", err)
1206 }
1207
1208
1209
1210 resp, err := http.Get(manifestURL)
1211 if err != nil {
1212 t.Fatalf("unexpected error getting manifest: %v", err)
1213 }
1214 defer resp.Body.Close()
1215 checkResponse(t, "getting non-existent manifest", resp, expectedStatusCode)
1216 checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, expectedErrorCode)
1217 }
1218
1219 func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
1220 tag := "thetag"
1221 args := manifestArgs{imageName: imageName}
1222
1223 tagRef, _ := reference.WithTag(imageName, tag)
1224 manifestURL, err := env.builder.BuildManifestURL(tagRef)
1225 if err != nil {
1226 t.Fatalf("unexpected error getting manifest url: %v", err)
1227 }
1228
1229
1230
1231 resp, err := http.Get(manifestURL)
1232 if err != nil {
1233 t.Fatalf("unexpected error getting manifest: %v", err)
1234 }
1235 defer resp.Body.Close()
1236
1237 checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
1238 checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
1239
1240 tagsURL, err := env.builder.BuildTagsURL(imageName)
1241 if err != nil {
1242 t.Fatalf("unexpected error building tags url: %v", err)
1243 }
1244
1245 resp, err = http.Get(tagsURL)
1246 if err != nil {
1247 t.Fatalf("unexpected error getting unknown tags: %v", err)
1248 }
1249 defer resp.Body.Close()
1250
1251
1252 checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
1253 checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
1254
1255
1256
1257 unsignedManifest := &schema1.Manifest{
1258 Versioned: manifest.Versioned{
1259 SchemaVersion: 1,
1260 },
1261 Name: imageName.Name(),
1262 Tag: tag,
1263 FSLayers: []schema1.FSLayer{
1264 {
1265 BlobSum: "asdf",
1266 },
1267 {
1268 BlobSum: "qwer",
1269 },
1270 },
1271 History: []schema1.History{
1272 {
1273 V1Compatibility: "",
1274 },
1275 {
1276 V1Compatibility: "",
1277 },
1278 },
1279 }
1280
1281 resp = putManifest(t, "putting unsigned manifest", manifestURL, "", unsignedManifest)
1282 defer resp.Body.Close()
1283 checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
1284 _, p, counts := checkBodyHasErrorCodes(t, "putting unsigned manifest", resp, v2.ErrorCodeManifestInvalid)
1285
1286 expectedCounts := map[errcode.ErrorCode]int{
1287 v2.ErrorCodeManifestInvalid: 1,
1288 }
1289
1290 if !reflect.DeepEqual(counts, expectedCounts) {
1291 t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1292 }
1293
1294
1295 sm, err := schema1.Sign(unsignedManifest, env.pk)
1296 if err != nil {
1297 t.Fatalf("error signing manifest: %v", err)
1298 }
1299
1300 resp = putManifest(t, "putting signed manifest with errors", manifestURL, "", sm)
1301 defer resp.Body.Close()
1302 checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
1303 _, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
1304 v2.ErrorCodeManifestBlobUnknown, v2.ErrorCodeDigestInvalid)
1305
1306 expectedCounts = map[errcode.ErrorCode]int{
1307 v2.ErrorCodeManifestBlobUnknown: 2,
1308 v2.ErrorCodeDigestInvalid: 2,
1309 }
1310
1311 if !reflect.DeepEqual(counts, expectedCounts) {
1312 t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1313 }
1314
1315
1316
1317
1318
1319
1320 expectedLayers := make(map[digest.Digest]io.ReadSeeker)
1321
1322 for i := range unsignedManifest.FSLayers {
1323 rs, dgst, err := testutil.CreateRandomTarFile()
1324
1325 if err != nil {
1326 t.Fatalf("error creating random layer %d: %v", i, err)
1327 }
1328
1329 expectedLayers[dgst] = rs
1330 unsignedManifest.FSLayers[i].BlobSum = dgst
1331
1332 uploadURLBase, _ := startPushLayer(t, env, imageName)
1333 pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
1334 }
1335
1336
1337
1338 signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
1339 if err != nil {
1340 t.Fatalf("unexpected error signing manifest: %v", err)
1341 }
1342
1343 dgst := digest.FromBytes(signedManifest.Canonical)
1344 args.manifest = signedManifest
1345 args.dgst = dgst
1346
1347 digestRef, _ := reference.WithDigest(imageName, dgst)
1348 manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
1349 checkErr(t, err, "building manifest url")
1350
1351 resp = putManifest(t, "putting signed manifest no error", manifestURL, "", signedManifest)
1352 checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
1353 checkHeaders(t, resp, http.Header{
1354 "Location": []string{manifestDigestURL},
1355 "Docker-Content-Digest": []string{dgst.String()},
1356 })
1357
1358
1359
1360 resp = putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
1361 checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
1362 checkHeaders(t, resp, http.Header{
1363 "Location": []string{manifestDigestURL},
1364 "Docker-Content-Digest": []string{dgst.String()},
1365 })
1366
1367
1368
1369 resp, err = http.Get(manifestURL)
1370 if err != nil {
1371 t.Fatalf("unexpected error fetching manifest: %v", err)
1372 }
1373 defer resp.Body.Close()
1374
1375 checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1376 checkHeaders(t, resp, http.Header{
1377 "Docker-Content-Digest": []string{dgst.String()},
1378 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
1379 })
1380
1381 var fetchedManifest schema1.SignedManifest
1382 dec := json.NewDecoder(resp.Body)
1383
1384 if err := dec.Decode(&fetchedManifest); err != nil {
1385 t.Fatalf("error decoding fetched manifest: %v", err)
1386 }
1387
1388 if !bytes.Equal(fetchedManifest.Canonical, signedManifest.Canonical) {
1389 t.Fatalf("manifests do not match")
1390 }
1391
1392
1393
1394 resp, err = http.Get(manifestDigestURL)
1395 checkErr(t, err, "fetching manifest by digest")
1396 defer resp.Body.Close()
1397
1398 checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1399 checkHeaders(t, resp, http.Header{
1400 "Docker-Content-Digest": []string{dgst.String()},
1401 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
1402 })
1403
1404 var fetchedManifestByDigest schema1.SignedManifest
1405 dec = json.NewDecoder(resp.Body)
1406 if err := dec.Decode(&fetchedManifestByDigest); err != nil {
1407 t.Fatalf("error decoding fetched manifest: %v", err)
1408 }
1409
1410 if !bytes.Equal(fetchedManifestByDigest.Canonical, signedManifest.Canonical) {
1411 t.Fatalf("manifests do not match")
1412 }
1413
1414
1415 signatures, err := fetchedManifestByDigest.Signatures()
1416 if err != nil {
1417 t.Fatal(err)
1418 }
1419
1420 if len(signatures) != 1 {
1421 t.Fatalf("expected 1 signature from manifest, got: %d", len(signatures))
1422 }
1423
1424
1425 sm2, err := schema1.Sign(&fetchedManifestByDigest.Manifest, env.pk)
1426 if err != nil {
1427 t.Fatal(err)
1428
1429 }
1430
1431
1432
1433
1434 resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, schema1.MediaTypeSignedManifest, sm2)
1435 checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
1436 resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json; charset=utf-8", sm2)
1437 checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
1438 resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json", sm2)
1439 checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
1440
1441 resp, err = http.Get(manifestDigestURL)
1442 checkErr(t, err, "re-fetching manifest by digest")
1443 defer resp.Body.Close()
1444
1445 checkResponse(t, "re-fetching uploaded manifest", resp, http.StatusOK)
1446 checkHeaders(t, resp, http.Header{
1447 "Docker-Content-Digest": []string{dgst.String()},
1448 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
1449 })
1450
1451 dec = json.NewDecoder(resp.Body)
1452 if err := dec.Decode(&fetchedManifestByDigest); err != nil {
1453 t.Fatalf("error decoding fetched manifest: %v", err)
1454 }
1455
1456
1457 signatures, err = fetchedManifestByDigest.Signatures()
1458 if err != nil {
1459 t.Fatal(err)
1460 }
1461
1462 if len(signatures) != 1 {
1463 t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures))
1464 }
1465
1466
1467 etag := resp.Header.Get("Etag")
1468 req, err := http.NewRequest("GET", manifestURL, nil)
1469 if err != nil {
1470 t.Fatalf("Error constructing request: %s", err)
1471 }
1472 req.Header.Set("If-None-Match", etag)
1473 resp, err = http.DefaultClient.Do(req)
1474 if err != nil {
1475 t.Fatalf("Error constructing request: %s", err)
1476 }
1477
1478 checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
1479
1480
1481 req, err = http.NewRequest("GET", manifestDigestURL, nil)
1482 if err != nil {
1483 t.Fatalf("Error constructing request: %s", err)
1484 }
1485 req.Header.Set("If-None-Match", etag)
1486 resp, err = http.DefaultClient.Do(req)
1487 if err != nil {
1488 t.Fatalf("Error constructing request: %s", err)
1489 }
1490
1491 checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
1492
1493
1494 resp, err = http.Get(tagsURL)
1495 if err != nil {
1496 t.Fatalf("unexpected error getting unknown tags: %v", err)
1497 }
1498 defer resp.Body.Close()
1499
1500 checkResponse(t, "getting tags", resp, http.StatusOK)
1501 dec = json.NewDecoder(resp.Body)
1502
1503 var tagsResponse tagsAPIResponse
1504
1505 if err := dec.Decode(&tagsResponse); err != nil {
1506 t.Fatalf("unexpected error decoding error response: %v", err)
1507 }
1508
1509 if tagsResponse.Name != imageName.Name() {
1510 t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName.Name())
1511 }
1512
1513 if len(tagsResponse.Tags) != 1 {
1514 t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
1515 }
1516
1517 if tagsResponse.Tags[0] != tag {
1518 t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
1519 }
1520
1521
1522
1523 unsignedManifest.History = append(unsignedManifest.History, schema1.History{
1524 V1Compatibility: "",
1525 })
1526 invalidSigned, err := schema1.Sign(unsignedManifest, env.pk)
1527 if err != nil {
1528 t.Fatalf("error signing manifest")
1529 }
1530
1531 resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, "", invalidSigned)
1532 checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)
1533
1534 return args
1535 }
1536
1537 func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
1538 tag := "schema2tag"
1539 args := manifestArgs{
1540 imageName: imageName,
1541 mediaType: schema2.MediaTypeManifest,
1542 }
1543
1544 tagRef, _ := reference.WithTag(imageName, tag)
1545 manifestURL, err := env.builder.BuildManifestURL(tagRef)
1546 if err != nil {
1547 t.Fatalf("unexpected error getting manifest url: %v", err)
1548 }
1549
1550
1551
1552 resp, err := http.Get(manifestURL)
1553 if err != nil {
1554 t.Fatalf("unexpected error getting manifest: %v", err)
1555 }
1556 defer resp.Body.Close()
1557
1558 checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
1559 checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
1560
1561 tagsURL, err := env.builder.BuildTagsURL(imageName)
1562 if err != nil {
1563 t.Fatalf("unexpected error building tags url: %v", err)
1564 }
1565
1566 resp, err = http.Get(tagsURL)
1567 if err != nil {
1568 t.Fatalf("unexpected error getting unknown tags: %v", err)
1569 }
1570 defer resp.Body.Close()
1571
1572
1573 checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
1574 checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
1575
1576
1577
1578 manifest := &schema2.Manifest{
1579 Versioned: manifest.Versioned{
1580 SchemaVersion: 2,
1581 MediaType: schema2.MediaTypeManifest,
1582 },
1583 Config: distribution.Descriptor{
1584 Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
1585 Size: 3253,
1586 MediaType: schema2.MediaTypeImageConfig,
1587 },
1588 Layers: []distribution.Descriptor{
1589 {
1590 Digest: "sha256:463434349086340864309863409683460843608348608934092322395278926a",
1591 Size: 6323,
1592 MediaType: schema2.MediaTypeLayer,
1593 },
1594 {
1595 Digest: "sha256:630923423623623423352523525237238023652897356239852383652aaaaaaa",
1596 Size: 6863,
1597 MediaType: schema2.MediaTypeLayer,
1598 },
1599 },
1600 }
1601
1602 resp = putManifest(t, "putting missing config manifest", manifestURL, schema2.MediaTypeManifest, manifest)
1603 defer resp.Body.Close()
1604 checkResponse(t, "putting missing config manifest", resp, http.StatusBadRequest)
1605 _, p, counts := checkBodyHasErrorCodes(t, "putting missing config manifest", resp, v2.ErrorCodeManifestBlobUnknown)
1606
1607 expectedCounts := map[errcode.ErrorCode]int{
1608 v2.ErrorCodeManifestBlobUnknown: 3,
1609 }
1610
1611 if !reflect.DeepEqual(counts, expectedCounts) {
1612 t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1613 }
1614
1615
1616 sampleConfig := []byte(`{
1617 "architecture": "amd64",
1618 "history": [
1619 {
1620 "created": "2015-10-31T22:22:54.690851953Z",
1621 "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
1622 },
1623 {
1624 "created": "2015-10-31T22:22:55.613815829Z",
1625 "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
1626 }
1627 ],
1628 "rootfs": {
1629 "diff_ids": [
1630 "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
1631 "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
1632 ],
1633 "type": "layers"
1634 }
1635 }`)
1636 sampleConfigDigest := digest.FromBytes(sampleConfig)
1637
1638 uploadURLBase, _ := startPushLayer(t, env, imageName)
1639 pushLayer(t, env.builder, imageName, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig))
1640 manifest.Config.Digest = sampleConfigDigest
1641 manifest.Config.Size = int64(len(sampleConfig))
1642
1643
1644 resp = putManifest(t, "putting missing layer manifest", manifestURL, schema2.MediaTypeManifest, manifest)
1645 defer resp.Body.Close()
1646 checkResponse(t, "putting missing layer manifest", resp, http.StatusBadRequest)
1647 _, p, counts = checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestBlobUnknown)
1648
1649 expectedCounts = map[errcode.ErrorCode]int{
1650 v2.ErrorCodeManifestBlobUnknown: 2,
1651 }
1652
1653 if !reflect.DeepEqual(counts, expectedCounts) {
1654 t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1655 }
1656
1657
1658 expectedLayers := make(map[digest.Digest]io.ReadSeeker)
1659
1660 for i := range manifest.Layers {
1661 rs, dgst, err := testutil.CreateRandomTarFile()
1662
1663 if err != nil {
1664 t.Fatalf("error creating random layer %d: %v", i, err)
1665 }
1666
1667 expectedLayers[dgst] = rs
1668 manifest.Layers[i].Digest = dgst
1669
1670 uploadURLBase, _ := startPushLayer(t, env, imageName)
1671 pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
1672 }
1673
1674
1675
1676 deserializedManifest, err := schema2.FromStruct(*manifest)
1677 if err != nil {
1678 t.Fatalf("could not create DeserializedManifest: %v", err)
1679 }
1680 _, canonical, err := deserializedManifest.Payload()
1681 if err != nil {
1682 t.Fatalf("could not get manifest payload: %v", err)
1683 }
1684 dgst := digest.FromBytes(canonical)
1685 args.dgst = dgst
1686 args.manifest = deserializedManifest
1687
1688 digestRef, _ := reference.WithDigest(imageName, dgst)
1689 manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
1690 checkErr(t, err, "building manifest url")
1691
1692 resp = putManifest(t, "putting manifest no error", manifestURL, schema2.MediaTypeManifest, manifest)
1693 checkResponse(t, "putting manifest no error", resp, http.StatusCreated)
1694 checkHeaders(t, resp, http.Header{
1695 "Location": []string{manifestDigestURL},
1696 "Docker-Content-Digest": []string{dgst.String()},
1697 })
1698
1699
1700
1701 resp = putManifest(t, "putting manifest by digest", manifestDigestURL, schema2.MediaTypeManifest, manifest)
1702 checkResponse(t, "putting manifest by digest", resp, http.StatusCreated)
1703 checkHeaders(t, resp, http.Header{
1704 "Location": []string{manifestDigestURL},
1705 "Docker-Content-Digest": []string{dgst.String()},
1706 })
1707
1708
1709
1710 req, err := http.NewRequest("GET", manifestURL, nil)
1711 if err != nil {
1712 t.Fatalf("Error constructing request: %s", err)
1713 }
1714 req.Header.Set("Accept", schema2.MediaTypeManifest)
1715 resp, err = http.DefaultClient.Do(req)
1716 if err != nil {
1717 t.Fatalf("unexpected error fetching manifest: %v", err)
1718 }
1719 defer resp.Body.Close()
1720
1721 checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1722 checkHeaders(t, resp, http.Header{
1723 "Docker-Content-Digest": []string{dgst.String()},
1724 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
1725 })
1726
1727 var fetchedManifest schema2.DeserializedManifest
1728 dec := json.NewDecoder(resp.Body)
1729
1730 if err := dec.Decode(&fetchedManifest); err != nil {
1731 t.Fatalf("error decoding fetched manifest: %v", err)
1732 }
1733
1734 _, fetchedCanonical, err := fetchedManifest.Payload()
1735 if err != nil {
1736 t.Fatalf("error getting manifest payload: %v", err)
1737 }
1738
1739 if !bytes.Equal(fetchedCanonical, canonical) {
1740 t.Fatalf("manifests do not match")
1741 }
1742
1743
1744
1745 req, err = http.NewRequest("GET", manifestDigestURL, nil)
1746 if err != nil {
1747 t.Fatalf("Error constructing request: %s", err)
1748 }
1749 req.Header.Set("Accept", schema2.MediaTypeManifest)
1750 resp, err = http.DefaultClient.Do(req)
1751 checkErr(t, err, "fetching manifest by digest")
1752 defer resp.Body.Close()
1753
1754 checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1755 checkHeaders(t, resp, http.Header{
1756 "Docker-Content-Digest": []string{dgst.String()},
1757 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
1758 })
1759
1760 var fetchedManifestByDigest schema2.DeserializedManifest
1761 dec = json.NewDecoder(resp.Body)
1762 if err := dec.Decode(&fetchedManifestByDigest); err != nil {
1763 t.Fatalf("error decoding fetched manifest: %v", err)
1764 }
1765
1766 _, fetchedCanonical, err = fetchedManifest.Payload()
1767 if err != nil {
1768 t.Fatalf("error getting manifest payload: %v", err)
1769 }
1770
1771 if !bytes.Equal(fetchedCanonical, canonical) {
1772 t.Fatalf("manifests do not match")
1773 }
1774
1775
1776 etag := resp.Header.Get("Etag")
1777 req, err = http.NewRequest("GET", manifestURL, nil)
1778 if err != nil {
1779 t.Fatalf("Error constructing request: %s", err)
1780 }
1781 req.Header.Set("If-None-Match", etag)
1782 resp, err = http.DefaultClient.Do(req)
1783 if err != nil {
1784 t.Fatalf("Error constructing request: %s", err)
1785 }
1786
1787 checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
1788
1789
1790 req, err = http.NewRequest("GET", manifestDigestURL, nil)
1791 if err != nil {
1792 t.Fatalf("Error constructing request: %s", err)
1793 }
1794 req.Header.Set("If-None-Match", etag)
1795 resp, err = http.DefaultClient.Do(req)
1796 if err != nil {
1797 t.Fatalf("Error constructing request: %s", err)
1798 }
1799
1800 checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
1801
1802
1803 resp, err = http.Get(tagsURL)
1804 if err != nil {
1805 t.Fatalf("unexpected error getting unknown tags: %v", err)
1806 }
1807 defer resp.Body.Close()
1808
1809 checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
1810 dec = json.NewDecoder(resp.Body)
1811
1812 var tagsResponse tagsAPIResponse
1813
1814 if err := dec.Decode(&tagsResponse); err != nil {
1815 t.Fatalf("unexpected error decoding error response: %v", err)
1816 }
1817
1818 if tagsResponse.Name != imageName.Name() {
1819 t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
1820 }
1821
1822 if len(tagsResponse.Tags) != 1 {
1823 t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
1824 }
1825
1826 if tagsResponse.Tags[0] != tag {
1827 t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
1828 }
1829
1830
1831
1832 resp, err = http.Get(manifestURL)
1833 if err != nil {
1834 t.Fatalf("unexpected error fetching manifest as schema1: %v", err)
1835 }
1836 defer resp.Body.Close()
1837
1838 manifestBytes, err := ioutil.ReadAll(resp.Body)
1839 if err != nil {
1840 t.Fatalf("error reading response body: %v", err)
1841 }
1842
1843 checkResponse(t, "fetching uploaded manifest as schema1", resp, http.StatusOK)
1844
1845 m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes)
1846 if err != nil {
1847 t.Fatalf("unexpected error unmarshalling manifest: %v", err)
1848 }
1849
1850 fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest)
1851 if !ok {
1852 t.Fatalf("expecting schema1 manifest")
1853 }
1854
1855 checkHeaders(t, resp, http.Header{
1856 "Docker-Content-Digest": []string{desc.Digest.String()},
1857 "ETag": []string{fmt.Sprintf(`"%s"`, desc.Digest)},
1858 })
1859
1860 if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
1861 t.Fatal("wrong schema version")
1862 }
1863 if fetchedSchema1Manifest.Architecture != "amd64" {
1864 t.Fatal("wrong architecture")
1865 }
1866 if fetchedSchema1Manifest.Name != imageName.Name() {
1867 t.Fatal("wrong image name")
1868 }
1869 if fetchedSchema1Manifest.Tag != tag {
1870 t.Fatal("wrong tag")
1871 }
1872 if len(fetchedSchema1Manifest.FSLayers) != 2 {
1873 t.Fatal("wrong number of FSLayers")
1874 }
1875 for i := range manifest.Layers {
1876 if fetchedSchema1Manifest.FSLayers[i].BlobSum != manifest.Layers[len(manifest.Layers)-i-1].Digest {
1877 t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
1878 }
1879 }
1880 if len(fetchedSchema1Manifest.History) != 2 {
1881 t.Fatal("wrong number of History entries")
1882 }
1883
1884
1885
1886
1887 return args
1888 }
1889
1890 func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs) {
1891 imageName := args.imageName
1892 tag := "manifestlisttag"
1893
1894 tagRef, _ := reference.WithTag(imageName, tag)
1895 manifestURL, err := env.builder.BuildManifestURL(tagRef)
1896 if err != nil {
1897 t.Fatalf("unexpected error getting manifest url: %v", err)
1898 }
1899
1900
1901
1902 manifestList := &manifestlist.ManifestList{
1903 Versioned: manifest.Versioned{
1904 SchemaVersion: 2,
1905 MediaType: manifestlist.MediaTypeManifestList,
1906 },
1907 Manifests: []manifestlist.ManifestDescriptor{
1908 {
1909 Descriptor: distribution.Descriptor{
1910 Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
1911 Size: 3253,
1912 MediaType: schema2.MediaTypeManifest,
1913 },
1914 Platform: manifestlist.PlatformSpec{
1915 Architecture: "amd64",
1916 OS: "linux",
1917 },
1918 },
1919 },
1920 }
1921
1922 resp := putManifest(t, "putting missing manifest manifestlist", manifestURL, manifestlist.MediaTypeManifestList, manifestList)
1923 defer resp.Body.Close()
1924 checkResponse(t, "putting missing manifest manifestlist", resp, http.StatusBadRequest)
1925 _, p, counts := checkBodyHasErrorCodes(t, "putting missing manifest manifestlist", resp, v2.ErrorCodeManifestBlobUnknown)
1926
1927 expectedCounts := map[errcode.ErrorCode]int{
1928 v2.ErrorCodeManifestBlobUnknown: 1,
1929 }
1930
1931 if !reflect.DeepEqual(counts, expectedCounts) {
1932 t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1933 }
1934
1935
1936
1937 manifestList.Manifests[0].Digest = args.dgst
1938 deserializedManifestList, err := manifestlist.FromDescriptors(manifestList.Manifests)
1939 if err != nil {
1940 t.Fatalf("could not create DeserializedManifestList: %v", err)
1941 }
1942 _, canonical, err := deserializedManifestList.Payload()
1943 if err != nil {
1944 t.Fatalf("could not get manifest list payload: %v", err)
1945 }
1946 dgst := digest.FromBytes(canonical)
1947
1948 digestRef, _ := reference.WithDigest(imageName, dgst)
1949 manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
1950 checkErr(t, err, "building manifest url")
1951
1952 resp = putManifest(t, "putting manifest list no error", manifestURL, manifestlist.MediaTypeManifestList, deserializedManifestList)
1953 checkResponse(t, "putting manifest list no error", resp, http.StatusCreated)
1954 checkHeaders(t, resp, http.Header{
1955 "Location": []string{manifestDigestURL},
1956 "Docker-Content-Digest": []string{dgst.String()},
1957 })
1958
1959
1960
1961 resp = putManifest(t, "putting manifest list by digest", manifestDigestURL, manifestlist.MediaTypeManifestList, deserializedManifestList)
1962 checkResponse(t, "putting manifest list by digest", resp, http.StatusCreated)
1963 checkHeaders(t, resp, http.Header{
1964 "Location": []string{manifestDigestURL},
1965 "Docker-Content-Digest": []string{dgst.String()},
1966 })
1967
1968
1969
1970 req, err := http.NewRequest("GET", manifestURL, nil)
1971 if err != nil {
1972 t.Fatalf("Error constructing request: %s", err)
1973 }
1974
1975 req.Header.Set("Accept", fmt.Sprintf(` %s ; q=0.8 , %s ; q=0.5 `, manifestlist.MediaTypeManifestList, schema1.MediaTypeSignedManifest))
1976 req.Header.Add("Accept", schema2.MediaTypeManifest)
1977 resp, err = http.DefaultClient.Do(req)
1978 if err != nil {
1979 t.Fatalf("unexpected error fetching manifest list: %v", err)
1980 }
1981 defer resp.Body.Close()
1982
1983 checkResponse(t, "fetching uploaded manifest list", resp, http.StatusOK)
1984 checkHeaders(t, resp, http.Header{
1985 "Docker-Content-Digest": []string{dgst.String()},
1986 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
1987 })
1988
1989 var fetchedManifestList manifestlist.DeserializedManifestList
1990 dec := json.NewDecoder(resp.Body)
1991
1992 if err := dec.Decode(&fetchedManifestList); err != nil {
1993 t.Fatalf("error decoding fetched manifest list: %v", err)
1994 }
1995
1996 _, fetchedCanonical, err := fetchedManifestList.Payload()
1997 if err != nil {
1998 t.Fatalf("error getting manifest list payload: %v", err)
1999 }
2000
2001 if !bytes.Equal(fetchedCanonical, canonical) {
2002 t.Fatalf("manifest lists do not match")
2003 }
2004
2005
2006
2007 req, err = http.NewRequest("GET", manifestDigestURL, nil)
2008 if err != nil {
2009 t.Fatalf("Error constructing request: %s", err)
2010 }
2011 req.Header.Set("Accept", manifestlist.MediaTypeManifestList)
2012 resp, err = http.DefaultClient.Do(req)
2013 checkErr(t, err, "fetching manifest list by digest")
2014 defer resp.Body.Close()
2015
2016 checkResponse(t, "fetching uploaded manifest list", resp, http.StatusOK)
2017 checkHeaders(t, resp, http.Header{
2018 "Docker-Content-Digest": []string{dgst.String()},
2019 "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
2020 })
2021
2022 var fetchedManifestListByDigest manifestlist.DeserializedManifestList
2023 dec = json.NewDecoder(resp.Body)
2024 if err := dec.Decode(&fetchedManifestListByDigest); err != nil {
2025 t.Fatalf("error decoding fetched manifest: %v", err)
2026 }
2027
2028 _, fetchedCanonical, err = fetchedManifestListByDigest.Payload()
2029 if err != nil {
2030 t.Fatalf("error getting manifest list payload: %v", err)
2031 }
2032
2033 if !bytes.Equal(fetchedCanonical, canonical) {
2034 t.Fatalf("manifests do not match")
2035 }
2036
2037
2038 etag := resp.Header.Get("Etag")
2039 req, err = http.NewRequest("GET", manifestURL, nil)
2040 if err != nil {
2041 t.Fatalf("Error constructing request: %s", err)
2042 }
2043 req.Header.Set("If-None-Match", etag)
2044 resp, err = http.DefaultClient.Do(req)
2045 if err != nil {
2046 t.Fatalf("Error constructing request: %s", err)
2047 }
2048
2049 checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
2050
2051
2052 req, err = http.NewRequest("GET", manifestDigestURL, nil)
2053 if err != nil {
2054 t.Fatalf("Error constructing request: %s", err)
2055 }
2056 req.Header.Set("If-None-Match", etag)
2057 resp, err = http.DefaultClient.Do(req)
2058 if err != nil {
2059 t.Fatalf("Error constructing request: %s", err)
2060 }
2061
2062 checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
2063
2064
2065
2066 resp, err = http.Get(manifestURL)
2067 if err != nil {
2068 t.Fatalf("unexpected error fetching manifest list as schema1: %v", err)
2069 }
2070 defer resp.Body.Close()
2071
2072 manifestBytes, err := ioutil.ReadAll(resp.Body)
2073 if err != nil {
2074 t.Fatalf("error reading response body: %v", err)
2075 }
2076
2077 checkResponse(t, "fetching uploaded manifest list as schema1", resp, http.StatusOK)
2078
2079 m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes)
2080 if err != nil {
2081 t.Fatalf("unexpected error unmarshalling manifest: %v", err)
2082 }
2083
2084 fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest)
2085 if !ok {
2086 t.Fatalf("expecting schema1 manifest")
2087 }
2088
2089 checkHeaders(t, resp, http.Header{
2090 "Docker-Content-Digest": []string{desc.Digest.String()},
2091 "ETag": []string{fmt.Sprintf(`"%s"`, desc.Digest)},
2092 })
2093
2094 if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
2095 t.Fatal("wrong schema version")
2096 }
2097 if fetchedSchema1Manifest.Architecture != "amd64" {
2098 t.Fatal("wrong architecture")
2099 }
2100 if fetchedSchema1Manifest.Name != imageName.Name() {
2101 t.Fatal("wrong image name")
2102 }
2103 if fetchedSchema1Manifest.Tag != tag {
2104 t.Fatal("wrong tag")
2105 }
2106 if len(fetchedSchema1Manifest.FSLayers) != 2 {
2107 t.Fatal("wrong number of FSLayers")
2108 }
2109 layers := args.manifest.(*schema2.DeserializedManifest).Layers
2110 for i := range layers {
2111 if fetchedSchema1Manifest.FSLayers[i].BlobSum != layers[len(layers)-i-1].Digest {
2112 t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
2113 }
2114 }
2115 if len(fetchedSchema1Manifest.History) != 2 {
2116 t.Fatal("wrong number of History entries")
2117 }
2118
2119
2120
2121 }
2122
2123 func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
2124 imageName := args.imageName
2125 dgst := args.dgst
2126 manifest := args.manifest
2127
2128 ref, _ := reference.WithDigest(imageName, dgst)
2129 manifestDigestURL, _ := env.builder.BuildManifestURL(ref)
2130
2131
2132 resp, err := httpDelete(manifestDigestURL)
2133 checkErr(t, err, "deleting manifest by digest")
2134
2135 checkResponse(t, "deleting manifest", resp, http.StatusAccepted)
2136 checkHeaders(t, resp, http.Header{
2137 "Content-Length": []string{"0"},
2138 })
2139
2140
2141
2142 resp, err = http.Get(manifestDigestURL)
2143 checkErr(t, err, "fetching deleted manifest by digest")
2144 defer resp.Body.Close()
2145
2146 checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
2147
2148
2149
2150 resp, err = httpDelete(manifestDigestURL)
2151 checkErr(t, err, "re-deleting manifest by digest")
2152
2153 checkResponse(t, "re-deleting manifest", resp, http.StatusNotFound)
2154
2155
2156
2157 resp = putManifest(t, "putting manifest", manifestDigestURL, args.mediaType, manifest)
2158 checkResponse(t, "putting manifest", resp, http.StatusCreated)
2159 checkHeaders(t, resp, http.Header{
2160 "Location": []string{manifestDigestURL},
2161 "Docker-Content-Digest": []string{dgst.String()},
2162 })
2163
2164
2165
2166 resp, err = http.Get(manifestDigestURL)
2167 checkErr(t, err, "fetching re-uploaded manifest by digest")
2168 defer resp.Body.Close()
2169
2170 checkResponse(t, "fetching re-uploaded manifest", resp, http.StatusOK)
2171 checkHeaders(t, resp, http.Header{
2172 "Docker-Content-Digest": []string{dgst.String()},
2173 })
2174
2175
2176
2177 unknownDigest := digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
2178 unknownRef, _ := reference.WithDigest(imageName, unknownDigest)
2179 unknownManifestDigestURL, err := env.builder.BuildManifestURL(unknownRef)
2180 checkErr(t, err, "building unknown manifest url")
2181
2182 resp, err = httpDelete(unknownManifestDigestURL)
2183 checkErr(t, err, "delting unknown manifest by digest")
2184 checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
2185
2186
2187
2188 tag := "atag"
2189 tagRef, _ := reference.WithTag(imageName, tag)
2190 manifestTagURL, _ := env.builder.BuildManifestURL(tagRef)
2191 resp = putManifest(t, "putting manifest by tag", manifestTagURL, args.mediaType, manifest)
2192 checkResponse(t, "putting manifest by tag", resp, http.StatusCreated)
2193 checkHeaders(t, resp, http.Header{
2194 "Location": []string{manifestDigestURL},
2195 "Docker-Content-Digest": []string{dgst.String()},
2196 })
2197
2198 tagsURL, err := env.builder.BuildTagsURL(imageName)
2199 if err != nil {
2200 t.Fatalf("unexpected error building tags url: %v", err)
2201 }
2202
2203
2204 resp, err = http.Get(tagsURL)
2205 if err != nil {
2206 t.Fatalf("unexpected error getting unknown tags: %v", err)
2207 }
2208 defer resp.Body.Close()
2209
2210 dec := json.NewDecoder(resp.Body)
2211 var tagsResponse tagsAPIResponse
2212 if err := dec.Decode(&tagsResponse); err != nil {
2213 t.Fatalf("unexpected error decoding error response: %v", err)
2214 }
2215
2216 if tagsResponse.Name != imageName.Name() {
2217 t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
2218 }
2219
2220 if len(tagsResponse.Tags) != 1 {
2221 t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
2222 }
2223
2224 if tagsResponse.Tags[0] != tag {
2225 t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
2226 }
2227
2228
2229
2230 resp, err = httpDelete(manifestDigestURL)
2231 checkErr(t, err, "deleting manifest by digest")
2232
2233 checkResponse(t, "deleting manifest with tag", resp, http.StatusAccepted)
2234 checkHeaders(t, resp, http.Header{
2235 "Content-Length": []string{"0"},
2236 })
2237
2238
2239 resp, err = http.Get(tagsURL)
2240 if err != nil {
2241 t.Fatalf("unexpected error getting unknown tags: %v", err)
2242 }
2243 defer resp.Body.Close()
2244
2245 dec = json.NewDecoder(resp.Body)
2246 if err := dec.Decode(&tagsResponse); err != nil {
2247 t.Fatalf("unexpected error decoding error response: %v", err)
2248 }
2249
2250 if tagsResponse.Name != imageName.Name() {
2251 t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
2252 }
2253
2254 if len(tagsResponse.Tags) != 0 {
2255 t.Fatalf("expected 0 tags in response: %v", tagsResponse.Tags)
2256 }
2257
2258 }
2259
2260 type testEnv struct {
2261 pk libtrust.PrivateKey
2262 ctx context.Context
2263 config configuration.Configuration
2264 app *App
2265 server *httptest.Server
2266 builder *v2.URLBuilder
2267 }
2268
2269 func newTestEnvMirror(t *testing.T, deleteEnabled bool) *testEnv {
2270 config := configuration.Configuration{
2271 Storage: configuration.Storage{
2272 "testdriver": configuration.Parameters{},
2273 "delete": configuration.Parameters{"enabled": deleteEnabled},
2274 "maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
2275 "enabled": false,
2276 }},
2277 },
2278 Proxy: configuration.Proxy{
2279 RemoteURL: "http://example.com",
2280 },
2281 Catalog: configuration.Catalog{
2282 MaxEntries: 5,
2283 },
2284 }
2285 config.Compatibility.Schema1.Enabled = true
2286
2287 return newTestEnvWithConfig(t, &config)
2288
2289 }
2290
2291 func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv {
2292 config := configuration.Configuration{
2293 Storage: configuration.Storage{
2294 "testdriver": configuration.Parameters{},
2295 "delete": configuration.Parameters{"enabled": deleteEnabled},
2296 "maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
2297 "enabled": false,
2298 }},
2299 },
2300 Catalog: configuration.Catalog{
2301 MaxEntries: 5,
2302 },
2303 }
2304
2305 config.Compatibility.Schema1.Enabled = true
2306 config.HTTP.Headers = headerConfig
2307
2308 return newTestEnvWithConfig(t, &config)
2309 }
2310
2311 func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv {
2312 ctx := context.Background()
2313
2314 app := NewApp(ctx, config)
2315 server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
2316 builder, err := v2.NewURLBuilderFromString(server.URL+config.HTTP.Prefix, false)
2317
2318 if err != nil {
2319 t.Fatalf("error creating url builder: %v", err)
2320 }
2321
2322 pk, err := libtrust.GenerateECP256PrivateKey()
2323 if err != nil {
2324 t.Fatalf("unexpected error generating private key: %v", err)
2325 }
2326
2327 return &testEnv{
2328 pk: pk,
2329 ctx: ctx,
2330 config: *config,
2331 app: app,
2332 server: server,
2333 builder: builder,
2334 }
2335 }
2336
2337 func (t *testEnv) Shutdown() {
2338 t.server.CloseClientConnections()
2339 t.server.Close()
2340 }
2341
2342 func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *http.Response {
2343 var body []byte
2344
2345 switch m := v.(type) {
2346 case *schema1.SignedManifest:
2347 _, pl, err := m.Payload()
2348 if err != nil {
2349 t.Fatalf("error getting payload: %v", err)
2350 }
2351 body = pl
2352 case *manifestlist.DeserializedManifestList:
2353 _, pl, err := m.Payload()
2354 if err != nil {
2355 t.Fatalf("error getting payload: %v", err)
2356 }
2357 body = pl
2358 default:
2359 var err error
2360 body, err = json.MarshalIndent(v, "", " ")
2361 if err != nil {
2362 t.Fatalf("unexpected error marshaling %v: %v", v, err)
2363 }
2364 }
2365
2366 req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
2367 if err != nil {
2368 t.Fatalf("error creating request for %s: %v", msg, err)
2369 }
2370
2371 if contentType != "" {
2372 req.Header.Set("Content-Type", contentType)
2373 }
2374
2375 resp, err := http.DefaultClient.Do(req)
2376 if err != nil {
2377 t.Fatalf("error doing put request while %s: %v", msg, err)
2378 }
2379
2380 return resp
2381 }
2382
2383 func startPushLayer(t *testing.T, env *testEnv, name reference.Named) (location string, uuid string) {
2384 layerUploadURL, err := env.builder.BuildBlobUploadURL(name)
2385 if err != nil {
2386 t.Fatalf("unexpected error building layer upload url: %v", err)
2387 }
2388
2389 u, err := url.Parse(layerUploadURL)
2390 if err != nil {
2391 t.Fatalf("error parsing layer upload URL: %v", err)
2392 }
2393
2394 base, err := url.Parse(env.server.URL)
2395 if err != nil {
2396 t.Fatalf("error parsing server URL: %v", err)
2397 }
2398
2399 layerUploadURL = base.ResolveReference(u).String()
2400 resp, err := http.Post(layerUploadURL, "", nil)
2401 if err != nil {
2402 t.Fatalf("unexpected error starting layer push: %v", err)
2403 }
2404
2405 defer resp.Body.Close()
2406
2407 checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name.String()), resp, http.StatusAccepted)
2408
2409 u, err = url.Parse(resp.Header.Get("Location"))
2410 if err != nil {
2411 t.Fatalf("error parsing location header: %v", err)
2412 }
2413
2414 uuid = path.Base(u.Path)
2415 checkHeaders(t, resp, http.Header{
2416 "Location": []string{"*"},
2417 "Content-Length": []string{"0"},
2418 "Docker-Upload-UUID": []string{uuid},
2419 })
2420
2421 return resp.Header.Get("Location"), uuid
2422 }
2423
2424
2425
2426 func doPushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst digest.Digest, uploadURLBase string, body io.Reader) (*http.Response, error) {
2427 u, err := url.Parse(uploadURLBase)
2428 if err != nil {
2429 t.Fatalf("unexpected error parsing pushLayer url: %v", err)
2430 }
2431
2432 u.RawQuery = url.Values{
2433 "_state": u.Query()["_state"],
2434 "digest": []string{dgst.String()},
2435 }.Encode()
2436
2437 uploadURL := u.String()
2438
2439
2440 req, err := http.NewRequest("PUT", uploadURL, body)
2441 if err != nil {
2442 t.Fatalf("unexpected error creating new request: %v", err)
2443 }
2444
2445 return http.DefaultClient.Do(req)
2446 }
2447
2448
2449 func pushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst digest.Digest, uploadURLBase string, body io.Reader) string {
2450 digester := digest.Canonical.Digester()
2451
2452 resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, io.TeeReader(body, digester.Hash()))
2453 if err != nil {
2454 t.Fatalf("unexpected error doing push layer request: %v", err)
2455 }
2456 defer resp.Body.Close()
2457
2458 checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
2459
2460 if err != nil {
2461 t.Fatalf("error generating sha256 digest of body")
2462 }
2463
2464 sha256Dgst := digester.Digest()
2465
2466 ref, _ := reference.WithDigest(name, sha256Dgst)
2467 expectedLayerURL, err := ub.BuildBlobURL(ref)
2468 if err != nil {
2469 t.Fatalf("error building expected layer url: %v", err)
2470 }
2471
2472 checkHeaders(t, resp, http.Header{
2473 "Location": []string{expectedLayerURL},
2474 "Content-Length": []string{"0"},
2475 "Docker-Content-Digest": []string{sha256Dgst.String()},
2476 })
2477
2478 return resp.Header.Get("Location")
2479 }
2480
2481 func finishUpload(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLBase string, dgst digest.Digest) string {
2482 resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, nil)
2483 if err != nil {
2484 t.Fatalf("unexpected error doing push layer request: %v", err)
2485 }
2486 defer resp.Body.Close()
2487
2488 checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
2489
2490 ref, _ := reference.WithDigest(name, dgst)
2491 expectedLayerURL, err := ub.BuildBlobURL(ref)
2492 if err != nil {
2493 t.Fatalf("error building expected layer url: %v", err)
2494 }
2495
2496 checkHeaders(t, resp, http.Header{
2497 "Location": []string{expectedLayerURL},
2498 "Content-Length": []string{"0"},
2499 "Docker-Content-Digest": []string{dgst.String()},
2500 })
2501
2502 return resp.Header.Get("Location")
2503 }
2504
2505 func doPushChunk(t *testing.T, uploadURLBase string, body io.Reader) (*http.Response, digest.Digest, error) {
2506 u, err := url.Parse(uploadURLBase)
2507 if err != nil {
2508 t.Fatalf("unexpected error parsing pushLayer url: %v", err)
2509 }
2510
2511 u.RawQuery = url.Values{
2512 "_state": u.Query()["_state"],
2513 }.Encode()
2514
2515 uploadURL := u.String()
2516
2517 digester := digest.Canonical.Digester()
2518
2519 req, err := http.NewRequest("PATCH", uploadURL, io.TeeReader(body, digester.Hash()))
2520 if err != nil {
2521 t.Fatalf("unexpected error creating new request: %v", err)
2522 }
2523 req.Header.Set("Content-Type", "application/octet-stream")
2524
2525 resp, err := http.DefaultClient.Do(req)
2526
2527 return resp, digester.Digest(), err
2528 }
2529
2530 func pushChunk(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLBase string, body io.Reader, length int64) (string, digest.Digest) {
2531 resp, dgst, err := doPushChunk(t, uploadURLBase, body)
2532 if err != nil {
2533 t.Fatalf("unexpected error doing push layer request: %v", err)
2534 }
2535 defer resp.Body.Close()
2536
2537 checkResponse(t, "putting chunk", resp, http.StatusAccepted)
2538
2539 if err != nil {
2540 t.Fatalf("error generating sha256 digest of body")
2541 }
2542
2543 checkHeaders(t, resp, http.Header{
2544 "Range": []string{fmt.Sprintf("0-%d", length-1)},
2545 "Content-Length": []string{"0"},
2546 })
2547
2548 return resp.Header.Get("Location"), dgst
2549 }
2550
2551 func checkResponse(t *testing.T, msg string, resp *http.Response, expectedStatus int) {
2552 if resp.StatusCode != expectedStatus {
2553 t.Logf("unexpected status %s: %v != %v", msg, resp.StatusCode, expectedStatus)
2554 maybeDumpResponse(t, resp)
2555 t.FailNow()
2556 }
2557
2558
2559
2560
2561 if resp.StatusCode != 405 && !reflect.DeepEqual(resp.Header["X-Content-Type-Options"], []string{"nosniff"}) {
2562 t.Logf("missing or incorrect header X-Content-Type-Options %s", msg)
2563 maybeDumpResponse(t, resp)
2564
2565 t.FailNow()
2566 }
2567 }
2568
2569
2570
2571
2572 func checkBodyHasErrorCodes(t *testing.T, msg string, resp *http.Response, errorCodes ...errcode.ErrorCode) (errcode.Errors, []byte, map[errcode.ErrorCode]int) {
2573 p, err := ioutil.ReadAll(resp.Body)
2574 if err != nil {
2575 t.Fatalf("unexpected error reading body %s: %v", msg, err)
2576 }
2577
2578 var errs errcode.Errors
2579 if err := json.Unmarshal(p, &errs); err != nil {
2580 t.Fatalf("unexpected error decoding error response: %v", err)
2581 }
2582
2583 if len(errs) == 0 {
2584 t.Fatalf("expected errors in response")
2585 }
2586
2587
2588
2589
2590
2591
2592
2593
2594 expected := map[errcode.ErrorCode]struct{}{}
2595 counts := map[errcode.ErrorCode]int{}
2596
2597
2598 for _, code := range errorCodes {
2599 expected[code] = struct{}{}
2600 counts[code] = 0
2601 }
2602
2603 for _, e := range errs {
2604 err, ok := e.(errcode.ErrorCoder)
2605 if !ok {
2606 t.Fatalf("not an ErrorCoder: %#v", e)
2607 }
2608 if _, ok := expected[err.ErrorCode()]; !ok {
2609 t.Fatalf("unexpected error code %v encountered during %s: %s ", err.ErrorCode(), msg, string(p))
2610 }
2611 counts[err.ErrorCode()]++
2612 }
2613
2614
2615 for code := range expected {
2616 if counts[code] == 0 {
2617 t.Fatalf("expected error code %v not encountered during %s: %s", code, msg, string(p))
2618 }
2619 }
2620
2621 return errs, p, counts
2622 }
2623
2624 func maybeDumpResponse(t *testing.T, resp *http.Response) {
2625 if d, err := httputil.DumpResponse(resp, true); err != nil {
2626 t.Logf("error dumping response: %v", err)
2627 } else {
2628 t.Logf("response:\n%s", string(d))
2629 }
2630 }
2631
2632
2633
2634
2635 func checkHeaders(t *testing.T, resp *http.Response, headers http.Header) {
2636 for k, vs := range headers {
2637 if resp.Header.Get(k) == "" {
2638 t.Fatalf("response missing header %q", k)
2639 }
2640
2641 for _, v := range vs {
2642 if v == "*" {
2643
2644 if len(resp.Header[http.CanonicalHeaderKey(k)]) > 0 {
2645 continue
2646 }
2647 }
2648
2649 for _, hv := range resp.Header[http.CanonicalHeaderKey(k)] {
2650 if hv != v {
2651 t.Fatalf("%+v %v header value not matched in response: %q != %q", resp.Header, k, hv, v)
2652 }
2653 }
2654 }
2655 }
2656 }
2657
2658 func checkErr(t *testing.T, err error, msg string) {
2659 if err != nil {
2660 t.Fatalf("unexpected error %s: %v", msg, err)
2661 }
2662 }
2663
2664 func createRepository(env *testEnv, t *testing.T, imageName string, tag string) digest.Digest {
2665 imageNameRef, err := reference.WithName(imageName)
2666 if err != nil {
2667 t.Fatalf("unable to parse reference: %v", err)
2668 }
2669
2670 unsignedManifest := &schema1.Manifest{
2671 Versioned: manifest.Versioned{
2672 SchemaVersion: 1,
2673 },
2674 Name: imageName,
2675 Tag: tag,
2676 FSLayers: []schema1.FSLayer{
2677 {
2678 BlobSum: "asdf",
2679 },
2680 },
2681 History: []schema1.History{
2682 {
2683 V1Compatibility: "",
2684 },
2685 },
2686 }
2687
2688
2689 expectedLayers := make(map[digest.Digest]io.ReadSeeker)
2690
2691 for i := range unsignedManifest.FSLayers {
2692 rs, dgst, err := testutil.CreateRandomTarFile()
2693 if err != nil {
2694 t.Fatalf("error creating random layer %d: %v", i, err)
2695 }
2696
2697 expectedLayers[dgst] = rs
2698 unsignedManifest.FSLayers[i].BlobSum = dgst
2699 uploadURLBase, _ := startPushLayer(t, env, imageNameRef)
2700 pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs)
2701 }
2702
2703 signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
2704 if err != nil {
2705 t.Fatalf("unexpected error signing manifest: %v", err)
2706 }
2707
2708 dgst := digest.FromBytes(signedManifest.Canonical)
2709
2710
2711 tagRef, _ := reference.WithTag(imageNameRef, tag)
2712 manifestDigestURL, err := env.builder.BuildManifestURL(tagRef)
2713 checkErr(t, err, "building manifest url")
2714
2715 digestRef, _ := reference.WithDigest(imageNameRef, dgst)
2716 location, err := env.builder.BuildManifestURL(digestRef)
2717 checkErr(t, err, "building location URL")
2718
2719 resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
2720 checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
2721 checkHeaders(t, resp, http.Header{
2722 "Location": []string{location},
2723 "Docker-Content-Digest": []string{dgst.String()},
2724 })
2725 return dgst
2726 }
2727
2728
2729
2730 func TestRegistryAsCacheMutationAPIs(t *testing.T) {
2731 deleteEnabled := true
2732 env := newTestEnvMirror(t, deleteEnabled)
2733 defer env.Shutdown()
2734
2735 imageName, _ := reference.WithName("foo/bar")
2736 tag := "latest"
2737 tagRef, _ := reference.WithTag(imageName, tag)
2738 manifestURL, err := env.builder.BuildManifestURL(tagRef)
2739 if err != nil {
2740 t.Fatalf("unexpected error building base url: %v", err)
2741 }
2742
2743
2744 m := &schema1.Manifest{
2745 Versioned: manifest.Versioned{
2746 SchemaVersion: 1,
2747 },
2748 Name: imageName.Name(),
2749 Tag: tag,
2750 FSLayers: []schema1.FSLayer{},
2751 History: []schema1.History{},
2752 }
2753
2754 sm, err := schema1.Sign(m, env.pk)
2755 if err != nil {
2756 t.Fatalf("error signing manifest: %v", err)
2757 }
2758
2759 resp := putManifest(t, "putting unsigned manifest", manifestURL, "", sm)
2760 checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2761
2762
2763 resp, _ = httpDelete(manifestURL)
2764 checkResponse(t, "deleting signed manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2765
2766
2767 layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
2768 if err != nil {
2769 t.Fatalf("unexpected error building layer upload url: %v", err)
2770 }
2771
2772 resp, err = http.Post(layerUploadURL, "", nil)
2773 if err != nil {
2774 t.Fatalf("unexpected error starting layer push: %v", err)
2775 }
2776 defer resp.Body.Close()
2777
2778 checkResponse(t, fmt.Sprintf("starting layer push to cache %v", imageName), resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2779
2780
2781 ref, _ := reference.WithDigest(imageName, digestSha256EmptyTar)
2782 blobURL, _ := env.builder.BuildBlobURL(ref)
2783 resp, _ = httpDelete(blobURL)
2784 checkResponse(t, "deleting blob from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2785
2786 }
2787
2788 func TestProxyManifestGetByTag(t *testing.T) {
2789 truthConfig := configuration.Configuration{
2790 Storage: configuration.Storage{
2791 "testdriver": configuration.Parameters{},
2792 "maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
2793 "enabled": false,
2794 }},
2795 },
2796 }
2797 truthConfig.Compatibility.Schema1.Enabled = true
2798 truthConfig.HTTP.Headers = headerConfig
2799
2800 imageName, _ := reference.WithName("foo/bar")
2801 tag := "latest"
2802
2803 truthEnv := newTestEnvWithConfig(t, &truthConfig)
2804 defer truthEnv.Shutdown()
2805
2806 dgst := createRepository(truthEnv, t, imageName.Name(), tag)
2807
2808 proxyConfig := configuration.Configuration{
2809 Storage: configuration.Storage{
2810 "testdriver": configuration.Parameters{},
2811 },
2812 Proxy: configuration.Proxy{
2813 RemoteURL: truthEnv.server.URL,
2814 },
2815 }
2816 proxyConfig.Compatibility.Schema1.Enabled = true
2817 proxyConfig.HTTP.Headers = headerConfig
2818
2819 proxyEnv := newTestEnvWithConfig(t, &proxyConfig)
2820 defer proxyEnv.Shutdown()
2821
2822 digestRef, _ := reference.WithDigest(imageName, dgst)
2823 manifestDigestURL, err := proxyEnv.builder.BuildManifestURL(digestRef)
2824 checkErr(t, err, "building manifest url")
2825
2826 resp, err := http.Get(manifestDigestURL)
2827 checkErr(t, err, "fetching manifest from proxy by digest")
2828 defer resp.Body.Close()
2829
2830 tagRef, _ := reference.WithTag(imageName, tag)
2831 manifestTagURL, err := proxyEnv.builder.BuildManifestURL(tagRef)
2832 checkErr(t, err, "building manifest url")
2833
2834 resp, err = http.Get(manifestTagURL)
2835 checkErr(t, err, "fetching manifest from proxy by tag (error check 1)")
2836 defer resp.Body.Close()
2837 checkResponse(t, "fetching manifest from proxy by tag (response check 1)", resp, http.StatusOK)
2838 checkHeaders(t, resp, http.Header{
2839 "Docker-Content-Digest": []string{dgst.String()},
2840 })
2841
2842
2843 newDigest := createRepository(truthEnv, t, imageName.Name(), tag)
2844 if dgst == newDigest {
2845 t.Fatalf("non-random test data")
2846 }
2847
2848
2849 resp, err = http.Get(manifestTagURL)
2850 checkErr(t, err, "fetching manifest from proxy by tag (error check 2)")
2851 defer resp.Body.Close()
2852 checkResponse(t, "fetching manifest from proxy by tag (response check 2)", resp, http.StatusOK)
2853 checkHeaders(t, resp, http.Header{
2854 "Docker-Content-Digest": []string{newDigest.String()},
2855 })
2856 }
2857
View as plain text