...

Source file src/github.com/google/go-containerregistry/pkg/v1/remote/write_test.go

Documentation: github.com/google/go-containerregistry/pkg/v1/remote

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  	// Append three identical tarball.Layers, which should be deduped
   528  	// because contents can be hashed before uploading.
   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  	// Append three identical stream.Layers, whose uploads will *not* be
   541  	// deduped since Write can't tell they're identical ahead of time.
   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  	// 3 random layers, 1 tarball layer (deduped), 3 stream layers (not deduped), 1 image config blob
   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  	// Hit the existing blob path.
   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  			// TODO(jasonhall): What should we check here?
   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  // If we actually attempt to read the contents, this will fail the test.
  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  	// Set up an image with a foreign layer.
  1346  	base := setupImage(t)
  1347  	img, err := mutate.AppendLayers(base, &fakeForeignLayer{t: t})
  1348  	if err != nil {
  1349  		t.Fatal(err)
  1350  	}
  1351  
  1352  	// Set up a fake registry.
  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  	// Set up an image with a foreign layer.
  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  	// 1 random layer, 1 foreign layer, 1 image config blob
  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  	// Set up a fake registry.
  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  	// Set up a fake registry.
  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  	// Set up a fake registry.
  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  	// unfortunately the registry _and_ the img have caching behaviour, so we need a new registry
  1594  	// and image every iteration of benchmarking.
  1595  	for i := 0; i < b.N; i++ {
  1596  		// set up the registry
  1597  		s := httptest.NewServer(registry.New())
  1598  		defer s.Close()
  1599  
  1600  		// load the image
  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