1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package remote
16
17 import (
18 "bytes"
19 "context"
20 "crypto"
21 "encoding/hex"
22 "errors"
23 "fmt"
24 "io"
25 "net/http"
26 "net/http/httptest"
27 "net/url"
28 "regexp"
29 "strings"
30 "sync/atomic"
31 "testing"
32
33 "github.com/google/go-cmp/cmp"
34 "github.com/google/go-cmp/cmp/cmpopts"
35 "github.com/google/go-containerregistry/pkg/name"
36 "github.com/google/go-containerregistry/pkg/registry"
37 v1 "github.com/google/go-containerregistry/pkg/v1"
38 "github.com/google/go-containerregistry/pkg/v1/empty"
39 "github.com/google/go-containerregistry/pkg/v1/mutate"
40 "github.com/google/go-containerregistry/pkg/v1/partial"
41 "github.com/google/go-containerregistry/pkg/v1/random"
42 "github.com/google/go-containerregistry/pkg/v1/remote/transport"
43 "github.com/google/go-containerregistry/pkg/v1/stream"
44 "github.com/google/go-containerregistry/pkg/v1/tarball"
45 "github.com/google/go-containerregistry/pkg/v1/types"
46 "github.com/google/go-containerregistry/pkg/v1/validate"
47 )
48
49 func mustNewTag(t *testing.T, s string) name.Tag {
50 tag, err := name.NewTag(s, name.WeakValidation)
51 if err != nil {
52 t.Fatalf("NewTag(%v) = %v", s, err)
53 }
54 return tag
55 }
56
57 func TestUrl(t *testing.T) {
58 tests := []struct {
59 tag string
60 path string
61 url string
62 }{{
63 tag: "gcr.io/foo/bar:latest",
64 path: "/v2/foo/bar/manifests/latest",
65 url: "https://gcr.io/v2/foo/bar/manifests/latest",
66 }, {
67 tag: "localhost:8080/foo/bar:baz",
68 path: "/v2/foo/bar/blobs/upload",
69 url: "http://localhost:8080/v2/foo/bar/blobs/upload",
70 }}
71
72 for _, test := range tests {
73 w := &writer{
74 repo: mustNewTag(t, test.tag).Context(),
75 }
76 if got, want := w.url(test.path), test.url; got.String() != want {
77 t.Errorf("url(%v) = %v, want %v", test.path, got.String(), want)
78 }
79 }
80 }
81
82 func TestNextLocation(t *testing.T) {
83 tests := []struct {
84 location string
85 url string
86 }{{
87 location: "https://gcr.io/v2/foo/bar/blobs/uploads/1234567?baz=blah",
88 url: "https://gcr.io/v2/foo/bar/blobs/uploads/1234567?baz=blah",
89 }, {
90 location: "/v2/foo/bar/blobs/uploads/1234567?baz=blah",
91 url: "https://gcr.io/v2/foo/bar/blobs/uploads/1234567?baz=blah",
92 }}
93
94 ref := mustNewTag(t, "gcr.io/foo/bar:latest")
95 w := &writer{
96 repo: ref.Context(),
97 }
98
99 for _, test := range tests {
100 resp := &http.Response{
101 Header: map[string][]string{
102 "Location": {test.location},
103 },
104 Request: &http.Request{
105 URL: &url.URL{
106 Scheme: ref.Registry.Scheme(),
107 Host: ref.RegistryStr(),
108 },
109 },
110 }
111
112 got, err := w.nextLocation(resp)
113 if err != nil {
114 t.Errorf("nextLocation(%v) = %v", resp, err)
115 }
116 want := test.url
117 if got != want {
118 t.Errorf("nextLocation(%v) = %v, want %v", resp, got, want)
119 }
120 }
121 }
122
123 type closer interface {
124 Close()
125 }
126
127 func setupImage(t *testing.T) v1.Image {
128 rnd, err := random.Image(1024, 1)
129 if err != nil {
130 t.Fatalf("random.Image() = %v", err)
131 }
132 return rnd
133 }
134
135 func setupIndex(t *testing.T, children int64) v1.ImageIndex {
136 rnd, err := random.Index(1024, 1, children)
137 if err != nil {
138 t.Fatalf("random.Index() = %v", err)
139 }
140 return rnd
141 }
142
143 func mustConfigName(t *testing.T, img v1.Image) v1.Hash {
144 h, err := img.ConfigName()
145 if err != nil {
146 t.Fatalf("ConfigName() = %v", err)
147 }
148 return h
149 }
150
151 func setupWriter(repo string, handler http.HandlerFunc) (*writer, closer, error) {
152 server := httptest.NewServer(handler)
153 return setupWriterWithServer(server, repo)
154 }
155
156 func setupWriterWithServer(server *httptest.Server, repo string) (*writer, closer, error) {
157 u, err := url.Parse(server.URL)
158 if err != nil {
159 server.Close()
160 return nil, nil, err
161 }
162 tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, repo), name.WeakValidation)
163 if err != nil {
164 server.Close()
165 return nil, nil, err
166 }
167
168 return &writer{
169 repo: tag.Context(),
170 client: http.DefaultClient,
171 predicate: defaultRetryPredicate,
172 backoff: defaultRetryBackoff,
173 }, server, nil
174 }
175
176 func TestCheckExistingBlob(t *testing.T) {
177 tests := []struct {
178 name string
179 status int
180 existing bool
181 wantErr bool
182 }{{
183 name: "success",
184 status: http.StatusOK,
185 existing: true,
186 }, {
187 name: "not found",
188 status: http.StatusNotFound,
189 existing: false,
190 }, {
191 name: "error",
192 status: http.StatusInternalServerError,
193 existing: false,
194 wantErr: true,
195 }}
196
197 img := setupImage(t)
198 h := mustConfigName(t, img)
199 expectedRepo := "foo/bar"
200 expectedPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, h.String())
201
202 for _, test := range tests {
203 t.Run(test.name, func(t *testing.T) {
204 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
205 if r.Method != http.MethodHead {
206 t.Errorf("Method; got %v, want %v", r.Method, http.MethodHead)
207 }
208 if r.URL.Path != expectedPath {
209 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
210 }
211 http.Error(w, http.StatusText(test.status), test.status)
212 }))
213 if err != nil {
214 t.Fatalf("setupWriter() = %v", err)
215 }
216 defer closer.Close()
217
218 existing, err := w.checkExistingBlob(context.Background(), h)
219 if test.existing != existing {
220 t.Errorf("checkExistingBlob() = %v, want %v", existing, test.existing)
221 }
222 if err != nil && !test.wantErr {
223 t.Errorf("checkExistingBlob() = %v", err)
224 } else if err == nil && test.wantErr {
225 t.Error("checkExistingBlob() wanted err, got nil")
226 }
227 })
228 }
229 }
230
231 func TestInitiateUploadNoMountsExists(t *testing.T) {
232 img := setupImage(t)
233 h := mustConfigName(t, img)
234 expectedRepo := "foo/bar"
235 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
236 expectedQuery := url.Values{
237 "mount": []string{h.String()},
238 "from": []string{"baz/bar"},
239 }.Encode()
240
241 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
242 if r.Method != http.MethodPost {
243 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
244 }
245 if r.URL.Path != expectedPath {
246 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
247 }
248 if r.URL.RawQuery != expectedQuery {
249 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
250 }
251 http.Error(w, "Mounted", http.StatusCreated)
252 }))
253 if err != nil {
254 t.Fatalf("setupWriter() = %v", err)
255 }
256 defer closer.Close()
257
258 _, mounted, err := w.initiateUpload(context.Background(), "baz/bar", h.String(), "")
259 if err != nil {
260 t.Errorf("intiateUpload() = %v", err)
261 }
262 if !mounted {
263 t.Error("initiateUpload() = !mounted, want mounted")
264 }
265 }
266
267 func TestInitiateUploadNoMountsInitiated(t *testing.T) {
268 img := setupImage(t)
269 h := mustConfigName(t, img)
270 expectedRepo := "baz/blah"
271 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
272 expectedQuery := url.Values{
273 "mount": []string{h.String()},
274 "from": []string{"baz/bar"},
275 }.Encode()
276 expectedLocation := "https://somewhere.io/upload?foo=bar"
277
278 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
279 if r.Method != http.MethodPost {
280 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
281 }
282 if r.URL.Path != expectedPath {
283 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
284 }
285 if r.URL.RawQuery != expectedQuery {
286 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
287 }
288 w.Header().Set("Location", expectedLocation)
289 http.Error(w, "Initiated", http.StatusAccepted)
290 }))
291 if err != nil {
292 t.Fatalf("setupWriter() = %v", err)
293 }
294 defer closer.Close()
295
296 location, mounted, err := w.initiateUpload(context.Background(), "baz/bar", h.String(), "")
297 if err != nil {
298 t.Errorf("intiateUpload() = %v", err)
299 }
300 if mounted {
301 t.Error("initiateUpload() = mounted, want !mounted")
302 }
303 if location != expectedLocation {
304 t.Errorf("initiateUpload(); got %v, want %v", location, expectedLocation)
305 }
306 }
307
308 func TestInitiateUploadNoMountsBadStatus(t *testing.T) {
309 img := setupImage(t)
310 h := mustConfigName(t, img)
311 expectedRepo := "ugh/another"
312 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
313 expectedQuery := url.Values{
314 "mount": []string{h.String()},
315 "from": []string{"baz/bar"},
316 }.Encode()
317
318 first := true
319
320 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
321 if r.Method != http.MethodPost {
322 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
323 }
324 if r.URL.Path != expectedPath {
325 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
326 }
327 if first {
328 if r.URL.RawQuery != expectedQuery {
329 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
330 }
331 first = false
332 } else {
333 if r.URL.RawQuery != "" {
334 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, "")
335 }
336 }
337
338 http.Error(w, "Unknown", http.StatusNoContent)
339 }))
340 if err != nil {
341 t.Fatalf("setupWriter() = %v", err)
342 }
343 defer closer.Close()
344
345 location, mounted, err := w.initiateUpload(context.Background(), "baz/bar", h.String(), "")
346 if err == nil {
347 t.Errorf("intiateUpload() = %v, %v; wanted error", location, mounted)
348 }
349 }
350
351 func TestInitiateUploadMountsWithMountFromDifferentRegistry(t *testing.T) {
352 img := setupImage(t)
353 h := mustConfigName(t, img)
354 expectedRepo := "yet/again"
355 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
356 expectedQuery := url.Values{
357 "mount": []string{h.String()},
358 "from": []string{"baz/bar"},
359 }.Encode()
360
361 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
362 if r.Method != http.MethodPost {
363 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
364 }
365 if r.URL.Path != expectedPath {
366 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
367 }
368 if r.URL.RawQuery != expectedQuery {
369 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
370 }
371 http.Error(w, "Mounted", http.StatusCreated)
372 }))
373 if err != nil {
374 t.Fatalf("setupWriter() = %v", err)
375 }
376 defer closer.Close()
377
378 _, mounted, err := w.initiateUpload(context.Background(), "baz/bar", h.String(), "")
379 if err != nil {
380 t.Errorf("intiateUpload() = %v", err)
381 }
382 if !mounted {
383 t.Error("initiateUpload() = !mounted, want mounted")
384 }
385 }
386
387 func TestInitiateUploadMountsWithMountFromTheSameRegistry(t *testing.T) {
388 img := setupImage(t)
389 h := mustConfigName(t, img)
390 expectedMountRepo := "a/different/repo"
391 expectedRepo := "yet/again"
392 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
393 expectedQuery := url.Values{
394 "mount": []string{h.String()},
395 "from": []string{expectedMountRepo},
396 }.Encode()
397
398 serverHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
399 if r.Method != http.MethodPost {
400 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
401 }
402 if r.URL.Path != expectedPath {
403 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
404 }
405 if r.URL.RawQuery != expectedQuery {
406 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
407 }
408 http.Error(w, "Mounted", http.StatusCreated)
409 })
410 server := httptest.NewServer(serverHandler)
411
412 w, closer, err := setupWriterWithServer(server, expectedRepo)
413 if err != nil {
414 t.Fatalf("setupWriterWithServer() = %v", err)
415 }
416 defer closer.Close()
417
418 _, mounted, err := w.initiateUpload(context.Background(), expectedMountRepo, h.String(), "")
419 if err != nil {
420 t.Errorf("intiateUpload() = %v", err)
421 }
422 if !mounted {
423 t.Error("initiateUpload() = !mounted, want mounted")
424 }
425 }
426
427 func TestInitiateUploadMountsWithOrigin(t *testing.T) {
428 img := setupImage(t)
429 h := mustConfigName(t, img)
430 expectedMountRepo := "a/different/repo"
431 expectedRepo := "yet/again"
432 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
433 expectedOrigin := "fakeOrigin"
434 expectedQuery := url.Values{
435 "mount": []string{h.String()},
436 "from": []string{expectedMountRepo},
437 "origin": []string{expectedOrigin},
438 }.Encode()
439
440 serverHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
441 if r.Method != http.MethodPost {
442 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
443 }
444 if r.URL.Path != expectedPath {
445 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
446 }
447 if r.URL.RawQuery != expectedQuery {
448 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
449 }
450 http.Error(w, "Mounted", http.StatusCreated)
451 })
452 server := httptest.NewServer(serverHandler)
453
454 w, closer, err := setupWriterWithServer(server, expectedRepo)
455 if err != nil {
456 t.Fatalf("setupWriterWithServer() = %v", err)
457 }
458 defer closer.Close()
459
460 _, mounted, err := w.initiateUpload(context.Background(), expectedMountRepo, h.String(), "fakeOrigin")
461 if err != nil {
462 t.Errorf("intiateUpload() = %v", err)
463 }
464 if !mounted {
465 t.Error("initiateUpload() = !mounted, want mounted")
466 }
467 }
468
469 func TestInitiateUploadMountsWithOriginFallback(t *testing.T) {
470 img := setupImage(t)
471 h := mustConfigName(t, img)
472 expectedMountRepo := "a/different/repo"
473 expectedRepo := "yet/again"
474 expectedPath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
475 expectedOrigin := "fakeOrigin"
476 expectedQuery := url.Values{
477 "mount": []string{h.String()},
478 "from": []string{expectedMountRepo},
479 "origin": []string{expectedOrigin},
480 }.Encode()
481
482 queries := []string{expectedQuery, ""}
483 queryCount := 0
484
485 serverHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
486 if r.Method != http.MethodPost {
487 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
488 }
489 if r.URL.Path != expectedPath {
490 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
491 }
492 if r.URL.RawQuery != queries[queryCount] {
493 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
494 }
495 if queryCount == 0 {
496 http.Error(w, "nope", http.StatusUnauthorized)
497 } else {
498 http.Error(w, "Mounted", http.StatusCreated)
499 }
500 queryCount++
501 })
502 server := httptest.NewServer(serverHandler)
503
504 w, closer, err := setupWriterWithServer(server, expectedRepo)
505 if err != nil {
506 t.Fatalf("setupWriterWithServer() = %v", err)
507 }
508 defer closer.Close()
509
510 _, mounted, err := w.initiateUpload(context.Background(), expectedMountRepo, h.String(), "fakeOrigin")
511 if err != nil {
512 t.Errorf("intiateUpload() = %v", err)
513 }
514 if !mounted {
515 t.Error("initiateUpload() = !mounted, want mounted")
516 }
517 }
518
519 func TestDedupeLayers(t *testing.T) {
520 newBlob := func() io.ReadCloser { return io.NopCloser(bytes.NewReader(bytes.Repeat([]byte{'a'}, 10000))) }
521
522 img, err := random.Image(1024, 3)
523 if err != nil {
524 t.Fatalf("random.Image: %v", err)
525 }
526
527
528
529 for i := 0; i < 3; i++ {
530 tl, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) { return newBlob(), nil })
531 if err != nil {
532 t.Fatalf("LayerFromOpener(#%d): %v", i, err)
533 }
534 img, err = mutate.AppendLayers(img, tl)
535 if err != nil {
536 t.Fatalf("mutate.AppendLayer(#%d): %v", i, err)
537 }
538 }
539
540
541
542 for i := 0; i < 3; i++ {
543 sl := stream.NewLayer(newBlob())
544 img, err = mutate.AppendLayers(img, sl)
545 if err != nil {
546 t.Fatalf("mutate.AppendLayer(#%d): %v", i, err)
547 }
548 }
549
550 expectedRepo := "write/time"
551 headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
552 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
553 manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
554 uploadPath := "/upload"
555 commitPath := "/commit"
556 var numUploads int32
557 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
558 if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
559 http.Error(w, "NotFound", http.StatusNotFound)
560 return
561 }
562 switch r.URL.Path {
563 case "/v2/":
564 w.WriteHeader(http.StatusOK)
565 case initiatePath:
566 if r.Method != http.MethodPost {
567 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
568 }
569 w.Header().Set("Location", uploadPath)
570 http.Error(w, "Accepted", http.StatusAccepted)
571 case uploadPath:
572 if r.Method != http.MethodPatch {
573 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPatch)
574 }
575 atomic.AddInt32(&numUploads, 1)
576 w.Header().Set("Location", commitPath)
577 http.Error(w, "Created", http.StatusCreated)
578 case commitPath:
579 http.Error(w, "Created", http.StatusCreated)
580 case manifestPath:
581 if r.Method == http.MethodHead {
582 w.Header().Set("Content-Type", string(types.DockerManifestSchema1Signed))
583 w.Header().Set("Docker-Content-Digest", fakeDigest)
584 w.Write([]byte("doesn't matter"))
585 return
586 }
587 if r.Method != http.MethodPut {
588 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
589 }
590 http.Error(w, "Created", http.StatusCreated)
591 default:
592 t.Fatalf("Unexpected path: %v", r.URL.Path)
593 }
594 }))
595 defer server.Close()
596 u, err := url.Parse(server.URL)
597 if err != nil {
598 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
599 }
600 tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
601 if err != nil {
602 t.Fatalf("NewTag() = %v", err)
603 }
604
605 if err := Write(tag, img); err != nil {
606 t.Errorf("Write: %v", err)
607 }
608
609
610 wantUploads := int32(3 + 1 + 3 + 1)
611 if numUploads != wantUploads {
612 t.Fatalf("Write uploaded %d blobs, want %d", numUploads, wantUploads)
613 }
614 }
615
616 func TestStreamBlob(t *testing.T) {
617 img := setupImage(t)
618 expectedPath := "/vWhatever/I/decide"
619 expectedCommitLocation := "https://commit.io/v12/blob"
620
621 w, closer, err := setupWriter("what/ever", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
622 if r.Method != http.MethodPatch {
623 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPatch)
624 }
625 if r.URL.Path != expectedPath {
626 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
627 }
628 got, err := io.ReadAll(r.Body)
629 if err != nil {
630 t.Errorf("ReadAll(Body) = %v", err)
631 }
632 want, err := img.RawConfigFile()
633 if err != nil {
634 t.Errorf("RawConfigFile() = %v", err)
635 }
636 if !bytes.Equal(got, want) {
637 t.Errorf("bytes.Equal(); got %v, want %v", got, want)
638 }
639 w.Header().Set("Location", expectedCommitLocation)
640 http.Error(w, "Created", http.StatusCreated)
641 }))
642 if err != nil {
643 t.Fatalf("setupWriter() = %v", err)
644 }
645 defer closer.Close()
646
647 streamLocation := w.url(expectedPath)
648
649 l, err := partial.ConfigLayer(img)
650 if err != nil {
651 t.Fatalf("ConfigLayer: %v", err)
652 }
653
654 commitLocation, err := w.streamBlob(context.Background(), l, streamLocation.String())
655 if err != nil {
656 t.Errorf("streamBlob() = %v", err)
657 }
658 if commitLocation != expectedCommitLocation {
659 t.Errorf("streamBlob(); got %v, want %v", commitLocation, expectedCommitLocation)
660 }
661 }
662
663 func TestStreamLayer(t *testing.T) {
664 var n, wantSize int64 = 10000, 49
665 newBlob := func() io.ReadCloser { return io.NopCloser(bytes.NewReader(bytes.Repeat([]byte{'a'}, int(n)))) }
666 wantDigest := "sha256:3d7c465be28d9e1ed810c42aeb0e747b44441424f566722ba635dc93c947f30e"
667
668 expectedPath := "/vWhatever/I/decide"
669 expectedCommitLocation := "https://commit.io/v12/blob"
670 w, closer, err := setupWriter("what/ever", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
671 if r.Method != http.MethodPatch {
672 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPatch)
673 }
674 if r.URL.Path != expectedPath {
675 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
676 }
677
678 h := crypto.SHA256.New()
679 s, err := io.Copy(h, r.Body)
680 if err != nil {
681 t.Errorf("Reading body: %v", err)
682 }
683 if s != wantSize {
684 t.Errorf("Received %d bytes, want %d", s, wantSize)
685 }
686 gotDigest := "sha256:" + hex.EncodeToString(h.Sum(nil))
687 if gotDigest != wantDigest {
688 t.Errorf("Received bytes with digest %q, want %q", gotDigest, wantDigest)
689 }
690
691 w.Header().Set("Location", expectedCommitLocation)
692 http.Error(w, "Created", http.StatusCreated)
693 }))
694 if err != nil {
695 t.Fatalf("setupWriter() = %v", err)
696 }
697 defer closer.Close()
698
699 streamLocation := w.url(expectedPath)
700 sl := stream.NewLayer(newBlob())
701
702 commitLocation, err := w.streamBlob(context.Background(), sl, streamLocation.String())
703 if err != nil {
704 t.Errorf("streamBlob: %v", err)
705 }
706 if commitLocation != expectedCommitLocation {
707 t.Errorf("streamBlob(); got %v, want %v", commitLocation, expectedCommitLocation)
708 }
709 }
710
711 func TestCommitBlob(t *testing.T) {
712 img := setupImage(t)
713 h := mustConfigName(t, img)
714 expectedPath := "/no/commitment/issues"
715 expectedQuery := url.Values{
716 "digest": []string{h.String()},
717 }.Encode()
718
719 w, closer, err := setupWriter("what/ever", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
720 if r.Method != http.MethodPut {
721 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
722 }
723 if r.URL.Path != expectedPath {
724 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
725 }
726 if r.URL.RawQuery != expectedQuery {
727 t.Errorf("RawQuery; got %v, want %v", r.URL.RawQuery, expectedQuery)
728 }
729 http.Error(w, "Created", http.StatusCreated)
730 }))
731 if err != nil {
732 t.Fatalf("setupWriter() = %v", err)
733 }
734 defer closer.Close()
735
736 commitLocation := w.url(expectedPath)
737
738 if err := w.commitBlob(context.Background(), commitLocation.String(), h.String()); err != nil {
739 t.Errorf("commitBlob() = %v", err)
740 }
741 }
742
743 func TestUploadOne(t *testing.T) {
744 img := setupImage(t)
745 h := mustConfigName(t, img)
746 expectedRepo := "baz/blah"
747 headPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, h.String())
748 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
749 streamPath := "/path/to/upload"
750 commitPath := "/path/to/commit"
751 ctx := context.Background()
752
753 uploaded := false
754 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
755 switch r.URL.Path {
756 case headPath:
757 if r.Method != http.MethodHead {
758 t.Errorf("Method; got %v, want %v", r.Method, http.MethodHead)
759 }
760 if uploaded {
761 return
762 }
763 http.Error(w, "NotFound", http.StatusNotFound)
764 case initiatePath:
765 if r.Method != http.MethodPost {
766 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
767 }
768 w.Header().Set("Location", streamPath)
769 http.Error(w, "Initiated", http.StatusAccepted)
770 case streamPath:
771 if r.Method != http.MethodPatch {
772 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPatch)
773 }
774 got, err := io.ReadAll(r.Body)
775 if err != nil {
776 t.Errorf("ReadAll(Body) = %v", err)
777 }
778 want, err := img.RawConfigFile()
779 if err != nil {
780 t.Errorf("RawConfigFile() = %v", err)
781 }
782 if !bytes.Equal(got, want) {
783 t.Errorf("bytes.Equal(); got %v, want %v", got, want)
784 }
785 w.Header().Set("Location", commitPath)
786 http.Error(w, "Initiated", http.StatusAccepted)
787 case commitPath:
788 if r.Method != http.MethodPut {
789 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
790 }
791 uploaded = true
792 http.Error(w, "Created", http.StatusCreated)
793 default:
794 t.Fatalf("Unexpected path: %v", r.URL.Path)
795 }
796 }))
797 if err != nil {
798 t.Fatalf("setupWriter() = %v", err)
799 }
800 defer closer.Close()
801
802 l, err := partial.ConfigLayer(img)
803 if err != nil {
804 t.Fatalf("ConfigLayer: %v", err)
805 }
806 ml := &MountableLayer{
807 Layer: l,
808 Reference: w.repo.Digest(h.String()),
809 }
810 if err := w.uploadOne(ctx, ml); err != nil {
811 t.Errorf("uploadOne() = %v", err)
812 }
813
814 if err := w.uploadOne(ctx, l); err != nil {
815 t.Errorf("uploadOne() = %v", err)
816 }
817 }
818
819 func TestUploadOneStreamedLayer(t *testing.T) {
820 expectedRepo := "baz/blah"
821 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
822 streamPath := "/path/to/upload"
823 commitPath := "/path/to/commit"
824 ctx := context.Background()
825
826 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
827 switch r.URL.Path {
828 case initiatePath:
829 if r.Method != http.MethodPost {
830 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
831 }
832 w.Header().Set("Location", streamPath)
833 http.Error(w, "Initiated", http.StatusAccepted)
834 case streamPath:
835 if r.Method != http.MethodPatch {
836 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPatch)
837 }
838
839 w.Header().Set("Location", commitPath)
840 http.Error(w, "Initiated", http.StatusAccepted)
841 case commitPath:
842 if r.Method != http.MethodPut {
843 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
844 }
845 http.Error(w, "Created", http.StatusCreated)
846 default:
847 t.Fatalf("Unexpected path: %v", r.URL.Path)
848 }
849 }))
850 if err != nil {
851 t.Fatalf("setupWriter() = %v", err)
852 }
853 defer closer.Close()
854
855 var n, wantSize int64 = 10000, 49
856 newBlob := func() io.ReadCloser { return io.NopCloser(bytes.NewReader(bytes.Repeat([]byte{'a'}, int(n)))) }
857 wantDigest := "sha256:3d7c465be28d9e1ed810c42aeb0e747b44441424f566722ba635dc93c947f30e"
858 wantDiffID := "sha256:27dd1f61b867b6a0f6e9d8a41c43231de52107e53ae424de8f847b821db4b711"
859 l := stream.NewLayer(newBlob())
860 if err := w.uploadOne(ctx, l); err != nil {
861 t.Fatalf("uploadOne: %v", err)
862 }
863
864 if dig, err := l.Digest(); err != nil {
865 t.Errorf("Digest: %v", err)
866 } else if dig.String() != wantDigest {
867 t.Errorf("Digest got %q, want %q", dig, wantDigest)
868 }
869 if diffID, err := l.DiffID(); err != nil {
870 t.Errorf("DiffID: %v", err)
871 } else if diffID.String() != wantDiffID {
872 t.Errorf("DiffID got %q, want %q", diffID, wantDiffID)
873 }
874 if size, err := l.Size(); err != nil {
875 t.Errorf("Size: %v", err)
876 } else if size != wantSize {
877 t.Errorf("Size got %d, want %d", size, wantSize)
878 }
879 }
880
881 func TestCommitImage(t *testing.T) {
882 img := setupImage(t)
883 ctx := context.Background()
884
885 expectedRepo := "foo/bar"
886 expectedPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
887
888 w, closer, err := setupWriter(expectedRepo, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
889 if r.Method != http.MethodPut {
890 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
891 }
892 if r.URL.Path != expectedPath {
893 t.Errorf("URL; got %v, want %v", r.URL.Path, expectedPath)
894 }
895 got, err := io.ReadAll(r.Body)
896 if err != nil {
897 t.Errorf("ReadAll(Body) = %v", err)
898 }
899 want, err := img.RawManifest()
900 if err != nil {
901 t.Errorf("RawManifest() = %v", err)
902 }
903 if !bytes.Equal(got, want) {
904 t.Errorf("bytes.Equal(); got %v, want %v", got, want)
905 }
906 mt, err := img.MediaType()
907 if err != nil {
908 t.Errorf("MediaType() = %v", err)
909 }
910 if got, want := r.Header.Get("Content-Type"), string(mt); got != want {
911 t.Errorf("Header; got %v, want %v", got, want)
912 }
913 http.Error(w, "Created", http.StatusCreated)
914 }))
915 if err != nil {
916 t.Fatalf("setupWriter() = %v", err)
917 }
918 defer closer.Close()
919
920 if err := w.commitManifest(ctx, img, w.repo.Tag("latest")); err != nil {
921 t.Error("commitManifest() = ", err)
922 }
923 }
924
925 func TestWrite(t *testing.T) {
926 img := setupImage(t)
927 expectedRepo := "write/time"
928 headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
929 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
930 manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
931
932 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
933 if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
934 http.Error(w, "NotFound", http.StatusNotFound)
935 return
936 }
937 switch r.URL.Path {
938 case "/v2/":
939 w.WriteHeader(http.StatusOK)
940 case initiatePath:
941 if r.Method != http.MethodPost {
942 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
943 }
944 http.Error(w, "Mounted", http.StatusCreated)
945 case manifestPath:
946 if r.Method == http.MethodHead {
947 w.WriteHeader(http.StatusNotFound)
948 return
949 }
950 if r.Method != http.MethodPut {
951 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
952 }
953 http.Error(w, "Created", http.StatusCreated)
954 default:
955 t.Fatalf("Unexpected path: %v", r.URL.Path)
956 }
957 }))
958 defer server.Close()
959 u, err := url.Parse(server.URL)
960 if err != nil {
961 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
962 }
963 tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
964 if err != nil {
965 t.Fatalf("NewTag() = %v", err)
966 }
967
968 if err := Write(tag, img); err != nil {
969 t.Errorf("Write() = %v", err)
970 }
971 }
972
973 func TestWriteWithErrors(t *testing.T) {
974 img := setupImage(t)
975 expectedRepo := "write/time"
976 headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
977 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
978 manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
979
980 errorBody := `{"errors":[{"code":"NAME_INVALID","message":"some explanation of how things were messed up."}],"StatusCode":400}`
981 expectedErrMsg, err := regexp.Compile(`POST .+ NAME_INVALID: some explanation of how things were messed up.`)
982 if err != nil {
983 t.Error(err)
984 }
985
986 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
987 if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
988 http.Error(w, "NotFound", http.StatusNotFound)
989 return
990 }
991 switch r.URL.Path {
992 case "/v2/":
993 w.WriteHeader(http.StatusOK)
994 case manifestPath:
995 w.WriteHeader(http.StatusNotFound)
996 case initiatePath:
997 if r.Method != http.MethodPost {
998 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
999 }
1000
1001 w.WriteHeader(http.StatusBadRequest)
1002 w.Write([]byte(errorBody))
1003 default:
1004 t.Fatalf("Unexpected path: %v", r.URL.Path)
1005 }
1006 }))
1007 defer server.Close()
1008 u, err := url.Parse(server.URL)
1009 if err != nil {
1010 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
1011 }
1012 tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
1013 if err != nil {
1014 t.Fatalf("NewTag() = %v", err)
1015 }
1016
1017 c := make(chan v1.Update, 100)
1018
1019 var terr *transport.Error
1020 if err := Write(tag, img, WithProgress(c)); err == nil {
1021 t.Error("Write() = nil; wanted error")
1022 } else if !errors.As(err, &terr) {
1023 t.Errorf("Write() = %T; wanted *transport.Error", err)
1024 } else if !expectedErrMsg.Match([]byte(terr.Error())) {
1025 diff := cmp.Diff(expectedErrMsg, terr.Error())
1026 t.Errorf("Write(); (-want +got) = %s", diff)
1027 }
1028
1029 var last v1.Update
1030 for update := range c {
1031 last = update
1032 }
1033 if last.Error == nil {
1034 t.Error("Progress chan didn't report error")
1035 }
1036 }
1037
1038 func TestDockerhubScopes(t *testing.T) {
1039 src, err := name.ParseReference("busybox")
1040 if err != nil {
1041 t.Fatal(err)
1042 }
1043 rl, err := random.Layer(1024, types.DockerLayer)
1044 if err != nil {
1045 t.Fatal(err)
1046 }
1047 ml := &MountableLayer{
1048 Layer: rl,
1049 Reference: src,
1050 }
1051 want := src.Scope(transport.PullScope)
1052
1053 for _, s := range []string{
1054 "jonjohnson/busybox",
1055 "docker.io/jonjohnson/busybox",
1056 "index.docker.io/jonjohnson/busybox",
1057 } {
1058 dst, err := name.ParseReference(s)
1059 if err != nil {
1060 t.Fatal(err)
1061 }
1062
1063 scopes := scopesForUploadingImage(dst.Context(), []v1.Layer{ml})
1064
1065 if len(scopes) != 2 {
1066 t.Errorf("Should have two scopes (src and dst), got %d", len(scopes))
1067 } else if diff := cmp.Diff(want, scopes[1]); diff != "" {
1068 t.Errorf("TestDockerhubScopes %q: (-want +got) = %v", s, diff)
1069 }
1070 }
1071 }
1072
1073 func TestScopesForUploadingImage(t *testing.T) {
1074 referenceToUpload, err := name.NewTag("example.com/sample/sample:latest", name.WeakValidation)
1075 if err != nil {
1076 t.Fatalf("name.NewTag() = %v", err)
1077 }
1078
1079 sameReference, err := name.NewTag("example.com/sample/sample:previous", name.WeakValidation)
1080 if err != nil {
1081 t.Fatalf("name.NewTag() = %v", err)
1082 }
1083
1084 anotherRepo1, err := name.NewTag("example.com/sample/another_repo1:latest", name.WeakValidation)
1085 if err != nil {
1086 t.Fatalf("name.NewTag() = %v", err)
1087 }
1088
1089 anotherRepo2, err := name.NewTag("example.com/sample/another_repo2:latest", name.WeakValidation)
1090 if err != nil {
1091 t.Fatalf("name.NewTag() = %v", err)
1092 }
1093
1094 repoOnOtherRegistry, err := name.NewTag("other-domain.com/sample/any_repo:latest", name.WeakValidation)
1095 if err != nil {
1096 t.Fatalf("name.NewTag() = %v", err)
1097 }
1098
1099 img := setupImage(t)
1100 layers, err := img.Layers()
1101 if err != nil {
1102 t.Fatalf("img.Layers() = %v", err)
1103 }
1104 dummyLayer := layers[0]
1105
1106 testCases := []struct {
1107 name string
1108 reference name.Reference
1109 layers []v1.Layer
1110 expected []string
1111 }{
1112 {
1113 name: "empty layers",
1114 reference: referenceToUpload,
1115 layers: []v1.Layer{},
1116 expected: []string{
1117 referenceToUpload.Scope(transport.PushScope),
1118 },
1119 },
1120 {
1121 name: "mountable layers with same reference",
1122 reference: referenceToUpload,
1123 layers: []v1.Layer{
1124 &MountableLayer{
1125 Layer: dummyLayer,
1126 Reference: sameReference,
1127 },
1128 },
1129 expected: []string{
1130 referenceToUpload.Scope(transport.PushScope),
1131 },
1132 },
1133 {
1134 name: "mountable layers with single reference with no-duplicate",
1135 reference: referenceToUpload,
1136 layers: []v1.Layer{
1137 &MountableLayer{
1138 Layer: dummyLayer,
1139 Reference: anotherRepo1,
1140 },
1141 },
1142 expected: []string{
1143 referenceToUpload.Scope(transport.PushScope),
1144 anotherRepo1.Scope(transport.PullScope),
1145 },
1146 },
1147 {
1148 name: "mountable layers with single reference with duplicate",
1149 reference: referenceToUpload,
1150 layers: []v1.Layer{
1151 &MountableLayer{
1152 Layer: dummyLayer,
1153 Reference: anotherRepo1,
1154 },
1155 &MountableLayer{
1156 Layer: dummyLayer,
1157 Reference: anotherRepo1,
1158 },
1159 },
1160 expected: []string{
1161 referenceToUpload.Scope(transport.PushScope),
1162 anotherRepo1.Scope(transport.PullScope),
1163 },
1164 },
1165 {
1166 name: "mountable layers with multiple references with no-duplicates",
1167 reference: referenceToUpload,
1168 layers: []v1.Layer{
1169 &MountableLayer{
1170 Layer: dummyLayer,
1171 Reference: anotherRepo1,
1172 },
1173 &MountableLayer{
1174 Layer: dummyLayer,
1175 Reference: anotherRepo2,
1176 },
1177 },
1178 expected: []string{
1179 referenceToUpload.Scope(transport.PushScope),
1180 anotherRepo1.Scope(transport.PullScope),
1181 anotherRepo2.Scope(transport.PullScope),
1182 },
1183 },
1184 {
1185 name: "mountable layers with multiple references with duplicates",
1186 reference: referenceToUpload,
1187 layers: []v1.Layer{
1188 &MountableLayer{
1189 Layer: dummyLayer,
1190 Reference: anotherRepo1,
1191 },
1192 &MountableLayer{
1193 Layer: dummyLayer,
1194 Reference: anotherRepo2,
1195 },
1196 &MountableLayer{
1197 Layer: dummyLayer,
1198 Reference: anotherRepo1,
1199 },
1200 &MountableLayer{
1201 Layer: dummyLayer,
1202 Reference: anotherRepo2,
1203 },
1204 },
1205 expected: []string{
1206 referenceToUpload.Scope(transport.PushScope),
1207 anotherRepo1.Scope(transport.PullScope),
1208 anotherRepo2.Scope(transport.PullScope),
1209 },
1210 },
1211 {
1212 name: "cross repository mountable layer",
1213 reference: referenceToUpload,
1214 layers: []v1.Layer{
1215 &MountableLayer{
1216 Layer: dummyLayer,
1217 Reference: repoOnOtherRegistry,
1218 },
1219 },
1220 expected: []string{
1221 referenceToUpload.Scope(transport.PushScope),
1222 },
1223 },
1224 }
1225
1226 for _, tc := range testCases {
1227 actual := scopesForUploadingImage(tc.reference.Context(), tc.layers)
1228
1229 if want, got := tc.expected[0], actual[0]; want != got {
1230 t.Errorf("TestScopesForUploadingImage() %s: Wrong first scope; want %v, got %v", tc.name, want, got)
1231 }
1232
1233 less := func(a, b string) bool {
1234 return strings.Compare(a, b) <= -1
1235 }
1236 if diff := cmp.Diff(tc.expected[1:], actual[1:], cmpopts.SortSlices(less)); diff != "" {
1237 t.Errorf("TestScopesForUploadingImage() %s: Wrong scopes (-want +got) = %v", tc.name, diff)
1238 }
1239 }
1240 }
1241
1242 func TestWriteIndex(t *testing.T) {
1243 idx := setupIndex(t, 2)
1244 expectedRepo := "write/time"
1245 headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
1246 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
1247 manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
1248 childDigest := mustIndexManifest(t, idx).Manifests[0].Digest
1249 childPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, childDigest)
1250 existingChildDigest := mustIndexManifest(t, idx).Manifests[1].Digest
1251 existingChildPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, existingChildDigest)
1252
1253 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1254 if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
1255 http.Error(w, "NotFound", http.StatusNotFound)
1256 return
1257 }
1258 switch r.URL.Path {
1259 case "/v2/":
1260 w.WriteHeader(http.StatusOK)
1261 case initiatePath:
1262 if r.Method != http.MethodPost {
1263 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
1264 }
1265 http.Error(w, "Mounted", http.StatusCreated)
1266 case manifestPath:
1267 if r.Method == http.MethodHead {
1268 w.WriteHeader(http.StatusNotFound)
1269 return
1270 }
1271 if r.Method != http.MethodPut {
1272 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
1273 }
1274 http.Error(w, "Created", http.StatusCreated)
1275 case existingChildPath:
1276 if r.Method == http.MethodHead {
1277 w.Header().Set("Content-Type", string(types.DockerManifestSchema1))
1278 w.Header().Set("Docker-Content-Digest", existingChildDigest.String())
1279 w.Header().Set("Content-Length", "123")
1280 return
1281 }
1282 t.Errorf("Unexpected method; got %v, want %v", r.Method, http.MethodHead)
1283 case childPath:
1284 if r.Method == http.MethodHead {
1285 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
1286 return
1287 }
1288 if r.Method != http.MethodPut {
1289 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
1290 }
1291 http.Error(w, "Created", http.StatusCreated)
1292 default:
1293 t.Fatalf("Unexpected path: %v", r.URL.Path)
1294 }
1295 }))
1296 defer server.Close()
1297 u, err := url.Parse(server.URL)
1298 if err != nil {
1299 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
1300 }
1301 tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
1302 if err != nil {
1303 t.Fatalf("NewTag() = %v", err)
1304 }
1305
1306 if err := WriteIndex(tag, idx); err != nil {
1307 t.Errorf("WriteIndex() = %v", err)
1308 }
1309 }
1310
1311
1312 type fakeForeignLayer struct {
1313 t *testing.T
1314 }
1315
1316 func (l *fakeForeignLayer) MediaType() (types.MediaType, error) {
1317 return types.DockerForeignLayer, nil
1318 }
1319
1320 func (l *fakeForeignLayer) Size() (int64, error) {
1321 return 0, nil
1322 }
1323
1324 func (l *fakeForeignLayer) Digest() (v1.Hash, error) {
1325 return v1.Hash{Algorithm: "sha256", Hex: strings.Repeat("a", 64)}, nil
1326 }
1327
1328 func (l *fakeForeignLayer) DiffID() (v1.Hash, error) {
1329 return v1.Hash{Algorithm: "sha256", Hex: strings.Repeat("a", 64)}, nil
1330 }
1331
1332 func (l *fakeForeignLayer) Compressed() (io.ReadCloser, error) {
1333 l.t.Helper()
1334 l.t.Errorf("foreign layer not skipped: Compressed")
1335 return nil, nil
1336 }
1337
1338 func (l *fakeForeignLayer) Uncompressed() (io.ReadCloser, error) {
1339 l.t.Helper()
1340 l.t.Errorf("foreign layer not skipped: Uncompressed")
1341 return nil, nil
1342 }
1343
1344 func TestSkipForeignLayersByDefault(t *testing.T) {
1345
1346 base := setupImage(t)
1347 img, err := mutate.AppendLayers(base, &fakeForeignLayer{t: t})
1348 if err != nil {
1349 t.Fatal(err)
1350 }
1351
1352
1353 s := httptest.NewServer(registry.New())
1354 defer s.Close()
1355 u, err := url.Parse(s.URL)
1356 if err != nil {
1357 t.Fatal(err)
1358 }
1359 dst := fmt.Sprintf("%s/test/foreign/upload", u.Host)
1360 ref, err := name.ParseReference(dst)
1361 if err != nil {
1362 t.Fatal(err)
1363 }
1364
1365 if err := Write(ref, img); err != nil {
1366 t.Errorf("failed to Write: %v", err)
1367 }
1368 }
1369
1370 func TestWriteForeignLayerIfOptionSet(t *testing.T) {
1371
1372 base := setupImage(t)
1373 foreignLayer, err := random.Layer(1024, types.DockerForeignLayer)
1374 if err != nil {
1375 t.Fatal("random.Layer:", err)
1376 }
1377 img, err := mutate.AppendLayers(base, foreignLayer)
1378 if err != nil {
1379 t.Fatal(err)
1380 }
1381
1382 expectedRepo := "write/time"
1383 headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
1384 initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
1385 manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
1386 uploadPath := "/upload"
1387 commitPath := "/commit"
1388 var numUploads int32
1389 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1390 if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
1391 http.Error(w, "NotFound", http.StatusNotFound)
1392 return
1393 }
1394 switch r.URL.Path {
1395 case "/v2/":
1396 w.WriteHeader(http.StatusOK)
1397 case initiatePath:
1398 if r.Method != http.MethodPost {
1399 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
1400 }
1401 w.Header().Set("Location", uploadPath)
1402 http.Error(w, "Accepted", http.StatusAccepted)
1403 case uploadPath:
1404 if r.Method != http.MethodPatch {
1405 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPatch)
1406 }
1407 atomic.AddInt32(&numUploads, 1)
1408 w.Header().Set("Location", commitPath)
1409 http.Error(w, "Created", http.StatusCreated)
1410 case commitPath:
1411 http.Error(w, "Created", http.StatusCreated)
1412 case manifestPath:
1413 if r.Method == http.MethodHead {
1414 w.Header().Set("Content-Type", string(types.DockerManifestSchema1Signed))
1415 w.Header().Set("Docker-Content-Digest", fakeDigest)
1416 w.Header().Set("Content-Length", "123")
1417 return
1418 }
1419 if r.Method != http.MethodPut && r.Method != http.MethodHead {
1420 t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
1421 }
1422 http.Error(w, "Created", http.StatusCreated)
1423 default:
1424 t.Fatalf("Unexpected path: %v", r.URL.Path)
1425 }
1426 }))
1427 defer server.Close()
1428 u, err := url.Parse(server.URL)
1429 if err != nil {
1430 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
1431 }
1432 tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
1433 if err != nil {
1434 t.Fatalf("NewTag() = %v", err)
1435 }
1436
1437 if err := Write(tag, img, WithNondistributable); err != nil {
1438 t.Errorf("Write: %v", err)
1439 }
1440
1441
1442 wantUploads := int32(1 + 1 + 1)
1443 if numUploads != wantUploads {
1444 t.Fatalf("Write uploaded %d blobs, want %d", numUploads, wantUploads)
1445 }
1446 }
1447
1448 func TestTag(t *testing.T) {
1449 idx := setupIndex(t, 3)
1450
1451 s := httptest.NewServer(registry.New())
1452 defer s.Close()
1453 u, err := url.Parse(s.URL)
1454 if err != nil {
1455 t.Fatal(err)
1456 }
1457 src := fmt.Sprintf("%s/test/tag:src", u.Host)
1458 srcRef, err := name.NewTag(src)
1459 if err != nil {
1460 t.Fatal(err)
1461 }
1462
1463 if err := WriteIndex(srcRef, idx); err != nil {
1464 t.Fatal(err)
1465 }
1466
1467 dst := fmt.Sprintf("%s/test/tag:dst", u.Host)
1468 dstRef, err := name.NewTag(dst)
1469 if err != nil {
1470 t.Fatal(err)
1471 }
1472
1473 if err := Tag(dstRef, idx); err != nil {
1474 t.Fatal(err)
1475 }
1476
1477 got, err := Index(dstRef)
1478 if err != nil {
1479 t.Fatal(err)
1480 }
1481
1482 if err := validate.Index(got); err != nil {
1483 t.Errorf("Validate() = %v", err)
1484 }
1485 }
1486
1487 func TestTagDescriptor(t *testing.T) {
1488 idx := setupIndex(t, 3)
1489
1490 s := httptest.NewServer(registry.New())
1491 defer s.Close()
1492 u, err := url.Parse(s.URL)
1493 if err != nil {
1494 t.Fatal(err)
1495 }
1496 src := fmt.Sprintf("%s/test/tag:src", u.Host)
1497 srcRef, err := name.NewTag(src)
1498 if err != nil {
1499 t.Fatal(err)
1500 }
1501
1502 if err := WriteIndex(srcRef, idx); err != nil {
1503 t.Fatal(err)
1504 }
1505
1506 desc, err := Get(srcRef)
1507 if err != nil {
1508 t.Fatal(err)
1509 }
1510
1511 dst := fmt.Sprintf("%s/test/tag:dst", u.Host)
1512 dstRef, err := name.NewTag(dst)
1513 if err != nil {
1514 t.Fatal(err)
1515 }
1516
1517 if err := Tag(dstRef, desc); err != nil {
1518 t.Fatal(err)
1519 }
1520 }
1521
1522 func TestNestedIndex(t *testing.T) {
1523
1524 s := httptest.NewServer(registry.New())
1525 defer s.Close()
1526 u, err := url.Parse(s.URL)
1527 if err != nil {
1528 t.Fatal(err)
1529 }
1530 src := fmt.Sprintf("%s/test/tag:src", u.Host)
1531 srcRef, err := name.NewTag(src)
1532 if err != nil {
1533 t.Fatal(err)
1534 }
1535
1536 child, err := random.Index(1024, 1, 1)
1537 if err != nil {
1538 t.Fatal(err)
1539 }
1540 parent := mutate.AppendManifests(empty.Index, mutate.IndexAddendum{
1541 Add: child,
1542 Descriptor: v1.Descriptor{
1543 URLs: []string{"example.com/url"},
1544 },
1545 })
1546
1547 l, err := random.Layer(100, types.DockerLayer)
1548 if err != nil {
1549 t.Fatal(err)
1550 }
1551
1552 parent = mutate.AppendManifests(parent, mutate.IndexAddendum{
1553 Add: l,
1554 })
1555
1556 if err := WriteIndex(srcRef, parent); err != nil {
1557 t.Fatal(err)
1558 }
1559 pulled, err := Index(srcRef)
1560 if err != nil {
1561 t.Fatal(err)
1562 }
1563
1564 if err := validate.Index(pulled); err != nil {
1565 t.Fatalf("validate.Index: %v", err)
1566 }
1567
1568 digest, err := child.Digest()
1569 if err != nil {
1570 t.Fatal(err)
1571 }
1572
1573 pulledChild, err := pulled.ImageIndex(digest)
1574 if err != nil {
1575 t.Fatal(err)
1576 }
1577
1578 desc, err := partial.Descriptor(pulledChild)
1579 if err != nil {
1580 t.Fatal(err)
1581 }
1582
1583 if len(desc.URLs) != 1 {
1584 t.Fatalf("expected url for pulledChild")
1585 }
1586
1587 if want, got := "example.com/url", desc.URLs[0]; want != got {
1588 t.Errorf("pulledChild.urls[0] = %s != %s", got, want)
1589 }
1590 }
1591
1592 func BenchmarkWrite(b *testing.B) {
1593
1594
1595 for i := 0; i < b.N; i++ {
1596
1597 s := httptest.NewServer(registry.New())
1598 defer s.Close()
1599
1600
1601 img, err := random.Image(50*1024*1024, 10)
1602 if err != nil {
1603 b.Fatalf("random.Image(...): %v", err)
1604 }
1605
1606 b.ResetTimer()
1607
1608 tagStr := strings.TrimPrefix(s.URL+"/test/image:tag", "http://")
1609 tag, err := name.NewTag(tagStr)
1610 if err != nil {
1611 b.Fatalf("parsing tag (%s): %v", tagStr, err)
1612 }
1613
1614 err = Write(tag, img)
1615 if err != nil {
1616 b.Fatalf("pushing tag one: %v", err)
1617 }
1618 }
1619 }
1620
View as plain text