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