...

Source file src/github.com/docker/distribution/registry/handlers/api_test.go

Documentation: github.com/docker/distribution/registry/handlers

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

View as plain text