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