github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/handlers/api_test.go (about)

     1  package handlers
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"net/http/httputil"
    12  	"net/url"
    13  	"os"
    14  	"path"
    15  	"reflect"
    16  	"regexp"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/docker/distribution"
    22  	"github.com/docker/distribution/configuration"
    23  	"github.com/docker/distribution/context"
    24  	"github.com/docker/distribution/digest"
    25  	"github.com/docker/distribution/manifest"
    26  	"github.com/docker/distribution/manifest/manifestlist"
    27  	"github.com/docker/distribution/manifest/schema1"
    28  	"github.com/docker/distribution/manifest/schema2"
    29  	"github.com/docker/distribution/registry/api/errcode"
    30  	"github.com/docker/distribution/registry/api/v2"
    31  	_ "github.com/docker/distribution/registry/storage/driver/inmemory"
    32  	"github.com/docker/distribution/testutil"
    33  	"github.com/docker/libtrust"
    34  	"github.com/gorilla/handlers"
    35  )
    36  
    37  var headerConfig = http.Header{
    38  	"X-Content-Type-Options": []string{"nosniff"},
    39  }
    40  
    41  // TestCheckAPI hits the base endpoint (/v2/) ensures we return the specified
    42  // 200 OK response.
    43  func TestCheckAPI(t *testing.T) {
    44  	env := newTestEnv(t, false)
    45  
    46  	baseURL, err := env.builder.BuildBaseURL()
    47  	if err != nil {
    48  		t.Fatalf("unexpected error building base url: %v", err)
    49  	}
    50  
    51  	resp, err := http.Get(baseURL)
    52  	if err != nil {
    53  		t.Fatalf("unexpected error issuing request: %v", err)
    54  	}
    55  	defer resp.Body.Close()
    56  
    57  	checkResponse(t, "issuing api base check", resp, http.StatusOK)
    58  	checkHeaders(t, resp, http.Header{
    59  		"Content-Type":   []string{"application/json; charset=utf-8"},
    60  		"Content-Length": []string{"2"},
    61  	})
    62  
    63  	p, err := ioutil.ReadAll(resp.Body)
    64  	if err != nil {
    65  		t.Fatalf("unexpected error reading response body: %v", err)
    66  	}
    67  
    68  	if string(p) != "{}" {
    69  		t.Fatalf("unexpected response body: %v", string(p))
    70  	}
    71  }
    72  
    73  // TestCatalogAPI tests the /v2/_catalog endpoint
    74  func TestCatalogAPI(t *testing.T) {
    75  	chunkLen := 2
    76  	env := newTestEnv(t, false)
    77  
    78  	values := url.Values{
    79  		"last": []string{""},
    80  		"n":    []string{strconv.Itoa(chunkLen)}}
    81  
    82  	catalogURL, err := env.builder.BuildCatalogURL(values)
    83  	if err != nil {
    84  		t.Fatalf("unexpected error building catalog url: %v", err)
    85  	}
    86  
    87  	// -----------------------------------
    88  	// try to get an empty catalog
    89  	resp, err := http.Get(catalogURL)
    90  	if err != nil {
    91  		t.Fatalf("unexpected error issuing request: %v", err)
    92  	}
    93  	defer resp.Body.Close()
    94  
    95  	checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
    96  
    97  	var ctlg struct {
    98  		Repositories []string `json:"repositories"`
    99  	}
   100  
   101  	dec := json.NewDecoder(resp.Body)
   102  	if err := dec.Decode(&ctlg); err != nil {
   103  		t.Fatalf("error decoding fetched manifest: %v", err)
   104  	}
   105  
   106  	// we haven't pushed anything to the registry yet
   107  	if len(ctlg.Repositories) != 0 {
   108  		t.Fatalf("repositories has unexpected values")
   109  	}
   110  
   111  	if resp.Header.Get("Link") != "" {
   112  		t.Fatalf("repositories has more data when none expected")
   113  	}
   114  
   115  	// -----------------------------------
   116  	// push something to the registry and try again
   117  	images := []string{"foo/aaaa", "foo/bbbb", "foo/cccc"}
   118  
   119  	for _, image := range images {
   120  		createRepository(env, t, image, "sometag")
   121  	}
   122  
   123  	resp, err = http.Get(catalogURL)
   124  	if err != nil {
   125  		t.Fatalf("unexpected error issuing request: %v", err)
   126  	}
   127  	defer resp.Body.Close()
   128  
   129  	checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
   130  
   131  	dec = json.NewDecoder(resp.Body)
   132  	if err = dec.Decode(&ctlg); err != nil {
   133  		t.Fatalf("error decoding fetched manifest: %v", err)
   134  	}
   135  
   136  	if len(ctlg.Repositories) != chunkLen {
   137  		t.Fatalf("repositories has unexpected values")
   138  	}
   139  
   140  	for _, image := range images[:chunkLen] {
   141  		if !contains(ctlg.Repositories, image) {
   142  			t.Fatalf("didn't find our repository '%s' in the catalog", image)
   143  		}
   144  	}
   145  
   146  	link := resp.Header.Get("Link")
   147  	if link == "" {
   148  		t.Fatalf("repositories has less data than expected")
   149  	}
   150  
   151  	newValues := checkLink(t, link, chunkLen, ctlg.Repositories[len(ctlg.Repositories)-1])
   152  
   153  	// -----------------------------------
   154  	// get the last chunk of data
   155  
   156  	catalogURL, err = env.builder.BuildCatalogURL(newValues)
   157  	if err != nil {
   158  		t.Fatalf("unexpected error building catalog url: %v", err)
   159  	}
   160  
   161  	resp, err = http.Get(catalogURL)
   162  	if err != nil {
   163  		t.Fatalf("unexpected error issuing request: %v", err)
   164  	}
   165  	defer resp.Body.Close()
   166  
   167  	checkResponse(t, "issuing catalog api check", resp, http.StatusOK)
   168  
   169  	dec = json.NewDecoder(resp.Body)
   170  	if err = dec.Decode(&ctlg); err != nil {
   171  		t.Fatalf("error decoding fetched manifest: %v", err)
   172  	}
   173  
   174  	if len(ctlg.Repositories) != 1 {
   175  		t.Fatalf("repositories has unexpected values")
   176  	}
   177  
   178  	lastImage := images[len(images)-1]
   179  	if !contains(ctlg.Repositories, lastImage) {
   180  		t.Fatalf("didn't find our repository '%s' in the catalog", lastImage)
   181  	}
   182  
   183  	link = resp.Header.Get("Link")
   184  	if link != "" {
   185  		t.Fatalf("catalog has unexpected data")
   186  	}
   187  }
   188  
   189  func checkLink(t *testing.T, urlStr string, numEntries int, last string) url.Values {
   190  	re := regexp.MustCompile("<(/v2/_catalog.*)>; rel=\"next\"")
   191  	matches := re.FindStringSubmatch(urlStr)
   192  
   193  	if len(matches) != 2 {
   194  		t.Fatalf("Catalog link address response was incorrect")
   195  	}
   196  	linkURL, _ := url.Parse(matches[1])
   197  	urlValues := linkURL.Query()
   198  
   199  	if urlValues.Get("n") != strconv.Itoa(numEntries) {
   200  		t.Fatalf("Catalog link entry size is incorrect")
   201  	}
   202  
   203  	if urlValues.Get("last") != last {
   204  		t.Fatal("Catalog link last entry is incorrect")
   205  	}
   206  
   207  	return urlValues
   208  }
   209  
   210  func contains(elems []string, e string) bool {
   211  	for _, elem := range elems {
   212  		if elem == e {
   213  			return true
   214  		}
   215  	}
   216  	return false
   217  }
   218  
   219  func TestURLPrefix(t *testing.T) {
   220  	config := configuration.Configuration{
   221  		Storage: configuration.Storage{
   222  			"inmemory": configuration.Parameters{},
   223  		},
   224  	}
   225  	config.HTTP.Prefix = "/test/"
   226  	config.HTTP.Headers = headerConfig
   227  
   228  	env := newTestEnvWithConfig(t, &config)
   229  
   230  	baseURL, err := env.builder.BuildBaseURL()
   231  	if err != nil {
   232  		t.Fatalf("unexpected error building base url: %v", err)
   233  	}
   234  
   235  	parsed, _ := url.Parse(baseURL)
   236  	if !strings.HasPrefix(parsed.Path, config.HTTP.Prefix) {
   237  		t.Fatalf("Prefix %v not included in test url %v", config.HTTP.Prefix, baseURL)
   238  	}
   239  
   240  	resp, err := http.Get(baseURL)
   241  	if err != nil {
   242  		t.Fatalf("unexpected error issuing request: %v", err)
   243  	}
   244  	defer resp.Body.Close()
   245  
   246  	checkResponse(t, "issuing api base check", resp, http.StatusOK)
   247  	checkHeaders(t, resp, http.Header{
   248  		"Content-Type":   []string{"application/json; charset=utf-8"},
   249  		"Content-Length": []string{"2"},
   250  	})
   251  }
   252  
   253  type blobArgs struct {
   254  	imageName   string
   255  	layerFile   io.ReadSeeker
   256  	layerDigest digest.Digest
   257  }
   258  
   259  func makeBlobArgs(t *testing.T) blobArgs {
   260  	layerFile, layerDigest, err := testutil.CreateRandomTarFile()
   261  	if err != nil {
   262  		t.Fatalf("error creating random layer file: %v", err)
   263  	}
   264  
   265  	args := blobArgs{
   266  		imageName:   "foo/bar",
   267  		layerFile:   layerFile,
   268  		layerDigest: layerDigest,
   269  	}
   270  	return args
   271  }
   272  
   273  // TestBlobAPI conducts a full test of the of the blob api.
   274  func TestBlobAPI(t *testing.T) {
   275  	deleteEnabled := false
   276  	env := newTestEnv(t, deleteEnabled)
   277  	args := makeBlobArgs(t)
   278  	testBlobAPI(t, env, args)
   279  
   280  	deleteEnabled = true
   281  	env = newTestEnv(t, deleteEnabled)
   282  	args = makeBlobArgs(t)
   283  	testBlobAPI(t, env, args)
   284  
   285  }
   286  
   287  func TestBlobDelete(t *testing.T) {
   288  	deleteEnabled := true
   289  	env := newTestEnv(t, deleteEnabled)
   290  
   291  	args := makeBlobArgs(t)
   292  	env = testBlobAPI(t, env, args)
   293  	testBlobDelete(t, env, args)
   294  }
   295  
   296  func TestBlobDeleteDisabled(t *testing.T) {
   297  	deleteEnabled := false
   298  	env := newTestEnv(t, deleteEnabled)
   299  	args := makeBlobArgs(t)
   300  
   301  	imageName := args.imageName
   302  	layerDigest := args.layerDigest
   303  	layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
   304  	if err != nil {
   305  		t.Fatalf("error building url: %v", err)
   306  	}
   307  
   308  	resp, err := httpDelete(layerURL)
   309  	if err != nil {
   310  		t.Fatalf("unexpected error deleting when disabled: %v", err)
   311  	}
   312  
   313  	checkResponse(t, "status of disabled delete", resp, http.StatusMethodNotAllowed)
   314  }
   315  
   316  func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv {
   317  	// TODO(stevvooe): This test code is complete junk but it should cover the
   318  	// complete flow. This must be broken down and checked against the
   319  	// specification *before* we submit the final to docker core.
   320  	imageName := args.imageName
   321  	layerFile := args.layerFile
   322  	layerDigest := args.layerDigest
   323  
   324  	// -----------------------------------
   325  	// Test fetch for non-existent content
   326  	layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
   327  	if err != nil {
   328  		t.Fatalf("error building url: %v", err)
   329  	}
   330  
   331  	resp, err := http.Get(layerURL)
   332  	if err != nil {
   333  		t.Fatalf("unexpected error fetching non-existent layer: %v", err)
   334  	}
   335  
   336  	checkResponse(t, "fetching non-existent content", resp, http.StatusNotFound)
   337  
   338  	// ------------------------------------------
   339  	// Test head request for non-existent content
   340  	resp, err = http.Head(layerURL)
   341  	if err != nil {
   342  		t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
   343  	}
   344  
   345  	checkResponse(t, "checking head on non-existent layer", resp, http.StatusNotFound)
   346  
   347  	// ------------------------------------------
   348  	// Start an upload, check the status then cancel
   349  	uploadURLBase, uploadUUID := startPushLayer(t, env.builder, imageName)
   350  
   351  	// A status check should work
   352  	resp, err = http.Get(uploadURLBase)
   353  	if err != nil {
   354  		t.Fatalf("unexpected error getting upload status: %v", err)
   355  	}
   356  	checkResponse(t, "status of deleted upload", resp, http.StatusNoContent)
   357  	checkHeaders(t, resp, http.Header{
   358  		"Location":           []string{"*"},
   359  		"Range":              []string{"0-0"},
   360  		"Docker-Upload-UUID": []string{uploadUUID},
   361  	})
   362  
   363  	req, err := http.NewRequest("DELETE", uploadURLBase, nil)
   364  	if err != nil {
   365  		t.Fatalf("unexpected error creating delete request: %v", err)
   366  	}
   367  
   368  	resp, err = http.DefaultClient.Do(req)
   369  	if err != nil {
   370  		t.Fatalf("unexpected error sending delete request: %v", err)
   371  	}
   372  
   373  	checkResponse(t, "deleting upload", resp, http.StatusNoContent)
   374  
   375  	// A status check should result in 404
   376  	resp, err = http.Get(uploadURLBase)
   377  	if err != nil {
   378  		t.Fatalf("unexpected error getting upload status: %v", err)
   379  	}
   380  	checkResponse(t, "status of deleted upload", resp, http.StatusNotFound)
   381  
   382  	// -----------------------------------------
   383  	// Do layer push with an empty body and different digest
   384  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   385  	resp, err = doPushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, bytes.NewReader([]byte{}))
   386  	if err != nil {
   387  		t.Fatalf("unexpected error doing bad layer push: %v", err)
   388  	}
   389  
   390  	checkResponse(t, "bad layer push", resp, http.StatusBadRequest)
   391  	checkBodyHasErrorCodes(t, "bad layer push", resp, v2.ErrorCodeDigestInvalid)
   392  
   393  	// -----------------------------------------
   394  	// Do layer push with an empty body and correct digest
   395  	zeroDigest, err := digest.FromReader(bytes.NewReader([]byte{}))
   396  	if err != nil {
   397  		t.Fatalf("unexpected error digesting empty buffer: %v", err)
   398  	}
   399  
   400  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   401  	pushLayer(t, env.builder, imageName, zeroDigest, uploadURLBase, bytes.NewReader([]byte{}))
   402  
   403  	// -----------------------------------------
   404  	// Do layer push with an empty body and correct digest
   405  
   406  	// This is a valid but empty tarfile!
   407  	emptyTar := bytes.Repeat([]byte("\x00"), 1024)
   408  	emptyDigest, err := digest.FromReader(bytes.NewReader(emptyTar))
   409  	if err != nil {
   410  		t.Fatalf("unexpected error digesting empty tar: %v", err)
   411  	}
   412  
   413  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   414  	pushLayer(t, env.builder, imageName, emptyDigest, uploadURLBase, bytes.NewReader(emptyTar))
   415  
   416  	// ------------------------------------------
   417  	// Now, actually do successful upload.
   418  	layerLength, _ := layerFile.Seek(0, os.SEEK_END)
   419  	layerFile.Seek(0, os.SEEK_SET)
   420  
   421  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   422  	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
   423  
   424  	// ------------------------------------------
   425  	// Now, push just a chunk
   426  	layerFile.Seek(0, 0)
   427  
   428  	canonicalDigester := digest.Canonical.New()
   429  	if _, err := io.Copy(canonicalDigester.Hash(), layerFile); err != nil {
   430  		t.Fatalf("error copying to digest: %v", err)
   431  	}
   432  	canonicalDigest := canonicalDigester.Digest()
   433  
   434  	layerFile.Seek(0, 0)
   435  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   436  	uploadURLBase, dgst := pushChunk(t, env.builder, imageName, uploadURLBase, layerFile, layerLength)
   437  	finishUpload(t, env.builder, imageName, uploadURLBase, dgst)
   438  
   439  	// ------------------------
   440  	// Use a head request to see if the layer exists.
   441  	resp, err = http.Head(layerURL)
   442  	if err != nil {
   443  		t.Fatalf("unexpected error checking head on existing layer: %v", err)
   444  	}
   445  
   446  	checkResponse(t, "checking head on existing layer", resp, http.StatusOK)
   447  	checkHeaders(t, resp, http.Header{
   448  		"Content-Length":        []string{fmt.Sprint(layerLength)},
   449  		"Docker-Content-Digest": []string{canonicalDigest.String()},
   450  	})
   451  
   452  	// ----------------
   453  	// Fetch the layer!
   454  	resp, err = http.Get(layerURL)
   455  	if err != nil {
   456  		t.Fatalf("unexpected error fetching layer: %v", err)
   457  	}
   458  
   459  	checkResponse(t, "fetching layer", resp, http.StatusOK)
   460  	checkHeaders(t, resp, http.Header{
   461  		"Content-Length":        []string{fmt.Sprint(layerLength)},
   462  		"Docker-Content-Digest": []string{canonicalDigest.String()},
   463  	})
   464  
   465  	// Verify the body
   466  	verifier, err := digest.NewDigestVerifier(layerDigest)
   467  	if err != nil {
   468  		t.Fatalf("unexpected error getting digest verifier: %s", err)
   469  	}
   470  	io.Copy(verifier, resp.Body)
   471  
   472  	if !verifier.Verified() {
   473  		t.Fatalf("response body did not pass verification")
   474  	}
   475  
   476  	// ----------------
   477  	// Fetch the layer with an invalid digest
   478  	badURL := strings.Replace(layerURL, "sha256", "sha257", 1)
   479  	resp, err = http.Get(badURL)
   480  	if err != nil {
   481  		t.Fatalf("unexpected error fetching layer: %v", err)
   482  	}
   483  
   484  	checkResponse(t, "fetching layer bad digest", resp, http.StatusBadRequest)
   485  
   486  	// Cache headers
   487  	resp, err = http.Get(layerURL)
   488  	if err != nil {
   489  		t.Fatalf("unexpected error fetching layer: %v", err)
   490  	}
   491  
   492  	checkResponse(t, "fetching layer", resp, http.StatusOK)
   493  	checkHeaders(t, resp, http.Header{
   494  		"Content-Length":        []string{fmt.Sprint(layerLength)},
   495  		"Docker-Content-Digest": []string{canonicalDigest.String()},
   496  		"ETag":                  []string{fmt.Sprintf(`"%s"`, canonicalDigest)},
   497  		"Cache-Control":         []string{"max-age=31536000"},
   498  	})
   499  
   500  	// Matching etag, gives 304
   501  	etag := resp.Header.Get("Etag")
   502  	req, err = http.NewRequest("GET", layerURL, nil)
   503  	if err != nil {
   504  		t.Fatalf("Error constructing request: %s", err)
   505  	}
   506  	req.Header.Set("If-None-Match", etag)
   507  
   508  	resp, err = http.DefaultClient.Do(req)
   509  	if err != nil {
   510  		t.Fatalf("Error constructing request: %s", err)
   511  	}
   512  
   513  	checkResponse(t, "fetching layer with etag", resp, http.StatusNotModified)
   514  
   515  	// Non-matching etag, gives 200
   516  	req, err = http.NewRequest("GET", layerURL, nil)
   517  	if err != nil {
   518  		t.Fatalf("Error constructing request: %s", err)
   519  	}
   520  	req.Header.Set("If-None-Match", "")
   521  	resp, err = http.DefaultClient.Do(req)
   522  	checkResponse(t, "fetching layer with invalid etag", resp, http.StatusOK)
   523  
   524  	// Missing tests:
   525  	// 	- Upload the same tar file under and different repository and
   526  	//       ensure the content remains uncorrupted.
   527  	return env
   528  }
   529  
   530  func testBlobDelete(t *testing.T, env *testEnv, args blobArgs) {
   531  	// Upload a layer
   532  	imageName := args.imageName
   533  	layerFile := args.layerFile
   534  	layerDigest := args.layerDigest
   535  
   536  	layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
   537  	if err != nil {
   538  		t.Fatalf(err.Error())
   539  	}
   540  	// ---------------
   541  	// Delete a layer
   542  	resp, err := httpDelete(layerURL)
   543  	if err != nil {
   544  		t.Fatalf("unexpected error deleting layer: %v", err)
   545  	}
   546  
   547  	checkResponse(t, "deleting layer", resp, http.StatusAccepted)
   548  	checkHeaders(t, resp, http.Header{
   549  		"Content-Length": []string{"0"},
   550  	})
   551  
   552  	// ---------------
   553  	// Try and get it back
   554  	// Use a head request to see if the layer exists.
   555  	resp, err = http.Head(layerURL)
   556  	if err != nil {
   557  		t.Fatalf("unexpected error checking head on existing layer: %v", err)
   558  	}
   559  
   560  	checkResponse(t, "checking existence of deleted layer", resp, http.StatusNotFound)
   561  
   562  	// Delete already deleted layer
   563  	resp, err = httpDelete(layerURL)
   564  	if err != nil {
   565  		t.Fatalf("unexpected error deleting layer: %v", err)
   566  	}
   567  
   568  	checkResponse(t, "deleting layer", resp, http.StatusNotFound)
   569  
   570  	// ----------------
   571  	// Attempt to delete a layer with an invalid digest
   572  	badURL := strings.Replace(layerURL, "sha256", "sha257", 1)
   573  	resp, err = httpDelete(badURL)
   574  	if err != nil {
   575  		t.Fatalf("unexpected error fetching layer: %v", err)
   576  	}
   577  
   578  	checkResponse(t, "deleting layer bad digest", resp, http.StatusBadRequest)
   579  
   580  	// ----------------
   581  	// Reupload previously deleted blob
   582  	layerFile.Seek(0, os.SEEK_SET)
   583  
   584  	uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
   585  	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
   586  
   587  	layerFile.Seek(0, os.SEEK_SET)
   588  	canonicalDigester := digest.Canonical.New()
   589  	if _, err := io.Copy(canonicalDigester.Hash(), layerFile); err != nil {
   590  		t.Fatalf("error copying to digest: %v", err)
   591  	}
   592  	canonicalDigest := canonicalDigester.Digest()
   593  
   594  	// ------------------------
   595  	// Use a head request to see if it exists
   596  	resp, err = http.Head(layerURL)
   597  	if err != nil {
   598  		t.Fatalf("unexpected error checking head on existing layer: %v", err)
   599  	}
   600  
   601  	layerLength, _ := layerFile.Seek(0, os.SEEK_END)
   602  	checkResponse(t, "checking head on reuploaded layer", resp, http.StatusOK)
   603  	checkHeaders(t, resp, http.Header{
   604  		"Content-Length":        []string{fmt.Sprint(layerLength)},
   605  		"Docker-Content-Digest": []string{canonicalDigest.String()},
   606  	})
   607  }
   608  
   609  func TestDeleteDisabled(t *testing.T) {
   610  	env := newTestEnv(t, false)
   611  
   612  	imageName := "foo/bar"
   613  	// "build" our layer file
   614  	layerFile, layerDigest, err := testutil.CreateRandomTarFile()
   615  	if err != nil {
   616  		t.Fatalf("error creating random layer file: %v", err)
   617  	}
   618  
   619  	layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
   620  	if err != nil {
   621  		t.Fatalf("Error building blob URL")
   622  	}
   623  	uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
   624  	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
   625  
   626  	resp, err := httpDelete(layerURL)
   627  	if err != nil {
   628  		t.Fatalf("unexpected error deleting layer: %v", err)
   629  	}
   630  
   631  	checkResponse(t, "deleting layer with delete disabled", resp, http.StatusMethodNotAllowed)
   632  }
   633  
   634  func TestDeleteReadOnly(t *testing.T) {
   635  	env := newTestEnv(t, true)
   636  
   637  	imageName := "foo/bar"
   638  	// "build" our layer file
   639  	layerFile, layerDigest, err := testutil.CreateRandomTarFile()
   640  	if err != nil {
   641  		t.Fatalf("error creating random layer file: %v", err)
   642  	}
   643  
   644  	layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
   645  	if err != nil {
   646  		t.Fatalf("Error building blob URL")
   647  	}
   648  	uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
   649  	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
   650  
   651  	env.app.readOnly = true
   652  
   653  	resp, err := httpDelete(layerURL)
   654  	if err != nil {
   655  		t.Fatalf("unexpected error deleting layer: %v", err)
   656  	}
   657  
   658  	checkResponse(t, "deleting layer in read-only mode", resp, http.StatusMethodNotAllowed)
   659  }
   660  
   661  func TestStartPushReadOnly(t *testing.T) {
   662  	env := newTestEnv(t, true)
   663  	env.app.readOnly = true
   664  
   665  	imageName := "foo/bar"
   666  
   667  	layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
   668  	if err != nil {
   669  		t.Fatalf("unexpected error building layer upload url: %v", err)
   670  	}
   671  
   672  	resp, err := http.Post(layerUploadURL, "", nil)
   673  	if err != nil {
   674  		t.Fatalf("unexpected error starting layer push: %v", err)
   675  	}
   676  	defer resp.Body.Close()
   677  
   678  	checkResponse(t, "starting push in read-only mode", resp, http.StatusMethodNotAllowed)
   679  }
   680  
   681  func httpDelete(url string) (*http.Response, error) {
   682  	req, err := http.NewRequest("DELETE", url, nil)
   683  	if err != nil {
   684  		return nil, err
   685  	}
   686  
   687  	resp, err := http.DefaultClient.Do(req)
   688  	if err != nil {
   689  		return nil, err
   690  	}
   691  	//	defer resp.Body.Close()
   692  	return resp, err
   693  }
   694  
   695  type manifestArgs struct {
   696  	imageName string
   697  	mediaType string
   698  	manifest  distribution.Manifest
   699  	dgst      digest.Digest
   700  }
   701  
   702  func TestManifestAPI(t *testing.T) {
   703  	deleteEnabled := false
   704  	env := newTestEnv(t, deleteEnabled)
   705  	testManifestAPISchema1(t, env, "foo/schema1")
   706  	schema2Args := testManifestAPISchema2(t, env, "foo/schema2")
   707  	testManifestAPIManifestList(t, env, schema2Args)
   708  
   709  	deleteEnabled = true
   710  	env = newTestEnv(t, deleteEnabled)
   711  	testManifestAPISchema1(t, env, "foo/schema1")
   712  	schema2Args = testManifestAPISchema2(t, env, "foo/schema2")
   713  	testManifestAPIManifestList(t, env, schema2Args)
   714  }
   715  
   716  func TestManifestDelete(t *testing.T) {
   717  	deleteEnabled := true
   718  	env := newTestEnv(t, deleteEnabled)
   719  	schema1Args := testManifestAPISchema1(t, env, "foo/schema1")
   720  	testManifestDelete(t, env, schema1Args)
   721  	schema2Args := testManifestAPISchema2(t, env, "foo/schema2")
   722  	testManifestDelete(t, env, schema2Args)
   723  }
   724  
   725  func TestManifestDeleteDisabled(t *testing.T) {
   726  	deleteEnabled := false
   727  	env := newTestEnv(t, deleteEnabled)
   728  	testManifestDeleteDisabled(t, env, "foo/schema1")
   729  }
   730  
   731  func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName string) {
   732  	manifestURL, err := env.builder.BuildManifestURL(imageName, digest.DigestSha256EmptyTar)
   733  	if err != nil {
   734  		t.Fatalf("unexpected error getting manifest url: %v", err)
   735  	}
   736  
   737  	resp, err := httpDelete(manifestURL)
   738  	if err != nil {
   739  		t.Fatalf("unexpected error deleting manifest %v", err)
   740  	}
   741  	defer resp.Body.Close()
   742  
   743  	checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
   744  }
   745  
   746  func testManifestAPISchema1(t *testing.T, env *testEnv, imageName string) manifestArgs {
   747  	tag := "thetag"
   748  	args := manifestArgs{imageName: imageName}
   749  
   750  	manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
   751  	if err != nil {
   752  		t.Fatalf("unexpected error getting manifest url: %v", err)
   753  	}
   754  
   755  	// -----------------------------
   756  	// Attempt to fetch the manifest
   757  	resp, err := http.Get(manifestURL)
   758  	if err != nil {
   759  		t.Fatalf("unexpected error getting manifest: %v", err)
   760  	}
   761  	defer resp.Body.Close()
   762  
   763  	checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
   764  	checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
   765  
   766  	tagsURL, err := env.builder.BuildTagsURL(imageName)
   767  	if err != nil {
   768  		t.Fatalf("unexpected error building tags url: %v", err)
   769  	}
   770  
   771  	resp, err = http.Get(tagsURL)
   772  	if err != nil {
   773  		t.Fatalf("unexpected error getting unknown tags: %v", err)
   774  	}
   775  	defer resp.Body.Close()
   776  
   777  	// Check that we get an unknown repository error when asking for tags
   778  	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
   779  	checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
   780  
   781  	// --------------------------------
   782  	// Attempt to push unsigned manifest with missing layers
   783  	unsignedManifest := &schema1.Manifest{
   784  		Versioned: manifest.Versioned{
   785  			SchemaVersion: 1,
   786  		},
   787  		Name: imageName,
   788  		Tag:  tag,
   789  		FSLayers: []schema1.FSLayer{
   790  			{
   791  				BlobSum: "asdf",
   792  			},
   793  			{
   794  				BlobSum: "qwer",
   795  			},
   796  		},
   797  		History: []schema1.History{
   798  			{
   799  				V1Compatibility: "",
   800  			},
   801  			{
   802  				V1Compatibility: "",
   803  			},
   804  		},
   805  	}
   806  
   807  	resp = putManifest(t, "putting unsigned manifest", manifestURL, "", unsignedManifest)
   808  	defer resp.Body.Close()
   809  	checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
   810  	_, p, counts := checkBodyHasErrorCodes(t, "putting unsigned manifest", resp, v2.ErrorCodeManifestInvalid)
   811  
   812  	expectedCounts := map[errcode.ErrorCode]int{
   813  		v2.ErrorCodeManifestInvalid: 1,
   814  	}
   815  
   816  	if !reflect.DeepEqual(counts, expectedCounts) {
   817  		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
   818  	}
   819  
   820  	// sign the manifest and still get some interesting errors.
   821  	sm, err := schema1.Sign(unsignedManifest, env.pk)
   822  	if err != nil {
   823  		t.Fatalf("error signing manifest: %v", err)
   824  	}
   825  
   826  	resp = putManifest(t, "putting signed manifest with errors", manifestURL, "", sm)
   827  	defer resp.Body.Close()
   828  	checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
   829  	_, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
   830  		v2.ErrorCodeManifestBlobUnknown, v2.ErrorCodeDigestInvalid)
   831  
   832  	expectedCounts = map[errcode.ErrorCode]int{
   833  		v2.ErrorCodeManifestBlobUnknown: 2,
   834  		v2.ErrorCodeDigestInvalid:       2,
   835  	}
   836  
   837  	if !reflect.DeepEqual(counts, expectedCounts) {
   838  		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
   839  	}
   840  
   841  	// TODO(stevvooe): Add a test case where we take a mostly valid registry,
   842  	// tamper with the content and ensure that we get a unverified manifest
   843  	// error.
   844  
   845  	// Push 2 random layers
   846  	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
   847  
   848  	for i := range unsignedManifest.FSLayers {
   849  		rs, dgstStr, err := testutil.CreateRandomTarFile()
   850  
   851  		if err != nil {
   852  			t.Fatalf("error creating random layer %d: %v", i, err)
   853  		}
   854  		dgst := digest.Digest(dgstStr)
   855  
   856  		expectedLayers[dgst] = rs
   857  		unsignedManifest.FSLayers[i].BlobSum = dgst
   858  
   859  		uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
   860  		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
   861  	}
   862  
   863  	// -------------------
   864  	// Push the signed manifest with all layers pushed.
   865  	signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
   866  	if err != nil {
   867  		t.Fatalf("unexpected error signing manifest: %v", err)
   868  	}
   869  
   870  	dgst := digest.FromBytes(signedManifest.Canonical)
   871  	args.manifest = signedManifest
   872  	args.dgst = dgst
   873  
   874  	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
   875  	checkErr(t, err, "building manifest url")
   876  
   877  	resp = putManifest(t, "putting signed manifest no error", manifestURL, "", signedManifest)
   878  	checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
   879  	checkHeaders(t, resp, http.Header{
   880  		"Location":              []string{manifestDigestURL},
   881  		"Docker-Content-Digest": []string{dgst.String()},
   882  	})
   883  
   884  	// --------------------
   885  	// Push by digest -- should get same result
   886  	resp = putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
   887  	checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
   888  	checkHeaders(t, resp, http.Header{
   889  		"Location":              []string{manifestDigestURL},
   890  		"Docker-Content-Digest": []string{dgst.String()},
   891  	})
   892  
   893  	// ------------------
   894  	// Fetch by tag name
   895  	resp, err = http.Get(manifestURL)
   896  	if err != nil {
   897  		t.Fatalf("unexpected error fetching manifest: %v", err)
   898  	}
   899  	defer resp.Body.Close()
   900  
   901  	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
   902  	checkHeaders(t, resp, http.Header{
   903  		"Docker-Content-Digest": []string{dgst.String()},
   904  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
   905  	})
   906  
   907  	var fetchedManifest schema1.SignedManifest
   908  	dec := json.NewDecoder(resp.Body)
   909  
   910  	if err := dec.Decode(&fetchedManifest); err != nil {
   911  		t.Fatalf("error decoding fetched manifest: %v", err)
   912  	}
   913  
   914  	if !bytes.Equal(fetchedManifest.Canonical, signedManifest.Canonical) {
   915  		t.Fatalf("manifests do not match")
   916  	}
   917  
   918  	// ---------------
   919  	// Fetch by digest
   920  	resp, err = http.Get(manifestDigestURL)
   921  	checkErr(t, err, "fetching manifest by digest")
   922  	defer resp.Body.Close()
   923  
   924  	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
   925  	checkHeaders(t, resp, http.Header{
   926  		"Docker-Content-Digest": []string{dgst.String()},
   927  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
   928  	})
   929  
   930  	var fetchedManifestByDigest schema1.SignedManifest
   931  	dec = json.NewDecoder(resp.Body)
   932  	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
   933  		t.Fatalf("error decoding fetched manifest: %v", err)
   934  	}
   935  
   936  	if !bytes.Equal(fetchedManifestByDigest.Canonical, signedManifest.Canonical) {
   937  		t.Fatalf("manifests do not match")
   938  	}
   939  
   940  	// check signature was roundtripped
   941  	signatures, err := fetchedManifestByDigest.Signatures()
   942  	if err != nil {
   943  		t.Fatal(err)
   944  	}
   945  
   946  	if len(signatures) != 1 {
   947  		t.Fatalf("expected 1 signature from manifest, got: %d", len(signatures))
   948  	}
   949  
   950  	// Re-sign, push and pull the same digest
   951  	sm2, err := schema1.Sign(&fetchedManifestByDigest.Manifest, env.pk)
   952  	if err != nil {
   953  		t.Fatal(err)
   954  
   955  	}
   956  
   957  	resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "", sm2)
   958  	checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
   959  
   960  	resp, err = http.Get(manifestDigestURL)
   961  	checkErr(t, err, "re-fetching manifest by digest")
   962  	defer resp.Body.Close()
   963  
   964  	checkResponse(t, "re-fetching uploaded manifest", resp, http.StatusOK)
   965  	checkHeaders(t, resp, http.Header{
   966  		"Docker-Content-Digest": []string{dgst.String()},
   967  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
   968  	})
   969  
   970  	dec = json.NewDecoder(resp.Body)
   971  	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
   972  		t.Fatalf("error decoding fetched manifest: %v", err)
   973  	}
   974  
   975  	// check two signatures were roundtripped
   976  	signatures, err = fetchedManifestByDigest.Signatures()
   977  	if err != nil {
   978  		t.Fatal(err)
   979  	}
   980  
   981  	if len(signatures) != 2 {
   982  		t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures))
   983  	}
   984  
   985  	// Get by name with etag, gives 304
   986  	etag := resp.Header.Get("Etag")
   987  	req, err := http.NewRequest("GET", manifestURL, nil)
   988  	if err != nil {
   989  		t.Fatalf("Error constructing request: %s", err)
   990  	}
   991  	req.Header.Set("If-None-Match", etag)
   992  	resp, err = http.DefaultClient.Do(req)
   993  	if err != nil {
   994  		t.Fatalf("Error constructing request: %s", err)
   995  	}
   996  
   997  	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
   998  
   999  	// Get by digest with etag, gives 304
  1000  	req, err = http.NewRequest("GET", manifestDigestURL, nil)
  1001  	if err != nil {
  1002  		t.Fatalf("Error constructing request: %s", err)
  1003  	}
  1004  	req.Header.Set("If-None-Match", etag)
  1005  	resp, err = http.DefaultClient.Do(req)
  1006  	if err != nil {
  1007  		t.Fatalf("Error constructing request: %s", err)
  1008  	}
  1009  
  1010  	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
  1011  
  1012  	// Ensure that the tag is listed.
  1013  	resp, err = http.Get(tagsURL)
  1014  	if err != nil {
  1015  		t.Fatalf("unexpected error getting unknown tags: %v", err)
  1016  	}
  1017  	defer resp.Body.Close()
  1018  
  1019  	checkResponse(t, "getting tags", resp, http.StatusOK)
  1020  	dec = json.NewDecoder(resp.Body)
  1021  
  1022  	var tagsResponse tagsAPIResponse
  1023  
  1024  	if err := dec.Decode(&tagsResponse); err != nil {
  1025  		t.Fatalf("unexpected error decoding error response: %v", err)
  1026  	}
  1027  
  1028  	if tagsResponse.Name != imageName {
  1029  		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
  1030  	}
  1031  
  1032  	if len(tagsResponse.Tags) != 1 {
  1033  		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
  1034  	}
  1035  
  1036  	if tagsResponse.Tags[0] != tag {
  1037  		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
  1038  	}
  1039  
  1040  	// Attempt to put a manifest with mismatching FSLayer and History array cardinalities
  1041  
  1042  	unsignedManifest.History = append(unsignedManifest.History, schema1.History{
  1043  		V1Compatibility: "",
  1044  	})
  1045  	invalidSigned, err := schema1.Sign(unsignedManifest, env.pk)
  1046  	if err != nil {
  1047  		t.Fatalf("error signing manifest")
  1048  	}
  1049  
  1050  	resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, "", invalidSigned)
  1051  	checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)
  1052  
  1053  	return args
  1054  }
  1055  
  1056  func testManifestAPISchema2(t *testing.T, env *testEnv, imageName string) manifestArgs {
  1057  	tag := "schema2tag"
  1058  	args := manifestArgs{
  1059  		imageName: imageName,
  1060  		mediaType: schema2.MediaTypeManifest,
  1061  	}
  1062  
  1063  	manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
  1064  	if err != nil {
  1065  		t.Fatalf("unexpected error getting manifest url: %v", err)
  1066  	}
  1067  
  1068  	// -----------------------------
  1069  	// Attempt to fetch the manifest
  1070  	resp, err := http.Get(manifestURL)
  1071  	if err != nil {
  1072  		t.Fatalf("unexpected error getting manifest: %v", err)
  1073  	}
  1074  	defer resp.Body.Close()
  1075  
  1076  	checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
  1077  	checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
  1078  
  1079  	tagsURL, err := env.builder.BuildTagsURL(imageName)
  1080  	if err != nil {
  1081  		t.Fatalf("unexpected error building tags url: %v", err)
  1082  	}
  1083  
  1084  	resp, err = http.Get(tagsURL)
  1085  	if err != nil {
  1086  		t.Fatalf("unexpected error getting unknown tags: %v", err)
  1087  	}
  1088  	defer resp.Body.Close()
  1089  
  1090  	// Check that we get an unknown repository error when asking for tags
  1091  	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
  1092  	checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
  1093  
  1094  	// --------------------------------
  1095  	// Attempt to push manifest with missing config and missing layers
  1096  	manifest := &schema2.Manifest{
  1097  		Versioned: manifest.Versioned{
  1098  			SchemaVersion: 2,
  1099  			MediaType:     schema2.MediaTypeManifest,
  1100  		},
  1101  		Config: distribution.Descriptor{
  1102  			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
  1103  			Size:      3253,
  1104  			MediaType: schema2.MediaTypeConfig,
  1105  		},
  1106  		Layers: []distribution.Descriptor{
  1107  			{
  1108  				Digest:    "sha256:463434349086340864309863409683460843608348608934092322395278926a",
  1109  				Size:      6323,
  1110  				MediaType: schema2.MediaTypeLayer,
  1111  			},
  1112  			{
  1113  				Digest:    "sha256:630923423623623423352523525237238023652897356239852383652aaaaaaa",
  1114  				Size:      6863,
  1115  				MediaType: schema2.MediaTypeLayer,
  1116  			},
  1117  		},
  1118  	}
  1119  
  1120  	resp = putManifest(t, "putting missing config manifest", manifestURL, schema2.MediaTypeManifest, manifest)
  1121  	defer resp.Body.Close()
  1122  	checkResponse(t, "putting missing config manifest", resp, http.StatusBadRequest)
  1123  	_, p, counts := checkBodyHasErrorCodes(t, "putting missing config manifest", resp, v2.ErrorCodeManifestBlobUnknown)
  1124  
  1125  	expectedCounts := map[errcode.ErrorCode]int{
  1126  		v2.ErrorCodeManifestBlobUnknown: 3,
  1127  	}
  1128  
  1129  	if !reflect.DeepEqual(counts, expectedCounts) {
  1130  		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
  1131  	}
  1132  
  1133  	// Push a config, and reference it in the manifest
  1134  	sampleConfig := []byte(`{
  1135  		"architecture": "amd64",
  1136  		"history": [
  1137  		  {
  1138  		    "created": "2015-10-31T22:22:54.690851953Z",
  1139  		    "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
  1140  		  },
  1141  		  {
  1142  		    "created": "2015-10-31T22:22:55.613815829Z",
  1143  		    "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
  1144  		  }
  1145  		],
  1146  		"rootfs": {
  1147  		  "diff_ids": [
  1148  		    "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
  1149  		    "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
  1150  		  ],
  1151  		  "type": "layers"
  1152  		}
  1153  	}`)
  1154  	sampleConfigDigest := digest.FromBytes(sampleConfig)
  1155  
  1156  	uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
  1157  	pushLayer(t, env.builder, imageName, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig))
  1158  	manifest.Config.Digest = sampleConfigDigest
  1159  	manifest.Config.Size = int64(len(sampleConfig))
  1160  
  1161  	// The manifest should still be invalid, because its layer doesnt exist
  1162  	resp = putManifest(t, "putting missing layer manifest", manifestURL, schema2.MediaTypeManifest, manifest)
  1163  	defer resp.Body.Close()
  1164  	checkResponse(t, "putting missing layer manifest", resp, http.StatusBadRequest)
  1165  	_, p, counts = checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestBlobUnknown)
  1166  
  1167  	expectedCounts = map[errcode.ErrorCode]int{
  1168  		v2.ErrorCodeManifestBlobUnknown: 2,
  1169  	}
  1170  
  1171  	if !reflect.DeepEqual(counts, expectedCounts) {
  1172  		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
  1173  	}
  1174  
  1175  	// Push 2 random layers
  1176  	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
  1177  
  1178  	for i := range manifest.Layers {
  1179  		rs, dgstStr, err := testutil.CreateRandomTarFile()
  1180  
  1181  		if err != nil {
  1182  			t.Fatalf("error creating random layer %d: %v", i, err)
  1183  		}
  1184  		dgst := digest.Digest(dgstStr)
  1185  
  1186  		expectedLayers[dgst] = rs
  1187  		manifest.Layers[i].Digest = dgst
  1188  
  1189  		uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
  1190  		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
  1191  	}
  1192  
  1193  	// -------------------
  1194  	// Push the manifest with all layers pushed.
  1195  	deserializedManifest, err := schema2.FromStruct(*manifest)
  1196  	if err != nil {
  1197  		t.Fatalf("could not create DeserializedManifest: %v", err)
  1198  	}
  1199  	_, canonical, err := deserializedManifest.Payload()
  1200  	if err != nil {
  1201  		t.Fatalf("could not get manifest payload: %v", err)
  1202  	}
  1203  	dgst := digest.FromBytes(canonical)
  1204  	args.dgst = dgst
  1205  	args.manifest = deserializedManifest
  1206  
  1207  	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
  1208  	checkErr(t, err, "building manifest url")
  1209  
  1210  	resp = putManifest(t, "putting manifest no error", manifestURL, schema2.MediaTypeManifest, manifest)
  1211  	checkResponse(t, "putting manifest no error", resp, http.StatusCreated)
  1212  	checkHeaders(t, resp, http.Header{
  1213  		"Location":              []string{manifestDigestURL},
  1214  		"Docker-Content-Digest": []string{dgst.String()},
  1215  	})
  1216  
  1217  	// --------------------
  1218  	// Push by digest -- should get same result
  1219  	resp = putManifest(t, "putting manifest by digest", manifestDigestURL, schema2.MediaTypeManifest, manifest)
  1220  	checkResponse(t, "putting manifest by digest", resp, http.StatusCreated)
  1221  	checkHeaders(t, resp, http.Header{
  1222  		"Location":              []string{manifestDigestURL},
  1223  		"Docker-Content-Digest": []string{dgst.String()},
  1224  	})
  1225  
  1226  	// ------------------
  1227  	// Fetch by tag name
  1228  	req, err := http.NewRequest("GET", manifestURL, nil)
  1229  	if err != nil {
  1230  		t.Fatalf("Error constructing request: %s", err)
  1231  	}
  1232  	req.Header.Set("Accept", schema2.MediaTypeManifest)
  1233  	resp, err = http.DefaultClient.Do(req)
  1234  	if err != nil {
  1235  		t.Fatalf("unexpected error fetching manifest: %v", err)
  1236  	}
  1237  	defer resp.Body.Close()
  1238  
  1239  	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
  1240  	checkHeaders(t, resp, http.Header{
  1241  		"Docker-Content-Digest": []string{dgst.String()},
  1242  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
  1243  	})
  1244  
  1245  	var fetchedManifest schema2.DeserializedManifest
  1246  	dec := json.NewDecoder(resp.Body)
  1247  
  1248  	if err := dec.Decode(&fetchedManifest); err != nil {
  1249  		t.Fatalf("error decoding fetched manifest: %v", err)
  1250  	}
  1251  
  1252  	_, fetchedCanonical, err := fetchedManifest.Payload()
  1253  	if err != nil {
  1254  		t.Fatalf("error getting manifest payload: %v", err)
  1255  	}
  1256  
  1257  	if !bytes.Equal(fetchedCanonical, canonical) {
  1258  		t.Fatalf("manifests do not match")
  1259  	}
  1260  
  1261  	// ---------------
  1262  	// Fetch by digest
  1263  	req, err = http.NewRequest("GET", manifestDigestURL, nil)
  1264  	if err != nil {
  1265  		t.Fatalf("Error constructing request: %s", err)
  1266  	}
  1267  	req.Header.Set("Accept", schema2.MediaTypeManifest)
  1268  	resp, err = http.DefaultClient.Do(req)
  1269  	checkErr(t, err, "fetching manifest by digest")
  1270  	defer resp.Body.Close()
  1271  
  1272  	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
  1273  	checkHeaders(t, resp, http.Header{
  1274  		"Docker-Content-Digest": []string{dgst.String()},
  1275  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
  1276  	})
  1277  
  1278  	var fetchedManifestByDigest schema2.DeserializedManifest
  1279  	dec = json.NewDecoder(resp.Body)
  1280  	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
  1281  		t.Fatalf("error decoding fetched manifest: %v", err)
  1282  	}
  1283  
  1284  	_, fetchedCanonical, err = fetchedManifest.Payload()
  1285  	if err != nil {
  1286  		t.Fatalf("error getting manifest payload: %v", err)
  1287  	}
  1288  
  1289  	if !bytes.Equal(fetchedCanonical, canonical) {
  1290  		t.Fatalf("manifests do not match")
  1291  	}
  1292  
  1293  	// Get by name with etag, gives 304
  1294  	etag := resp.Header.Get("Etag")
  1295  	req, err = http.NewRequest("GET", manifestURL, nil)
  1296  	if err != nil {
  1297  		t.Fatalf("Error constructing request: %s", err)
  1298  	}
  1299  	req.Header.Set("If-None-Match", etag)
  1300  	resp, err = http.DefaultClient.Do(req)
  1301  	if err != nil {
  1302  		t.Fatalf("Error constructing request: %s", err)
  1303  	}
  1304  
  1305  	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
  1306  
  1307  	// Get by digest with etag, gives 304
  1308  	req, err = http.NewRequest("GET", manifestDigestURL, nil)
  1309  	if err != nil {
  1310  		t.Fatalf("Error constructing request: %s", err)
  1311  	}
  1312  	req.Header.Set("If-None-Match", etag)
  1313  	resp, err = http.DefaultClient.Do(req)
  1314  	if err != nil {
  1315  		t.Fatalf("Error constructing request: %s", err)
  1316  	}
  1317  
  1318  	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
  1319  
  1320  	// Ensure that the tag is listed.
  1321  	resp, err = http.Get(tagsURL)
  1322  	if err != nil {
  1323  		t.Fatalf("unexpected error getting unknown tags: %v", err)
  1324  	}
  1325  	defer resp.Body.Close()
  1326  
  1327  	checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
  1328  	dec = json.NewDecoder(resp.Body)
  1329  
  1330  	var tagsResponse tagsAPIResponse
  1331  
  1332  	if err := dec.Decode(&tagsResponse); err != nil {
  1333  		t.Fatalf("unexpected error decoding error response: %v", err)
  1334  	}
  1335  
  1336  	if tagsResponse.Name != imageName {
  1337  		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
  1338  	}
  1339  
  1340  	if len(tagsResponse.Tags) != 1 {
  1341  		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
  1342  	}
  1343  
  1344  	if tagsResponse.Tags[0] != tag {
  1345  		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
  1346  	}
  1347  
  1348  	// ------------------
  1349  	// Fetch as a schema1 manifest
  1350  	resp, err = http.Get(manifestURL)
  1351  	if err != nil {
  1352  		t.Fatalf("unexpected error fetching manifest as schema1: %v", err)
  1353  	}
  1354  	defer resp.Body.Close()
  1355  
  1356  	checkResponse(t, "fetching uploaded manifest as schema1", resp, http.StatusOK)
  1357  	checkHeaders(t, resp, http.Header{
  1358  		"Docker-Content-Digest": []string{dgst.String()},
  1359  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
  1360  	})
  1361  
  1362  	var fetchedSchema1Manifest schema1.SignedManifest
  1363  	dec = json.NewDecoder(resp.Body)
  1364  
  1365  	if err := dec.Decode(&fetchedSchema1Manifest); err != nil {
  1366  		t.Fatalf("error decoding fetched schema1 manifest: %v", err)
  1367  	}
  1368  
  1369  	if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
  1370  		t.Fatal("wrong schema version")
  1371  	}
  1372  	if fetchedSchema1Manifest.Architecture != "amd64" {
  1373  		t.Fatal("wrong architecture")
  1374  	}
  1375  	if fetchedSchema1Manifest.Name != imageName {
  1376  		t.Fatal("wrong image name")
  1377  	}
  1378  	if fetchedSchema1Manifest.Tag != tag {
  1379  		t.Fatal("wrong tag")
  1380  	}
  1381  	if len(fetchedSchema1Manifest.FSLayers) != 2 {
  1382  		t.Fatal("wrong number of FSLayers")
  1383  	}
  1384  	for i := range manifest.Layers {
  1385  		if fetchedSchema1Manifest.FSLayers[i].BlobSum != manifest.Layers[len(manifest.Layers)-i-1].Digest {
  1386  			t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
  1387  		}
  1388  	}
  1389  	if len(fetchedSchema1Manifest.History) != 2 {
  1390  		t.Fatal("wrong number of History entries")
  1391  	}
  1392  
  1393  	// Don't check V1Compatibility fields becuase we're using randomly-generated
  1394  	// layers.
  1395  
  1396  	return args
  1397  }
  1398  
  1399  func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs) {
  1400  	imageName := args.imageName
  1401  	tag := "manifestlisttag"
  1402  
  1403  	manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
  1404  	if err != nil {
  1405  		t.Fatalf("unexpected error getting manifest url: %v", err)
  1406  	}
  1407  
  1408  	// --------------------------------
  1409  	// Attempt to push manifest list that refers to an unknown manifest
  1410  	manifestList := &manifestlist.ManifestList{
  1411  		Versioned: manifest.Versioned{
  1412  			SchemaVersion: 2,
  1413  			MediaType:     manifestlist.MediaTypeManifestList,
  1414  		},
  1415  		Manifests: []manifestlist.ManifestDescriptor{
  1416  			{
  1417  				Descriptor: distribution.Descriptor{
  1418  					Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
  1419  					Size:      3253,
  1420  					MediaType: schema2.MediaTypeManifest,
  1421  				},
  1422  				Platform: manifestlist.PlatformSpec{
  1423  					Architecture: "amd64",
  1424  					OS:           "linux",
  1425  				},
  1426  			},
  1427  		},
  1428  	}
  1429  
  1430  	resp := putManifest(t, "putting missing manifest manifestlist", manifestURL, manifestlist.MediaTypeManifestList, manifestList)
  1431  	defer resp.Body.Close()
  1432  	checkResponse(t, "putting missing manifest manifestlist", resp, http.StatusBadRequest)
  1433  	_, p, counts := checkBodyHasErrorCodes(t, "putting missing manifest manifestlist", resp, v2.ErrorCodeManifestBlobUnknown)
  1434  
  1435  	expectedCounts := map[errcode.ErrorCode]int{
  1436  		v2.ErrorCodeManifestBlobUnknown: 1,
  1437  	}
  1438  
  1439  	if !reflect.DeepEqual(counts, expectedCounts) {
  1440  		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
  1441  	}
  1442  
  1443  	// -------------------
  1444  	// Push a manifest list that references an actual manifest
  1445  	manifestList.Manifests[0].Digest = args.dgst
  1446  	deserializedManifestList, err := manifestlist.FromDescriptors(manifestList.Manifests)
  1447  	if err != nil {
  1448  		t.Fatalf("could not create DeserializedManifestList: %v", err)
  1449  	}
  1450  	_, canonical, err := deserializedManifestList.Payload()
  1451  	if err != nil {
  1452  		t.Fatalf("could not get manifest list payload: %v", err)
  1453  	}
  1454  	dgst := digest.FromBytes(canonical)
  1455  
  1456  	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
  1457  	checkErr(t, err, "building manifest url")
  1458  
  1459  	resp = putManifest(t, "putting manifest list no error", manifestURL, manifestlist.MediaTypeManifestList, deserializedManifestList)
  1460  	checkResponse(t, "putting manifest list no error", resp, http.StatusCreated)
  1461  	checkHeaders(t, resp, http.Header{
  1462  		"Location":              []string{manifestDigestURL},
  1463  		"Docker-Content-Digest": []string{dgst.String()},
  1464  	})
  1465  
  1466  	// --------------------
  1467  	// Push by digest -- should get same result
  1468  	resp = putManifest(t, "putting manifest list by digest", manifestDigestURL, manifestlist.MediaTypeManifestList, deserializedManifestList)
  1469  	checkResponse(t, "putting manifest list by digest", resp, http.StatusCreated)
  1470  	checkHeaders(t, resp, http.Header{
  1471  		"Location":              []string{manifestDigestURL},
  1472  		"Docker-Content-Digest": []string{dgst.String()},
  1473  	})
  1474  
  1475  	// ------------------
  1476  	// Fetch by tag name
  1477  	req, err := http.NewRequest("GET", manifestURL, nil)
  1478  	if err != nil {
  1479  		t.Fatalf("Error constructing request: %s", err)
  1480  	}
  1481  	req.Header.Set("Accept", manifestlist.MediaTypeManifestList)
  1482  	req.Header.Add("Accept", schema1.MediaTypeManifest)
  1483  	req.Header.Add("Accept", schema2.MediaTypeManifest)
  1484  	resp, err = http.DefaultClient.Do(req)
  1485  	if err != nil {
  1486  		t.Fatalf("unexpected error fetching manifest list: %v", err)
  1487  	}
  1488  	defer resp.Body.Close()
  1489  
  1490  	checkResponse(t, "fetching uploaded manifest list", resp, http.StatusOK)
  1491  	checkHeaders(t, resp, http.Header{
  1492  		"Docker-Content-Digest": []string{dgst.String()},
  1493  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
  1494  	})
  1495  
  1496  	var fetchedManifestList manifestlist.DeserializedManifestList
  1497  	dec := json.NewDecoder(resp.Body)
  1498  
  1499  	if err := dec.Decode(&fetchedManifestList); err != nil {
  1500  		t.Fatalf("error decoding fetched manifest list: %v", err)
  1501  	}
  1502  
  1503  	_, fetchedCanonical, err := fetchedManifestList.Payload()
  1504  	if err != nil {
  1505  		t.Fatalf("error getting manifest list payload: %v", err)
  1506  	}
  1507  
  1508  	if !bytes.Equal(fetchedCanonical, canonical) {
  1509  		t.Fatalf("manifest lists do not match")
  1510  	}
  1511  
  1512  	// ---------------
  1513  	// Fetch by digest
  1514  	req, err = http.NewRequest("GET", manifestDigestURL, nil)
  1515  	if err != nil {
  1516  		t.Fatalf("Error constructing request: %s", err)
  1517  	}
  1518  	req.Header.Set("Accept", manifestlist.MediaTypeManifestList)
  1519  	resp, err = http.DefaultClient.Do(req)
  1520  	checkErr(t, err, "fetching manifest list by digest")
  1521  	defer resp.Body.Close()
  1522  
  1523  	checkResponse(t, "fetching uploaded manifest list", resp, http.StatusOK)
  1524  	checkHeaders(t, resp, http.Header{
  1525  		"Docker-Content-Digest": []string{dgst.String()},
  1526  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
  1527  	})
  1528  
  1529  	var fetchedManifestListByDigest manifestlist.DeserializedManifestList
  1530  	dec = json.NewDecoder(resp.Body)
  1531  	if err := dec.Decode(&fetchedManifestListByDigest); err != nil {
  1532  		t.Fatalf("error decoding fetched manifest: %v", err)
  1533  	}
  1534  
  1535  	_, fetchedCanonical, err = fetchedManifestListByDigest.Payload()
  1536  	if err != nil {
  1537  		t.Fatalf("error getting manifest list payload: %v", err)
  1538  	}
  1539  
  1540  	if !bytes.Equal(fetchedCanonical, canonical) {
  1541  		t.Fatalf("manifests do not match")
  1542  	}
  1543  
  1544  	// Get by name with etag, gives 304
  1545  	etag := resp.Header.Get("Etag")
  1546  	req, err = http.NewRequest("GET", manifestURL, nil)
  1547  	if err != nil {
  1548  		t.Fatalf("Error constructing request: %s", err)
  1549  	}
  1550  	req.Header.Set("If-None-Match", etag)
  1551  	resp, err = http.DefaultClient.Do(req)
  1552  	if err != nil {
  1553  		t.Fatalf("Error constructing request: %s", err)
  1554  	}
  1555  
  1556  	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
  1557  
  1558  	// Get by digest with etag, gives 304
  1559  	req, err = http.NewRequest("GET", manifestDigestURL, nil)
  1560  	if err != nil {
  1561  		t.Fatalf("Error constructing request: %s", err)
  1562  	}
  1563  	req.Header.Set("If-None-Match", etag)
  1564  	resp, err = http.DefaultClient.Do(req)
  1565  	if err != nil {
  1566  		t.Fatalf("Error constructing request: %s", err)
  1567  	}
  1568  
  1569  	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
  1570  
  1571  	// ------------------
  1572  	// Fetch as a schema1 manifest
  1573  	resp, err = http.Get(manifestURL)
  1574  	if err != nil {
  1575  		t.Fatalf("unexpected error fetching manifest list as schema1: %v", err)
  1576  	}
  1577  	defer resp.Body.Close()
  1578  
  1579  	checkResponse(t, "fetching uploaded manifest list as schema1", resp, http.StatusOK)
  1580  	checkHeaders(t, resp, http.Header{
  1581  		"Docker-Content-Digest": []string{dgst.String()},
  1582  		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
  1583  	})
  1584  
  1585  	var fetchedSchema1Manifest schema1.SignedManifest
  1586  	dec = json.NewDecoder(resp.Body)
  1587  
  1588  	if err := dec.Decode(&fetchedSchema1Manifest); err != nil {
  1589  		t.Fatalf("error decoding fetched schema1 manifest: %v", err)
  1590  	}
  1591  
  1592  	if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
  1593  		t.Fatal("wrong schema version")
  1594  	}
  1595  	if fetchedSchema1Manifest.Architecture != "amd64" {
  1596  		t.Fatal("wrong architecture")
  1597  	}
  1598  	if fetchedSchema1Manifest.Name != imageName {
  1599  		t.Fatal("wrong image name")
  1600  	}
  1601  	if fetchedSchema1Manifest.Tag != tag {
  1602  		t.Fatal("wrong tag")
  1603  	}
  1604  	if len(fetchedSchema1Manifest.FSLayers) != 2 {
  1605  		t.Fatal("wrong number of FSLayers")
  1606  	}
  1607  	layers := args.manifest.(*schema2.DeserializedManifest).Layers
  1608  	for i := range layers {
  1609  		if fetchedSchema1Manifest.FSLayers[i].BlobSum != layers[len(layers)-i-1].Digest {
  1610  			t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
  1611  		}
  1612  	}
  1613  	if len(fetchedSchema1Manifest.History) != 2 {
  1614  		t.Fatal("wrong number of History entries")
  1615  	}
  1616  
  1617  	// Don't check V1Compatibility fields becuase we're using randomly-generated
  1618  	// layers.
  1619  }
  1620  
  1621  func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
  1622  	imageName := args.imageName
  1623  	dgst := args.dgst
  1624  	manifest := args.manifest
  1625  	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
  1626  	// ---------------
  1627  	// Delete by digest
  1628  	resp, err := httpDelete(manifestDigestURL)
  1629  	checkErr(t, err, "deleting manifest by digest")
  1630  
  1631  	checkResponse(t, "deleting manifest", resp, http.StatusAccepted)
  1632  	checkHeaders(t, resp, http.Header{
  1633  		"Content-Length": []string{"0"},
  1634  	})
  1635  
  1636  	// ---------------
  1637  	// Attempt to fetch deleted manifest
  1638  	resp, err = http.Get(manifestDigestURL)
  1639  	checkErr(t, err, "fetching deleted manifest by digest")
  1640  	defer resp.Body.Close()
  1641  
  1642  	checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
  1643  
  1644  	// ---------------
  1645  	// Delete already deleted manifest by digest
  1646  	resp, err = httpDelete(manifestDigestURL)
  1647  	checkErr(t, err, "re-deleting manifest by digest")
  1648  
  1649  	checkResponse(t, "re-deleting manifest", resp, http.StatusNotFound)
  1650  
  1651  	// --------------------
  1652  	// Re-upload manifest by digest
  1653  	resp = putManifest(t, "putting manifest", manifestDigestURL, args.mediaType, manifest)
  1654  	checkResponse(t, "putting manifest", resp, http.StatusCreated)
  1655  	checkHeaders(t, resp, http.Header{
  1656  		"Location":              []string{manifestDigestURL},
  1657  		"Docker-Content-Digest": []string{dgst.String()},
  1658  	})
  1659  
  1660  	// ---------------
  1661  	// Attempt to fetch re-uploaded deleted digest
  1662  	resp, err = http.Get(manifestDigestURL)
  1663  	checkErr(t, err, "fetching re-uploaded manifest by digest")
  1664  	defer resp.Body.Close()
  1665  
  1666  	checkResponse(t, "fetching re-uploaded manifest", resp, http.StatusOK)
  1667  	checkHeaders(t, resp, http.Header{
  1668  		"Docker-Content-Digest": []string{dgst.String()},
  1669  	})
  1670  
  1671  	// ---------------
  1672  	// Attempt to delete an unknown manifest
  1673  	unknownDigest := "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  1674  	unknownManifestDigestURL, err := env.builder.BuildManifestURL(imageName, unknownDigest)
  1675  	checkErr(t, err, "building unknown manifest url")
  1676  
  1677  	resp, err = httpDelete(unknownManifestDigestURL)
  1678  	checkErr(t, err, "delting unknown manifest by digest")
  1679  	checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
  1680  
  1681  }
  1682  
  1683  type testEnv struct {
  1684  	pk      libtrust.PrivateKey
  1685  	ctx     context.Context
  1686  	config  configuration.Configuration
  1687  	app     *App
  1688  	server  *httptest.Server
  1689  	builder *v2.URLBuilder
  1690  }
  1691  
  1692  func newTestEnvMirror(t *testing.T, deleteEnabled bool) *testEnv {
  1693  	config := configuration.Configuration{
  1694  		Storage: configuration.Storage{
  1695  			"inmemory": configuration.Parameters{},
  1696  			"delete":   configuration.Parameters{"enabled": deleteEnabled},
  1697  		},
  1698  		Proxy: configuration.Proxy{
  1699  			RemoteURL: "http://example.com",
  1700  		},
  1701  	}
  1702  
  1703  	return newTestEnvWithConfig(t, &config)
  1704  
  1705  }
  1706  
  1707  func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv {
  1708  	config := configuration.Configuration{
  1709  		Storage: configuration.Storage{
  1710  			"inmemory": configuration.Parameters{},
  1711  			"delete":   configuration.Parameters{"enabled": deleteEnabled},
  1712  		},
  1713  	}
  1714  
  1715  	config.HTTP.Headers = headerConfig
  1716  
  1717  	return newTestEnvWithConfig(t, &config)
  1718  }
  1719  
  1720  func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv {
  1721  	ctx := context.Background()
  1722  
  1723  	app := NewApp(ctx, config)
  1724  	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
  1725  	builder, err := v2.NewURLBuilderFromString(server.URL + config.HTTP.Prefix)
  1726  
  1727  	if err != nil {
  1728  		t.Fatalf("error creating url builder: %v", err)
  1729  	}
  1730  
  1731  	pk, err := libtrust.GenerateECP256PrivateKey()
  1732  	if err != nil {
  1733  		t.Fatalf("unexpected error generating private key: %v", err)
  1734  	}
  1735  
  1736  	return &testEnv{
  1737  		pk:      pk,
  1738  		ctx:     ctx,
  1739  		config:  *config,
  1740  		app:     app,
  1741  		server:  server,
  1742  		builder: builder,
  1743  	}
  1744  }
  1745  
  1746  func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *http.Response {
  1747  	var body []byte
  1748  
  1749  	switch m := v.(type) {
  1750  	case *schema1.SignedManifest:
  1751  		_, pl, err := m.Payload()
  1752  		if err != nil {
  1753  			t.Fatalf("error getting payload: %v", err)
  1754  		}
  1755  		body = pl
  1756  	case *manifestlist.DeserializedManifestList:
  1757  		_, pl, err := m.Payload()
  1758  		if err != nil {
  1759  			t.Fatalf("error getting payload: %v", err)
  1760  		}
  1761  		body = pl
  1762  	default:
  1763  		var err error
  1764  		body, err = json.MarshalIndent(v, "", "   ")
  1765  		if err != nil {
  1766  			t.Fatalf("unexpected error marshaling %v: %v", v, err)
  1767  		}
  1768  	}
  1769  
  1770  	req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
  1771  	if err != nil {
  1772  		t.Fatalf("error creating request for %s: %v", msg, err)
  1773  	}
  1774  
  1775  	if contentType != "" {
  1776  		req.Header.Set("Content-Type", contentType)
  1777  	}
  1778  
  1779  	resp, err := http.DefaultClient.Do(req)
  1780  	if err != nil {
  1781  		t.Fatalf("error doing put request while %s: %v", msg, err)
  1782  	}
  1783  
  1784  	return resp
  1785  }
  1786  
  1787  func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) (location string, uuid string) {
  1788  	layerUploadURL, err := ub.BuildBlobUploadURL(name)
  1789  	if err != nil {
  1790  		t.Fatalf("unexpected error building layer upload url: %v", err)
  1791  	}
  1792  
  1793  	resp, err := http.Post(layerUploadURL, "", nil)
  1794  	if err != nil {
  1795  		t.Fatalf("unexpected error starting layer push: %v", err)
  1796  	}
  1797  	defer resp.Body.Close()
  1798  
  1799  	checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name), resp, http.StatusAccepted)
  1800  
  1801  	u, err := url.Parse(resp.Header.Get("Location"))
  1802  	if err != nil {
  1803  		t.Fatalf("error parsing location header: %v", err)
  1804  	}
  1805  
  1806  	uuid = path.Base(u.Path)
  1807  	checkHeaders(t, resp, http.Header{
  1808  		"Location":           []string{"*"},
  1809  		"Content-Length":     []string{"0"},
  1810  		"Docker-Upload-UUID": []string{uuid},
  1811  	})
  1812  
  1813  	return resp.Header.Get("Location"), uuid
  1814  }
  1815  
  1816  // doPushLayer pushes the layer content returning the url on success returning
  1817  // the response. If you're only expecting a successful response, use pushLayer.
  1818  func doPushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, body io.Reader) (*http.Response, error) {
  1819  	u, err := url.Parse(uploadURLBase)
  1820  	if err != nil {
  1821  		t.Fatalf("unexpected error parsing pushLayer url: %v", err)
  1822  	}
  1823  
  1824  	u.RawQuery = url.Values{
  1825  		"_state": u.Query()["_state"],
  1826  
  1827  		"digest": []string{dgst.String()},
  1828  	}.Encode()
  1829  
  1830  	uploadURL := u.String()
  1831  
  1832  	// Just do a monolithic upload
  1833  	req, err := http.NewRequest("PUT", uploadURL, body)
  1834  	if err != nil {
  1835  		t.Fatalf("unexpected error creating new request: %v", err)
  1836  	}
  1837  
  1838  	return http.DefaultClient.Do(req)
  1839  }
  1840  
  1841  // pushLayer pushes the layer content returning the url on success.
  1842  func pushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, body io.Reader) string {
  1843  	digester := digest.Canonical.New()
  1844  
  1845  	resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, io.TeeReader(body, digester.Hash()))
  1846  	if err != nil {
  1847  		t.Fatalf("unexpected error doing push layer request: %v", err)
  1848  	}
  1849  	defer resp.Body.Close()
  1850  
  1851  	checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
  1852  
  1853  	if err != nil {
  1854  		t.Fatalf("error generating sha256 digest of body")
  1855  	}
  1856  
  1857  	sha256Dgst := digester.Digest()
  1858  
  1859  	expectedLayerURL, err := ub.BuildBlobURL(name, sha256Dgst)
  1860  	if err != nil {
  1861  		t.Fatalf("error building expected layer url: %v", err)
  1862  	}
  1863  
  1864  	checkHeaders(t, resp, http.Header{
  1865  		"Location":              []string{expectedLayerURL},
  1866  		"Content-Length":        []string{"0"},
  1867  		"Docker-Content-Digest": []string{sha256Dgst.String()},
  1868  	})
  1869  
  1870  	return resp.Header.Get("Location")
  1871  }
  1872  
  1873  func finishUpload(t *testing.T, ub *v2.URLBuilder, name string, uploadURLBase string, dgst digest.Digest) string {
  1874  	resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, nil)
  1875  	if err != nil {
  1876  		t.Fatalf("unexpected error doing push layer request: %v", err)
  1877  	}
  1878  	defer resp.Body.Close()
  1879  
  1880  	checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
  1881  
  1882  	expectedLayerURL, err := ub.BuildBlobURL(name, dgst)
  1883  	if err != nil {
  1884  		t.Fatalf("error building expected layer url: %v", err)
  1885  	}
  1886  
  1887  	checkHeaders(t, resp, http.Header{
  1888  		"Location":              []string{expectedLayerURL},
  1889  		"Content-Length":        []string{"0"},
  1890  		"Docker-Content-Digest": []string{dgst.String()},
  1891  	})
  1892  
  1893  	return resp.Header.Get("Location")
  1894  }
  1895  
  1896  func doPushChunk(t *testing.T, uploadURLBase string, body io.Reader) (*http.Response, digest.Digest, error) {
  1897  	u, err := url.Parse(uploadURLBase)
  1898  	if err != nil {
  1899  		t.Fatalf("unexpected error parsing pushLayer url: %v", err)
  1900  	}
  1901  
  1902  	u.RawQuery = url.Values{
  1903  		"_state": u.Query()["_state"],
  1904  	}.Encode()
  1905  
  1906  	uploadURL := u.String()
  1907  
  1908  	digester := digest.Canonical.New()
  1909  
  1910  	req, err := http.NewRequest("PATCH", uploadURL, io.TeeReader(body, digester.Hash()))
  1911  	if err != nil {
  1912  		t.Fatalf("unexpected error creating new request: %v", err)
  1913  	}
  1914  	req.Header.Set("Content-Type", "application/octet-stream")
  1915  
  1916  	resp, err := http.DefaultClient.Do(req)
  1917  
  1918  	return resp, digester.Digest(), err
  1919  }
  1920  
  1921  func pushChunk(t *testing.T, ub *v2.URLBuilder, name string, uploadURLBase string, body io.Reader, length int64) (string, digest.Digest) {
  1922  	resp, dgst, err := doPushChunk(t, uploadURLBase, body)
  1923  	if err != nil {
  1924  		t.Fatalf("unexpected error doing push layer request: %v", err)
  1925  	}
  1926  	defer resp.Body.Close()
  1927  
  1928  	checkResponse(t, "putting chunk", resp, http.StatusAccepted)
  1929  
  1930  	if err != nil {
  1931  		t.Fatalf("error generating sha256 digest of body")
  1932  	}
  1933  
  1934  	checkHeaders(t, resp, http.Header{
  1935  		"Range":          []string{fmt.Sprintf("0-%d", length-1)},
  1936  		"Content-Length": []string{"0"},
  1937  	})
  1938  
  1939  	return resp.Header.Get("Location"), dgst
  1940  }
  1941  
  1942  func checkResponse(t *testing.T, msg string, resp *http.Response, expectedStatus int) {
  1943  	if resp.StatusCode != expectedStatus {
  1944  		t.Logf("unexpected status %s: %v != %v", msg, resp.StatusCode, expectedStatus)
  1945  		maybeDumpResponse(t, resp)
  1946  
  1947  		t.FailNow()
  1948  	}
  1949  
  1950  	// We expect the headers included in the configuration, unless the
  1951  	// status code is 405 (Method Not Allowed), which means the handler
  1952  	// doesn't even get called.
  1953  	if resp.StatusCode != 405 && !reflect.DeepEqual(resp.Header["X-Content-Type-Options"], []string{"nosniff"}) {
  1954  		t.Logf("missing or incorrect header X-Content-Type-Options %s", msg)
  1955  		maybeDumpResponse(t, resp)
  1956  
  1957  		t.FailNow()
  1958  	}
  1959  }
  1960  
  1961  // checkBodyHasErrorCodes ensures the body is an error body and has the
  1962  // expected error codes, returning the error structure, the json slice and a
  1963  // count of the errors by code.
  1964  func checkBodyHasErrorCodes(t *testing.T, msg string, resp *http.Response, errorCodes ...errcode.ErrorCode) (errcode.Errors, []byte, map[errcode.ErrorCode]int) {
  1965  	p, err := ioutil.ReadAll(resp.Body)
  1966  	if err != nil {
  1967  		t.Fatalf("unexpected error reading body %s: %v", msg, err)
  1968  	}
  1969  
  1970  	var errs errcode.Errors
  1971  	if err := json.Unmarshal(p, &errs); err != nil {
  1972  		t.Fatalf("unexpected error decoding error response: %v", err)
  1973  	}
  1974  
  1975  	if len(errs) == 0 {
  1976  		t.Fatalf("expected errors in response")
  1977  	}
  1978  
  1979  	// TODO(stevvooe): Shoot. The error setup is not working out. The content-
  1980  	// type headers are being set after writing the status code.
  1981  	// if resp.Header.Get("Content-Type") != "application/json; charset=utf-8" {
  1982  	// 	t.Fatalf("unexpected content type: %v != 'application/json'",
  1983  	// 		resp.Header.Get("Content-Type"))
  1984  	// }
  1985  
  1986  	expected := map[errcode.ErrorCode]struct{}{}
  1987  	counts := map[errcode.ErrorCode]int{}
  1988  
  1989  	// Initialize map with zeros for expected
  1990  	for _, code := range errorCodes {
  1991  		expected[code] = struct{}{}
  1992  		counts[code] = 0
  1993  	}
  1994  
  1995  	for _, e := range errs {
  1996  		err, ok := e.(errcode.ErrorCoder)
  1997  		if !ok {
  1998  			t.Fatalf("not an ErrorCoder: %#v", e)
  1999  		}
  2000  		if _, ok := expected[err.ErrorCode()]; !ok {
  2001  			t.Fatalf("unexpected error code %v encountered during %s: %s ", err.ErrorCode(), msg, string(p))
  2002  		}
  2003  		counts[err.ErrorCode()]++
  2004  	}
  2005  
  2006  	// Ensure that counts of expected errors were all non-zero
  2007  	for code := range expected {
  2008  		if counts[code] == 0 {
  2009  			t.Fatalf("expected error code %v not encounterd during %s: %s", code, msg, string(p))
  2010  		}
  2011  	}
  2012  
  2013  	return errs, p, counts
  2014  }
  2015  
  2016  func maybeDumpResponse(t *testing.T, resp *http.Response) {
  2017  	if d, err := httputil.DumpResponse(resp, true); err != nil {
  2018  		t.Logf("error dumping response: %v", err)
  2019  	} else {
  2020  		t.Logf("response:\n%s", string(d))
  2021  	}
  2022  }
  2023  
  2024  // matchHeaders checks that the response has at least the headers. If not, the
  2025  // test will fail. If a passed in header value is "*", any non-zero value will
  2026  // suffice as a match.
  2027  func checkHeaders(t *testing.T, resp *http.Response, headers http.Header) {
  2028  	for k, vs := range headers {
  2029  		if resp.Header.Get(k) == "" {
  2030  			t.Fatalf("response missing header %q", k)
  2031  		}
  2032  
  2033  		for _, v := range vs {
  2034  			if v == "*" {
  2035  				// Just ensure there is some value.
  2036  				if len(resp.Header[http.CanonicalHeaderKey(k)]) > 0 {
  2037  					continue
  2038  				}
  2039  			}
  2040  
  2041  			for _, hv := range resp.Header[http.CanonicalHeaderKey(k)] {
  2042  				if hv != v {
  2043  					t.Fatalf("%+v %v header value not matched in response: %q != %q", resp.Header, k, hv, v)
  2044  				}
  2045  			}
  2046  		}
  2047  	}
  2048  }
  2049  
  2050  func checkErr(t *testing.T, err error, msg string) {
  2051  	if err != nil {
  2052  		t.Fatalf("unexpected error %s: %v", msg, err)
  2053  	}
  2054  }
  2055  
  2056  func createRepository(env *testEnv, t *testing.T, imageName string, tag string) digest.Digest {
  2057  	unsignedManifest := &schema1.Manifest{
  2058  		Versioned: manifest.Versioned{
  2059  			SchemaVersion: 1,
  2060  		},
  2061  		Name: imageName,
  2062  		Tag:  tag,
  2063  		FSLayers: []schema1.FSLayer{
  2064  			{
  2065  				BlobSum: "asdf",
  2066  			},
  2067  		},
  2068  		History: []schema1.History{
  2069  			{
  2070  				V1Compatibility: "",
  2071  			},
  2072  		},
  2073  	}
  2074  
  2075  	// Push 2 random layers
  2076  	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
  2077  
  2078  	for i := range unsignedManifest.FSLayers {
  2079  		rs, dgstStr, err := testutil.CreateRandomTarFile()
  2080  		if err != nil {
  2081  			t.Fatalf("error creating random layer %d: %v", i, err)
  2082  		}
  2083  		dgst := digest.Digest(dgstStr)
  2084  
  2085  		expectedLayers[dgst] = rs
  2086  		unsignedManifest.FSLayers[i].BlobSum = dgst
  2087  
  2088  		uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
  2089  		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
  2090  	}
  2091  
  2092  	signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
  2093  	if err != nil {
  2094  		t.Fatalf("unexpected error signing manifest: %v", err)
  2095  	}
  2096  
  2097  	dgst := digest.FromBytes(signedManifest.Canonical)
  2098  
  2099  	// Create this repository by tag to ensure the tag mapping is made in the registry
  2100  	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, tag)
  2101  	checkErr(t, err, "building manifest url")
  2102  
  2103  	location, err := env.builder.BuildManifestURL(imageName, dgst.String())
  2104  	checkErr(t, err, "building location URL")
  2105  
  2106  	resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
  2107  	checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
  2108  	checkHeaders(t, resp, http.Header{
  2109  		"Location":              []string{location},
  2110  		"Docker-Content-Digest": []string{dgst.String()},
  2111  	})
  2112  	return dgst
  2113  }
  2114  
  2115  // Test mutation operations on a registry configured as a cache.  Ensure that they return
  2116  // appropriate errors.
  2117  func TestRegistryAsCacheMutationAPIs(t *testing.T) {
  2118  	deleteEnabled := true
  2119  	env := newTestEnvMirror(t, deleteEnabled)
  2120  
  2121  	imageName := "foo/bar"
  2122  	tag := "latest"
  2123  	manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
  2124  	if err != nil {
  2125  		t.Fatalf("unexpected error building base url: %v", err)
  2126  	}
  2127  
  2128  	// Manifest upload
  2129  	m := &schema1.Manifest{
  2130  		Versioned: manifest.Versioned{
  2131  			SchemaVersion: 1,
  2132  		},
  2133  		Name:     imageName,
  2134  		Tag:      tag,
  2135  		FSLayers: []schema1.FSLayer{},
  2136  		History:  []schema1.History{},
  2137  	}
  2138  
  2139  	sm, err := schema1.Sign(m, env.pk)
  2140  	if err != nil {
  2141  		t.Fatalf("error signing manifest: %v", err)
  2142  	}
  2143  
  2144  	resp := putManifest(t, "putting unsigned manifest", manifestURL, "", sm)
  2145  	checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
  2146  
  2147  	// Manifest Delete
  2148  	resp, err = httpDelete(manifestURL)
  2149  	checkResponse(t, "deleting signed manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
  2150  
  2151  	// Blob upload initialization
  2152  	layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
  2153  	if err != nil {
  2154  		t.Fatalf("unexpected error building layer upload url: %v", err)
  2155  	}
  2156  
  2157  	resp, err = http.Post(layerUploadURL, "", nil)
  2158  	if err != nil {
  2159  		t.Fatalf("unexpected error starting layer push: %v", err)
  2160  	}
  2161  	defer resp.Body.Close()
  2162  
  2163  	checkResponse(t, fmt.Sprintf("starting layer push to cache %v", imageName), resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
  2164  
  2165  	// Blob Delete
  2166  	blobURL, err := env.builder.BuildBlobURL(imageName, digest.DigestSha256EmptyTar)
  2167  	resp, err = httpDelete(blobURL)
  2168  	checkResponse(t, "deleting blob from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
  2169  
  2170  }
  2171  
  2172  // TestCheckContextNotifier makes sure the API endpoints get a ResponseWriter
  2173  // that implements http.ContextNotifier.
  2174  func TestCheckContextNotifier(t *testing.T) {
  2175  	env := newTestEnv(t, false)
  2176  
  2177  	// Register a new endpoint for testing
  2178  	env.app.router.Handle("/unittest/{name}/", env.app.dispatcher(func(ctx *Context, r *http.Request) http.Handler {
  2179  		return handlers.MethodHandler{
  2180  			"GET": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2181  				if _, ok := w.(http.CloseNotifier); !ok {
  2182  					t.Fatal("could not cast ResponseWriter to CloseNotifier")
  2183  				}
  2184  				w.WriteHeader(200)
  2185  			}),
  2186  		}
  2187  	}))
  2188  
  2189  	resp, err := http.Get(env.server.URL + "/unittest/reponame/")
  2190  	if err != nil {
  2191  		t.Fatalf("unexpected error issuing request: %v", err)
  2192  	}
  2193  	defer resp.Body.Close()
  2194  
  2195  	if resp.StatusCode != 200 {
  2196  		t.Fatalf("wrong status code - expected 200, got %d", resp.StatusCode)
  2197  	}
  2198  }
  2199  
  2200  func TestProxyManifestGetByTag(t *testing.T) {
  2201  	truthConfig := configuration.Configuration{
  2202  		Storage: configuration.Storage{
  2203  			"inmemory": configuration.Parameters{},
  2204  		},
  2205  	}
  2206  	truthConfig.HTTP.Headers = headerConfig
  2207  
  2208  	imageName := "foo/bar"
  2209  	tag := "latest"
  2210  
  2211  	truthEnv := newTestEnvWithConfig(t, &truthConfig)
  2212  	// create a repository in the truth registry
  2213  	dgst := createRepository(truthEnv, t, imageName, tag)
  2214  
  2215  	proxyConfig := configuration.Configuration{
  2216  		Storage: configuration.Storage{
  2217  			"inmemory": configuration.Parameters{},
  2218  		},
  2219  		Proxy: configuration.Proxy{
  2220  			RemoteURL: truthEnv.server.URL,
  2221  		},
  2222  	}
  2223  	proxyConfig.HTTP.Headers = headerConfig
  2224  
  2225  	proxyEnv := newTestEnvWithConfig(t, &proxyConfig)
  2226  
  2227  	manifestDigestURL, err := proxyEnv.builder.BuildManifestURL(imageName, dgst.String())
  2228  	checkErr(t, err, "building manifest url")
  2229  
  2230  	resp, err := http.Get(manifestDigestURL)
  2231  	checkErr(t, err, "fetching manifest from proxy by digest")
  2232  	defer resp.Body.Close()
  2233  
  2234  	manifestTagURL, err := proxyEnv.builder.BuildManifestURL(imageName, tag)
  2235  	checkErr(t, err, "building manifest url")
  2236  
  2237  	resp, err = http.Get(manifestTagURL)
  2238  	checkErr(t, err, "fetching manifest from proxy by tag")
  2239  	defer resp.Body.Close()
  2240  	checkResponse(t, "fetching manifest from proxy by tag", resp, http.StatusOK)
  2241  	checkHeaders(t, resp, http.Header{
  2242  		"Docker-Content-Digest": []string{dgst.String()},
  2243  	})
  2244  
  2245  	// Create another manifest in the remote with the same image/tag pair
  2246  	newDigest := createRepository(truthEnv, t, imageName, tag)
  2247  	if dgst == newDigest {
  2248  		t.Fatalf("non-random test data")
  2249  	}
  2250  
  2251  	// fetch it with the same proxy URL as before.  Ensure the updated content is at the same tag
  2252  	resp, err = http.Get(manifestTagURL)
  2253  	checkErr(t, err, "fetching manifest from proxy by tag")
  2254  	defer resp.Body.Close()
  2255  	checkResponse(t, "fetching manifest from proxy by tag", resp, http.StatusOK)
  2256  	checkHeaders(t, resp, http.Header{
  2257  		"Docker-Content-Digest": []string{newDigest.String()},
  2258  	})
  2259  }