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}
963
964func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
965	tag := "thetag"
966	args := manifestArgs{imageName: imageName}
967
968	tagRef, _ := reference.WithTag(imageName, tag)
969	manifestURL, err := env.builder.BuildManifestURL(tagRef)
970	if err != nil {
971		t.Fatalf("unexpected error getting manifest url: %v", err)
972	}
973
974	// -----------------------------
975	// Attempt to fetch the manifest
976	resp, err := http.Get(manifestURL)
977	if err != nil {
978		t.Fatalf("unexpected error getting manifest: %v", err)
979	}
980	defer resp.Body.Close()
981
982	checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
983	checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
984
985	tagsURL, err := env.builder.BuildTagsURL(imageName)
986	if err != nil {
987		t.Fatalf("unexpected error building tags url: %v", err)
988	}
989
990	resp, err = http.Get(tagsURL)
991	if err != nil {
992		t.Fatalf("unexpected error getting unknown tags: %v", err)
993	}
994	defer resp.Body.Close()
995
996	// Check that we get an unknown repository error when asking for tags
997	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
998	checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
999
1000	// --------------------------------
1001	// Attempt to push unsigned manifest with missing layers
1002	unsignedManifest := &schema1.Manifest{
1003		Versioned: manifest.Versioned{
1004			SchemaVersion: 1,
1005		},
1006		Name: imageName.Name(),
1007		Tag:  tag,
1008		FSLayers: []schema1.FSLayer{
1009			{
1010				BlobSum: "asdf",
1011			},
1012			{
1013				BlobSum: "qwer",
1014			},
1015		},
1016		History: []schema1.History{
1017			{
1018				V1Compatibility: "",
1019			},
1020			{
1021				V1Compatibility: "",
1022			},
1023		},
1024	}
1025
1026	resp = putManifest(t, "putting unsigned manifest", manifestURL, "", unsignedManifest)
1027	defer resp.Body.Close()
1028	checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
1029	_, p, counts := checkBodyHasErrorCodes(t, "putting unsigned manifest", resp, v2.ErrorCodeManifestInvalid)
1030
1031	expectedCounts := map[errcode.ErrorCode]int{
1032		v2.ErrorCodeManifestInvalid: 1,
1033	}
1034
1035	if !reflect.DeepEqual(counts, expectedCounts) {
1036		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1037	}
1038
1039	// sign the manifest and still get some interesting errors.
1040	sm, err := schema1.Sign(unsignedManifest, env.pk)
1041	if err != nil {
1042		t.Fatalf("error signing manifest: %v", err)
1043	}
1044
1045	resp = putManifest(t, "putting signed manifest with errors", manifestURL, "", sm)
1046	defer resp.Body.Close()
1047	checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
1048	_, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
1049		v2.ErrorCodeManifestBlobUnknown, v2.ErrorCodeDigestInvalid)
1050
1051	expectedCounts = map[errcode.ErrorCode]int{
1052		v2.ErrorCodeManifestBlobUnknown: 2,
1053		v2.ErrorCodeDigestInvalid:       2,
1054	}
1055
1056	if !reflect.DeepEqual(counts, expectedCounts) {
1057		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1058	}
1059
1060	// TODO(stevvooe): Add a test case where we take a mostly valid registry,
1061	// tamper with the content and ensure that we get an unverified manifest
1062	// error.
1063
1064	// Push 2 random layers
1065	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
1066
1067	for i := range unsignedManifest.FSLayers {
1068		rs, dgst, err := testutil.CreateRandomTarFile()
1069
1070		if err != nil {
1071			t.Fatalf("error creating random layer %d: %v", i, err)
1072		}
1073
1074		expectedLayers[dgst] = rs
1075		unsignedManifest.FSLayers[i].BlobSum = dgst
1076
1077		uploadURLBase, _ := startPushLayer(t, env, imageName)
1078		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
1079	}
1080
1081	// -------------------
1082	// Push the signed manifest with all layers pushed.
1083	signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
1084	if err != nil {
1085		t.Fatalf("unexpected error signing manifest: %v", err)
1086	}
1087
1088	dgst := digest.FromBytes(signedManifest.Canonical)
1089	args.manifest = signedManifest
1090	args.dgst = dgst
1091
1092	digestRef, _ := reference.WithDigest(imageName, dgst)
1093	manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
1094	checkErr(t, err, "building manifest url")
1095
1096	resp = putManifest(t, "putting signed manifest no error", manifestURL, "", signedManifest)
1097	checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
1098	checkHeaders(t, resp, http.Header{
1099		"Location":              []string{manifestDigestURL},
1100		"Docker-Content-Digest": []string{dgst.String()},
1101	})
1102
1103	// --------------------
1104	// Push by digest -- should get same result
1105	resp = putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
1106	checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
1107	checkHeaders(t, resp, http.Header{
1108		"Location":              []string{manifestDigestURL},
1109		"Docker-Content-Digest": []string{dgst.String()},
1110	})
1111
1112	// ------------------
1113	// Fetch by tag name
1114	resp, err = http.Get(manifestURL)
1115	if err != nil {
1116		t.Fatalf("unexpected error fetching manifest: %v", err)
1117	}
1118	defer resp.Body.Close()
1119
1120	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1121	checkHeaders(t, resp, http.Header{
1122		"Docker-Content-Digest": []string{dgst.String()},
1123		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1124	})
1125
1126	var fetchedManifest schema1.SignedManifest
1127	dec := json.NewDecoder(resp.Body)
1128
1129	if err := dec.Decode(&fetchedManifest); err != nil {
1130		t.Fatalf("error decoding fetched manifest: %v", err)
1131	}
1132
1133	if !bytes.Equal(fetchedManifest.Canonical, signedManifest.Canonical) {
1134		t.Fatalf("manifests do not match")
1135	}
1136
1137	// ---------------
1138	// Fetch by digest
1139	resp, err = http.Get(manifestDigestURL)
1140	checkErr(t, err, "fetching manifest by digest")
1141	defer resp.Body.Close()
1142
1143	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1144	checkHeaders(t, resp, http.Header{
1145		"Docker-Content-Digest": []string{dgst.String()},
1146		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1147	})
1148
1149	var fetchedManifestByDigest schema1.SignedManifest
1150	dec = json.NewDecoder(resp.Body)
1151	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
1152		t.Fatalf("error decoding fetched manifest: %v", err)
1153	}
1154
1155	if !bytes.Equal(fetchedManifestByDigest.Canonical, signedManifest.Canonical) {
1156		t.Fatalf("manifests do not match")
1157	}
1158
1159	// check signature was roundtripped
1160	signatures, err := fetchedManifestByDigest.Signatures()
1161	if err != nil {
1162		t.Fatal(err)
1163	}
1164
1165	if len(signatures) != 1 {
1166		t.Fatalf("expected 1 signature from manifest, got: %d", len(signatures))
1167	}
1168
1169	// Re-sign, push and pull the same digest
1170	sm2, err := schema1.Sign(&fetchedManifestByDigest.Manifest, env.pk)
1171	if err != nil {
1172		t.Fatal(err)
1173
1174	}
1175
1176	// Re-push with a few different Content-Types. The official schema1
1177	// content type should work, as should application/json with/without a
1178	// charset.
1179	resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, schema1.MediaTypeSignedManifest, sm2)
1180	checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
1181	resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json; charset=utf-8", sm2)
1182	checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
1183	resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json", sm2)
1184	checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
1185
1186	resp, err = http.Get(manifestDigestURL)
1187	checkErr(t, err, "re-fetching manifest by digest")
1188	defer resp.Body.Close()
1189
1190	checkResponse(t, "re-fetching uploaded manifest", resp, http.StatusOK)
1191	checkHeaders(t, resp, http.Header{
1192		"Docker-Content-Digest": []string{dgst.String()},
1193		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1194	})
1195
1196	dec = json.NewDecoder(resp.Body)
1197	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
1198		t.Fatalf("error decoding fetched manifest: %v", err)
1199	}
1200
1201	// check only 1 signature is returned
1202	signatures, err = fetchedManifestByDigest.Signatures()
1203	if err != nil {
1204		t.Fatal(err)
1205	}
1206
1207	if len(signatures) != 1 {
1208		t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures))
1209	}
1210
1211	// Get by name with etag, gives 304
1212	etag := resp.Header.Get("Etag")
1213	req, err := http.NewRequest("GET", manifestURL, nil)
1214	if err != nil {
1215		t.Fatalf("Error constructing request: %s", err)
1216	}
1217	req.Header.Set("If-None-Match", etag)
1218	resp, err = http.DefaultClient.Do(req)
1219	if err != nil {
1220		t.Fatalf("Error constructing request: %s", err)
1221	}
1222
1223	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
1224
1225	// Get by digest with etag, gives 304
1226	req, err = http.NewRequest("GET", manifestDigestURL, nil)
1227	if err != nil {
1228		t.Fatalf("Error constructing request: %s", err)
1229	}
1230	req.Header.Set("If-None-Match", etag)
1231	resp, err = http.DefaultClient.Do(req)
1232	if err != nil {
1233		t.Fatalf("Error constructing request: %s", err)
1234	}
1235
1236	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
1237
1238	// Ensure that the tag is listed.
1239	resp, err = http.Get(tagsURL)
1240	if err != nil {
1241		t.Fatalf("unexpected error getting unknown tags: %v", err)
1242	}
1243	defer resp.Body.Close()
1244
1245	checkResponse(t, "getting tags", resp, http.StatusOK)
1246	dec = json.NewDecoder(resp.Body)
1247
1248	var tagsResponse tagsAPIResponse
1249
1250	if err := dec.Decode(&tagsResponse); err != nil {
1251		t.Fatalf("unexpected error decoding error response: %v", err)
1252	}
1253
1254	if tagsResponse.Name != imageName.Name() {
1255		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName.Name())
1256	}
1257
1258	if len(tagsResponse.Tags) != 1 {
1259		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
1260	}
1261
1262	if tagsResponse.Tags[0] != tag {
1263		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
1264	}
1265
1266	// Attempt to put a manifest with mismatching FSLayer and History array cardinalities
1267
1268	unsignedManifest.History = append(unsignedManifest.History, schema1.History{
1269		V1Compatibility: "",
1270	})
1271	invalidSigned, err := schema1.Sign(unsignedManifest, env.pk)
1272	if err != nil {
1273		t.Fatalf("error signing manifest")
1274	}
1275
1276	resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, "", invalidSigned)
1277	checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)
1278
1279	return args
1280}
1281
1282func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
1283	tag := "schema2tag"
1284	args := manifestArgs{
1285		imageName: imageName,
1286		mediaType: schema2.MediaTypeManifest,
1287	}
1288
1289	tagRef, _ := reference.WithTag(imageName, tag)
1290	manifestURL, err := env.builder.BuildManifestURL(tagRef)
1291	if err != nil {
1292		t.Fatalf("unexpected error getting manifest url: %v", err)
1293	}
1294
1295	// -----------------------------
1296	// Attempt to fetch the manifest
1297	resp, err := http.Get(manifestURL)
1298	if err != nil {
1299		t.Fatalf("unexpected error getting manifest: %v", err)
1300	}
1301	defer resp.Body.Close()
1302
1303	checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
1304	checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
1305
1306	tagsURL, err := env.builder.BuildTagsURL(imageName)
1307	if err != nil {
1308		t.Fatalf("unexpected error building tags url: %v", err)
1309	}
1310
1311	resp, err = http.Get(tagsURL)
1312	if err != nil {
1313		t.Fatalf("unexpected error getting unknown tags: %v", err)
1314	}
1315	defer resp.Body.Close()
1316
1317	// Check that we get an unknown repository error when asking for tags
1318	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
1319	checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
1320
1321	// --------------------------------
1322	// Attempt to push manifest with missing config and missing layers
1323	manifest := &schema2.Manifest{
1324		Versioned: manifest.Versioned{
1325			SchemaVersion: 2,
1326			MediaType:     schema2.MediaTypeManifest,
1327		},
1328		Config: distribution.Descriptor{
1329			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
1330			Size:      3253,
1331			MediaType: schema2.MediaTypeImageConfig,
1332		},
1333		Layers: []distribution.Descriptor{
1334			{
1335				Digest:    "sha256:463434349086340864309863409683460843608348608934092322395278926a",
1336				Size:      6323,
1337				MediaType: schema2.MediaTypeLayer,
1338			},
1339			{
1340				Digest:    "sha256:630923423623623423352523525237238023652897356239852383652aaaaaaa",
1341				Size:      6863,
1342				MediaType: schema2.MediaTypeLayer,
1343			},
1344		},
1345	}
1346
1347	resp = putManifest(t, "putting missing config manifest", manifestURL, schema2.MediaTypeManifest, manifest)
1348	defer resp.Body.Close()
1349	checkResponse(t, "putting missing config manifest", resp, http.StatusBadRequest)
1350	_, p, counts := checkBodyHasErrorCodes(t, "putting missing config manifest", resp, v2.ErrorCodeManifestBlobUnknown)
1351
1352	expectedCounts := map[errcode.ErrorCode]int{
1353		v2.ErrorCodeManifestBlobUnknown: 3,
1354	}
1355
1356	if !reflect.DeepEqual(counts, expectedCounts) {
1357		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1358	}
1359
1360	// Push a config, and reference it in the manifest
1361	sampleConfig := []byte(`{
1362		"architecture": "amd64",
1363		"history": [
1364		  {
1365		    "created": "2015-10-31T22:22:54.690851953Z",
1366		    "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
1367		  },
1368		  {
1369		    "created": "2015-10-31T22:22:55.613815829Z",
1370		    "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
1371		  }
1372		],
1373		"rootfs": {
1374		  "diff_ids": [
1375		    "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
1376		    "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
1377		  ],
1378		  "type": "layers"
1379		}
1380	}`)
1381	sampleConfigDigest := digest.FromBytes(sampleConfig)
1382
1383	uploadURLBase, _ := startPushLayer(t, env, imageName)
1384	pushLayer(t, env.builder, imageName, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig))
1385	manifest.Config.Digest = sampleConfigDigest
1386	manifest.Config.Size = int64(len(sampleConfig))
1387
1388	// The manifest should still be invalid, because its layer doesn't exist
1389	resp = putManifest(t, "putting missing layer manifest", manifestURL, schema2.MediaTypeManifest, manifest)
1390	defer resp.Body.Close()
1391	checkResponse(t, "putting missing layer manifest", resp, http.StatusBadRequest)
1392	_, p, counts = checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestBlobUnknown)
1393
1394	expectedCounts = map[errcode.ErrorCode]int{
1395		v2.ErrorCodeManifestBlobUnknown: 2,
1396	}
1397
1398	if !reflect.DeepEqual(counts, expectedCounts) {
1399		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1400	}
1401
1402	// Push 2 random layers
1403	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
1404
1405	for i := range manifest.Layers {
1406		rs, dgst, err := testutil.CreateRandomTarFile()
1407
1408		if err != nil {
1409			t.Fatalf("error creating random layer %d: %v", i, err)
1410		}
1411
1412		expectedLayers[dgst] = rs
1413		manifest.Layers[i].Digest = dgst
1414
1415		uploadURLBase, _ := startPushLayer(t, env, imageName)
1416		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
1417	}
1418
1419	// -------------------
1420	// Push the manifest with all layers pushed.
1421	deserializedManifest, err := schema2.FromStruct(*manifest)
1422	if err != nil {
1423		t.Fatalf("could not create DeserializedManifest: %v", err)
1424	}
1425	_, canonical, err := deserializedManifest.Payload()
1426	if err != nil {
1427		t.Fatalf("could not get manifest payload: %v", err)
1428	}
1429	dgst := digest.FromBytes(canonical)
1430	args.dgst = dgst
1431	args.manifest = deserializedManifest
1432
1433	digestRef, _ := reference.WithDigest(imageName, dgst)
1434	manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
1435	checkErr(t, err, "building manifest url")
1436
1437	resp = putManifest(t, "putting manifest no error", manifestURL, schema2.MediaTypeManifest, manifest)
1438	checkResponse(t, "putting manifest no error", resp, http.StatusCreated)
1439	checkHeaders(t, resp, http.Header{
1440		"Location":              []string{manifestDigestURL},
1441		"Docker-Content-Digest": []string{dgst.String()},
1442	})
1443
1444	// --------------------
1445	// Push by digest -- should get same result
1446	resp = putManifest(t, "putting manifest by digest", manifestDigestURL, schema2.MediaTypeManifest, manifest)
1447	checkResponse(t, "putting manifest by digest", resp, http.StatusCreated)
1448	checkHeaders(t, resp, http.Header{
1449		"Location":              []string{manifestDigestURL},
1450		"Docker-Content-Digest": []string{dgst.String()},
1451	})
1452
1453	// ------------------
1454	// Fetch by tag name
1455	req, err := http.NewRequest("GET", manifestURL, nil)
1456	if err != nil {
1457		t.Fatalf("Error constructing request: %s", err)
1458	}
1459	req.Header.Set("Accept", schema2.MediaTypeManifest)
1460	resp, err = http.DefaultClient.Do(req)
1461	if err != nil {
1462		t.Fatalf("unexpected error fetching manifest: %v", err)
1463	}
1464	defer resp.Body.Close()
1465
1466	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1467	checkHeaders(t, resp, http.Header{
1468		"Docker-Content-Digest": []string{dgst.String()},
1469		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1470	})
1471
1472	var fetchedManifest schema2.DeserializedManifest
1473	dec := json.NewDecoder(resp.Body)
1474
1475	if err := dec.Decode(&fetchedManifest); err != nil {
1476		t.Fatalf("error decoding fetched manifest: %v", err)
1477	}
1478
1479	_, fetchedCanonical, err := fetchedManifest.Payload()
1480	if err != nil {
1481		t.Fatalf("error getting manifest payload: %v", err)
1482	}
1483
1484	if !bytes.Equal(fetchedCanonical, canonical) {
1485		t.Fatalf("manifests do not match")
1486	}
1487
1488	// ---------------
1489	// Fetch by digest
1490	req, err = http.NewRequest("GET", manifestDigestURL, nil)
1491	if err != nil {
1492		t.Fatalf("Error constructing request: %s", err)
1493	}
1494	req.Header.Set("Accept", schema2.MediaTypeManifest)
1495	resp, err = http.DefaultClient.Do(req)
1496	checkErr(t, err, "fetching manifest by digest")
1497	defer resp.Body.Close()
1498
1499	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
1500	checkHeaders(t, resp, http.Header{
1501		"Docker-Content-Digest": []string{dgst.String()},
1502		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1503	})
1504
1505	var fetchedManifestByDigest schema2.DeserializedManifest
1506	dec = json.NewDecoder(resp.Body)
1507	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
1508		t.Fatalf("error decoding fetched manifest: %v", err)
1509	}
1510
1511	_, fetchedCanonical, err = fetchedManifest.Payload()
1512	if err != nil {
1513		t.Fatalf("error getting manifest payload: %v", err)
1514	}
1515
1516	if !bytes.Equal(fetchedCanonical, canonical) {
1517		t.Fatalf("manifests do not match")
1518	}
1519
1520	// Get by name with etag, gives 304
1521	etag := resp.Header.Get("Etag")
1522	req, err = http.NewRequest("GET", manifestURL, nil)
1523	if err != nil {
1524		t.Fatalf("Error constructing request: %s", err)
1525	}
1526	req.Header.Set("If-None-Match", etag)
1527	resp, err = http.DefaultClient.Do(req)
1528	if err != nil {
1529		t.Fatalf("Error constructing request: %s", err)
1530	}
1531
1532	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
1533
1534	// Get by digest with etag, gives 304
1535	req, err = http.NewRequest("GET", manifestDigestURL, nil)
1536	if err != nil {
1537		t.Fatalf("Error constructing request: %s", err)
1538	}
1539	req.Header.Set("If-None-Match", etag)
1540	resp, err = http.DefaultClient.Do(req)
1541	if err != nil {
1542		t.Fatalf("Error constructing request: %s", err)
1543	}
1544
1545	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
1546
1547	// Ensure that the tag is listed.
1548	resp, err = http.Get(tagsURL)
1549	if err != nil {
1550		t.Fatalf("unexpected error getting unknown tags: %v", err)
1551	}
1552	defer resp.Body.Close()
1553
1554	checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
1555	dec = json.NewDecoder(resp.Body)
1556
1557	var tagsResponse tagsAPIResponse
1558
1559	if err := dec.Decode(&tagsResponse); err != nil {
1560		t.Fatalf("unexpected error decoding error response: %v", err)
1561	}
1562
1563	if tagsResponse.Name != imageName.Name() {
1564		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
1565	}
1566
1567	if len(tagsResponse.Tags) != 1 {
1568		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
1569	}
1570
1571	if tagsResponse.Tags[0] != tag {
1572		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
1573	}
1574
1575	// ------------------
1576	// Fetch as a schema1 manifest
1577	resp, err = http.Get(manifestURL)
1578	if err != nil {
1579		t.Fatalf("unexpected error fetching manifest as schema1: %v", err)
1580	}
1581	defer resp.Body.Close()
1582
1583	manifestBytes, err := ioutil.ReadAll(resp.Body)
1584	if err != nil {
1585		t.Fatalf("error reading response body: %v", err)
1586	}
1587
1588	checkResponse(t, "fetching uploaded manifest as schema1", resp, http.StatusOK)
1589
1590	m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes)
1591	if err != nil {
1592		t.Fatalf("unexpected error unmarshalling manifest: %v", err)
1593	}
1594
1595	fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest)
1596	if !ok {
1597		t.Fatalf("expecting schema1 manifest")
1598	}
1599
1600	checkHeaders(t, resp, http.Header{
1601		"Docker-Content-Digest": []string{desc.Digest.String()},
1602		"ETag":                  []string{fmt.Sprintf(`"%s"`, desc.Digest)},
1603	})
1604
1605	if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
1606		t.Fatal("wrong schema version")
1607	}
1608	if fetchedSchema1Manifest.Architecture != "amd64" {
1609		t.Fatal("wrong architecture")
1610	}
1611	if fetchedSchema1Manifest.Name != imageName.Name() {
1612		t.Fatal("wrong image name")
1613	}
1614	if fetchedSchema1Manifest.Tag != tag {
1615		t.Fatal("wrong tag")
1616	}
1617	if len(fetchedSchema1Manifest.FSLayers) != 2 {
1618		t.Fatal("wrong number of FSLayers")
1619	}
1620	for i := range manifest.Layers {
1621		if fetchedSchema1Manifest.FSLayers[i].BlobSum != manifest.Layers[len(manifest.Layers)-i-1].Digest {
1622			t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
1623		}
1624	}
1625	if len(fetchedSchema1Manifest.History) != 2 {
1626		t.Fatal("wrong number of History entries")
1627	}
1628
1629	// Don't check V1Compatibility fields because we're using randomly-generated
1630	// layers.
1631
1632	return args
1633}
1634
1635func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs) {
1636	imageName := args.imageName
1637	tag := "manifestlisttag"
1638
1639	tagRef, _ := reference.WithTag(imageName, tag)
1640	manifestURL, err := env.builder.BuildManifestURL(tagRef)
1641	if err != nil {
1642		t.Fatalf("unexpected error getting manifest url: %v", err)
1643	}
1644
1645	// --------------------------------
1646	// Attempt to push manifest list that refers to an unknown manifest
1647	manifestList := &manifestlist.ManifestList{
1648		Versioned: manifest.Versioned{
1649			SchemaVersion: 2,
1650			MediaType:     manifestlist.MediaTypeManifestList,
1651		},
1652		Manifests: []manifestlist.ManifestDescriptor{
1653			{
1654				Descriptor: distribution.Descriptor{
1655					Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
1656					Size:      3253,
1657					MediaType: schema2.MediaTypeManifest,
1658				},
1659				Platform: manifestlist.PlatformSpec{
1660					Architecture: "amd64",
1661					OS:           "linux",
1662				},
1663			},
1664		},
1665	}
1666
1667	resp := putManifest(t, "putting missing manifest manifestlist", manifestURL, manifestlist.MediaTypeManifestList, manifestList)
1668	defer resp.Body.Close()
1669	checkResponse(t, "putting missing manifest manifestlist", resp, http.StatusBadRequest)
1670	_, p, counts := checkBodyHasErrorCodes(t, "putting missing manifest manifestlist", resp, v2.ErrorCodeManifestBlobUnknown)
1671
1672	expectedCounts := map[errcode.ErrorCode]int{
1673		v2.ErrorCodeManifestBlobUnknown: 1,
1674	}
1675
1676	if !reflect.DeepEqual(counts, expectedCounts) {
1677		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
1678	}
1679
1680	// -------------------
1681	// Push a manifest list that references an actual manifest
1682	manifestList.Manifests[0].Digest = args.dgst
1683	deserializedManifestList, err := manifestlist.FromDescriptors(manifestList.Manifests)
1684	if err != nil {
1685		t.Fatalf("could not create DeserializedManifestList: %v", err)
1686	}
1687	_, canonical, err := deserializedManifestList.Payload()
1688	if err != nil {
1689		t.Fatalf("could not get manifest list payload: %v", err)
1690	}
1691	dgst := digest.FromBytes(canonical)
1692
1693	digestRef, _ := reference.WithDigest(imageName, dgst)
1694	manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
1695	checkErr(t, err, "building manifest url")
1696
1697	resp = putManifest(t, "putting manifest list no error", manifestURL, manifestlist.MediaTypeManifestList, deserializedManifestList)
1698	checkResponse(t, "putting manifest list no error", resp, http.StatusCreated)
1699	checkHeaders(t, resp, http.Header{
1700		"Location":              []string{manifestDigestURL},
1701		"Docker-Content-Digest": []string{dgst.String()},
1702	})
1703
1704	// --------------------
1705	// Push by digest -- should get same result
1706	resp = putManifest(t, "putting manifest list by digest", manifestDigestURL, manifestlist.MediaTypeManifestList, deserializedManifestList)
1707	checkResponse(t, "putting manifest list by digest", resp, http.StatusCreated)
1708	checkHeaders(t, resp, http.Header{
1709		"Location":              []string{manifestDigestURL},
1710		"Docker-Content-Digest": []string{dgst.String()},
1711	})
1712
1713	// ------------------
1714	// Fetch by tag name
1715	req, err := http.NewRequest("GET", manifestURL, nil)
1716	if err != nil {
1717		t.Fatalf("Error constructing request: %s", err)
1718	}
1719	// multiple headers in mixed list format to ensure we parse correctly server-side
1720	req.Header.Set("Accept", fmt.Sprintf(` %s ; q=0.8 , %s ; q=0.5 `, manifestlist.MediaTypeManifestList, schema1.MediaTypeSignedManifest))
1721	req.Header.Add("Accept", schema2.MediaTypeManifest)
1722	resp, err = http.DefaultClient.Do(req)
1723	if err != nil {
1724		t.Fatalf("unexpected error fetching manifest list: %v", err)
1725	}
1726	defer resp.Body.Close()
1727
1728	checkResponse(t, "fetching uploaded manifest list", resp, http.StatusOK)
1729	checkHeaders(t, resp, http.Header{
1730		"Docker-Content-Digest": []string{dgst.String()},
1731		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1732	})
1733
1734	var fetchedManifestList manifestlist.DeserializedManifestList
1735	dec := json.NewDecoder(resp.Body)
1736
1737	if err := dec.Decode(&fetchedManifestList); err != nil {
1738		t.Fatalf("error decoding fetched manifest list: %v", err)
1739	}
1740
1741	_, fetchedCanonical, err := fetchedManifestList.Payload()
1742	if err != nil {
1743		t.Fatalf("error getting manifest list payload: %v", err)
1744	}
1745
1746	if !bytes.Equal(fetchedCanonical, canonical) {
1747		t.Fatalf("manifest lists do not match")
1748	}
1749
1750	// ---------------
1751	// Fetch by digest
1752	req, err = http.NewRequest("GET", manifestDigestURL, nil)
1753	if err != nil {
1754		t.Fatalf("Error constructing request: %s", err)
1755	}
1756	req.Header.Set("Accept", manifestlist.MediaTypeManifestList)
1757	resp, err = http.DefaultClient.Do(req)
1758	checkErr(t, err, "fetching manifest list by digest")
1759	defer resp.Body.Close()
1760
1761	checkResponse(t, "fetching uploaded manifest list", resp, http.StatusOK)
1762	checkHeaders(t, resp, http.Header{
1763		"Docker-Content-Digest": []string{dgst.String()},
1764		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
1765	})
1766
1767	var fetchedManifestListByDigest manifestlist.DeserializedManifestList
1768	dec = json.NewDecoder(resp.Body)
1769	if err := dec.Decode(&fetchedManifestListByDigest); err != nil {
1770		t.Fatalf("error decoding fetched manifest: %v", err)
1771	}
1772
1773	_, fetchedCanonical, err = fetchedManifestListByDigest.Payload()
1774	if err != nil {
1775		t.Fatalf("error getting manifest list payload: %v", err)
1776	}
1777
1778	if !bytes.Equal(fetchedCanonical, canonical) {
1779		t.Fatalf("manifests do not match")
1780	}
1781
1782	// Get by name with etag, gives 304
1783	etag := resp.Header.Get("Etag")
1784	req, err = http.NewRequest("GET", manifestURL, nil)
1785	if err != nil {
1786		t.Fatalf("Error constructing request: %s", err)
1787	}
1788	req.Header.Set("If-None-Match", etag)
1789	resp, err = http.DefaultClient.Do(req)
1790	if err != nil {
1791		t.Fatalf("Error constructing request: %s", err)
1792	}
1793
1794	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
1795
1796	// Get by digest with etag, gives 304
1797	req, err = http.NewRequest("GET", manifestDigestURL, nil)
1798	if err != nil {
1799		t.Fatalf("Error constructing request: %s", err)
1800	}
1801	req.Header.Set("If-None-Match", etag)
1802	resp, err = http.DefaultClient.Do(req)
1803	if err != nil {
1804		t.Fatalf("Error constructing request: %s", err)
1805	}
1806
1807	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
1808
1809	// ------------------
1810	// Fetch as a schema1 manifest
1811	resp, err = http.Get(manifestURL)
1812	if err != nil {
1813		t.Fatalf("unexpected error fetching manifest list as schema1: %v", err)
1814	}
1815	defer resp.Body.Close()
1816
1817	manifestBytes, err := ioutil.ReadAll(resp.Body)
1818	if err != nil {
1819		t.Fatalf("error reading response body: %v", err)
1820	}
1821
1822	checkResponse(t, "fetching uploaded manifest list as schema1", resp, http.StatusOK)
1823
1824	m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes)
1825	if err != nil {
1826		t.Fatalf("unexpected error unmarshalling manifest: %v", err)
1827	}
1828
1829	fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest)
1830	if !ok {
1831		t.Fatalf("expecting schema1 manifest")
1832	}
1833
1834	checkHeaders(t, resp, http.Header{
1835		"Docker-Content-Digest": []string{desc.Digest.String()},
1836		"ETag":                  []string{fmt.Sprintf(`"%s"`, desc.Digest)},
1837	})
1838
1839	if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
1840		t.Fatal("wrong schema version")
1841	}
1842	if fetchedSchema1Manifest.Architecture != "amd64" {
1843		t.Fatal("wrong architecture")
1844	}
1845	if fetchedSchema1Manifest.Name != imageName.Name() {
1846		t.Fatal("wrong image name")
1847	}
1848	if fetchedSchema1Manifest.Tag != tag {
1849		t.Fatal("wrong tag")
1850	}
1851	if len(fetchedSchema1Manifest.FSLayers) != 2 {
1852		t.Fatal("wrong number of FSLayers")
1853	}
1854	layers := args.manifest.(*schema2.DeserializedManifest).Layers
1855	for i := range layers {
1856		if fetchedSchema1Manifest.FSLayers[i].BlobSum != layers[len(layers)-i-1].Digest {
1857			t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
1858		}
1859	}
1860	if len(fetchedSchema1Manifest.History) != 2 {
1861		t.Fatal("wrong number of History entries")
1862	}
1863
1864	// Don't check V1Compatibility fields because we're using randomly-generated
1865	// layers.
1866}
1867
1868func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
1869	imageName := args.imageName
1870	dgst := args.dgst
1871	manifest := args.manifest
1872
1873	ref, _ := reference.WithDigest(imageName, dgst)
1874	manifestDigestURL, _ := env.builder.BuildManifestURL(ref)
1875	// ---------------
1876	// Delete by digest
1877	resp, err := httpDelete(manifestDigestURL)
1878	checkErr(t, err, "deleting manifest by digest")
1879
1880	checkResponse(t, "deleting manifest", resp, http.StatusAccepted)
1881	checkHeaders(t, resp, http.Header{
1882		"Content-Length": []string{"0"},
1883	})
1884
1885	// ---------------
1886	// Attempt to fetch deleted manifest
1887	resp, err = http.Get(manifestDigestURL)
1888	checkErr(t, err, "fetching deleted manifest by digest")
1889	defer resp.Body.Close()
1890
1891	checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
1892
1893	// ---------------
1894	// Delete already deleted manifest by digest
1895	resp, err = httpDelete(manifestDigestURL)
1896	checkErr(t, err, "re-deleting manifest by digest")
1897
1898	checkResponse(t, "re-deleting manifest", resp, http.StatusNotFound)
1899
1900	// --------------------
1901	// Re-upload manifest by digest
1902	resp = putManifest(t, "putting manifest", manifestDigestURL, args.mediaType, manifest)
1903	checkResponse(t, "putting manifest", resp, http.StatusCreated)
1904	checkHeaders(t, resp, http.Header{
1905		"Location":              []string{manifestDigestURL},
1906		"Docker-Content-Digest": []string{dgst.String()},
1907	})
1908
1909	// ---------------
1910	// Attempt to fetch re-uploaded deleted digest
1911	resp, err = http.Get(manifestDigestURL)
1912	checkErr(t, err, "fetching re-uploaded manifest by digest")
1913	defer resp.Body.Close()
1914
1915	checkResponse(t, "fetching re-uploaded manifest", resp, http.StatusOK)
1916	checkHeaders(t, resp, http.Header{
1917		"Docker-Content-Digest": []string{dgst.String()},
1918	})
1919
1920	// ---------------
1921	// Attempt to delete an unknown manifest
1922	unknownDigest := digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
1923	unknownRef, _ := reference.WithDigest(imageName, unknownDigest)
1924	unknownManifestDigestURL, err := env.builder.BuildManifestURL(unknownRef)
1925	checkErr(t, err, "building unknown manifest url")
1926
1927	resp, err = httpDelete(unknownManifestDigestURL)
1928	checkErr(t, err, "delting unknown manifest by digest")
1929	checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
1930
1931	// --------------------
1932	// Upload manifest by tag
1933	tag := "atag"
1934	tagRef, _ := reference.WithTag(imageName, tag)
1935	manifestTagURL, _ := env.builder.BuildManifestURL(tagRef)
1936	resp = putManifest(t, "putting manifest by tag", manifestTagURL, args.mediaType, manifest)
1937	checkResponse(t, "putting manifest by tag", resp, http.StatusCreated)
1938	checkHeaders(t, resp, http.Header{
1939		"Location":              []string{manifestDigestURL},
1940		"Docker-Content-Digest": []string{dgst.String()},
1941	})
1942
1943	tagsURL, err := env.builder.BuildTagsURL(imageName)
1944	if err != nil {
1945		t.Fatalf("unexpected error building tags url: %v", err)
1946	}
1947
1948	// Ensure that the tag is listed.
1949	resp, err = http.Get(tagsURL)
1950	if err != nil {
1951		t.Fatalf("unexpected error getting unknown tags: %v", err)
1952	}
1953	defer resp.Body.Close()
1954
1955	dec := json.NewDecoder(resp.Body)
1956	var tagsResponse tagsAPIResponse
1957	if err := dec.Decode(&tagsResponse); err != nil {
1958		t.Fatalf("unexpected error decoding error response: %v", err)
1959	}
1960
1961	if tagsResponse.Name != imageName.Name() {
1962		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
1963	}
1964
1965	if len(tagsResponse.Tags) != 1 {
1966		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
1967	}
1968
1969	if tagsResponse.Tags[0] != tag {
1970		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
1971	}
1972
1973	// ---------------
1974	// Delete by digest
1975	resp, err = httpDelete(manifestDigestURL)
1976	checkErr(t, err, "deleting manifest by digest")
1977
1978	checkResponse(t, "deleting manifest with tag", resp, http.StatusAccepted)
1979	checkHeaders(t, resp, http.Header{
1980		"Content-Length": []string{"0"},
1981	})
1982
1983	// Ensure that the tag is not listed.
1984	resp, err = http.Get(tagsURL)
1985	if err != nil {
1986		t.Fatalf("unexpected error getting unknown tags: %v", err)
1987	}
1988	defer resp.Body.Close()
1989
1990	dec = json.NewDecoder(resp.Body)
1991	if err := dec.Decode(&tagsResponse); err != nil {
1992		t.Fatalf("unexpected error decoding error response: %v", err)
1993	}
1994
1995	if tagsResponse.Name != imageName.Name() {
1996		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
1997	}
1998
1999	if len(tagsResponse.Tags) != 0 {
2000		t.Fatalf("expected 0 tags in response: %v", tagsResponse.Tags)
2001	}
2002
2003}
2004
2005type testEnv struct {
2006	pk      libtrust.PrivateKey
2007	ctx     context.Context
2008	config  configuration.Configuration
2009	app     *App
2010	server  *httptest.Server
2011	builder *v2.URLBuilder
2012}
2013
2014func newTestEnvMirror(t *testing.T, deleteEnabled bool) *testEnv {
2015	config := configuration.Configuration{
2016		Storage: configuration.Storage{
2017			"testdriver": configuration.Parameters{},
2018			"delete":     configuration.Parameters{"enabled": deleteEnabled},
2019			"maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
2020				"enabled": false,
2021			}},
2022		},
2023		Proxy: configuration.Proxy{
2024			RemoteURL: "http://example.com",
2025		},
2026	}
2027	config.Compatibility.Schema1.Enabled = true
2028
2029	return newTestEnvWithConfig(t, &config)
2030
2031}
2032
2033func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv {
2034	config := configuration.Configuration{
2035		Storage: configuration.Storage{
2036			"testdriver": configuration.Parameters{},
2037			"delete":     configuration.Parameters{"enabled": deleteEnabled},
2038			"maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
2039				"enabled": false,
2040			}},
2041		},
2042	}
2043
2044	config.Compatibility.Schema1.Enabled = true
2045	config.HTTP.Headers = headerConfig
2046
2047	return newTestEnvWithConfig(t, &config)
2048}
2049
2050func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv {
2051	ctx := context.Background()
2052
2053	app := NewApp(ctx, config)
2054	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
2055	builder, err := v2.NewURLBuilderFromString(server.URL+config.HTTP.Prefix, false)
2056
2057	if err != nil {
2058		t.Fatalf("error creating url builder: %v", err)
2059	}
2060
2061	pk, err := libtrust.GenerateECP256PrivateKey()
2062	if err != nil {
2063		t.Fatalf("unexpected error generating private key: %v", err)
2064	}
2065
2066	return &testEnv{
2067		pk:      pk,
2068		ctx:     ctx,
2069		config:  *config,
2070		app:     app,
2071		server:  server,
2072		builder: builder,
2073	}
2074}
2075
2076func (t *testEnv) Shutdown() {
2077	t.server.CloseClientConnections()
2078	t.server.Close()
2079}
2080
2081func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *http.Response {
2082	var body []byte
2083
2084	switch m := v.(type) {
2085	case *schema1.SignedManifest:
2086		_, pl, err := m.Payload()
2087		if err != nil {
2088			t.Fatalf("error getting payload: %v", err)
2089		}
2090		body = pl
2091	case *manifestlist.DeserializedManifestList:
2092		_, pl, err := m.Payload()
2093		if err != nil {
2094			t.Fatalf("error getting payload: %v", err)
2095		}
2096		body = pl
2097	default:
2098		var err error
2099		body, err = json.MarshalIndent(v, "", "   ")
2100		if err != nil {
2101			t.Fatalf("unexpected error marshaling %v: %v", v, err)
2102		}
2103	}
2104
2105	req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
2106	if err != nil {
2107		t.Fatalf("error creating request for %s: %v", msg, err)
2108	}
2109
2110	if contentType != "" {
2111		req.Header.Set("Content-Type", contentType)
2112	}
2113
2114	resp, err := http.DefaultClient.Do(req)
2115	if err != nil {
2116		t.Fatalf("error doing put request while %s: %v", msg, err)
2117	}
2118
2119	return resp
2120}
2121
2122func startPushLayer(t *testing.T, env *testEnv, name reference.Named) (location string, uuid string) {
2123	layerUploadURL, err := env.builder.BuildBlobUploadURL(name)
2124	if err != nil {
2125		t.Fatalf("unexpected error building layer upload url: %v", err)
2126	}
2127
2128	u, err := url.Parse(layerUploadURL)
2129	if err != nil {
2130		t.Fatalf("error parsing layer upload URL: %v", err)
2131	}
2132
2133	base, err := url.Parse(env.server.URL)
2134	if err != nil {
2135		t.Fatalf("error parsing server URL: %v", err)
2136	}
2137
2138	layerUploadURL = base.ResolveReference(u).String()
2139	resp, err := http.Post(layerUploadURL, "", nil)
2140	if err != nil {
2141		t.Fatalf("unexpected error starting layer push: %v", err)
2142	}
2143
2144	defer resp.Body.Close()
2145
2146	checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name.String()), resp, http.StatusAccepted)
2147
2148	u, err = url.Parse(resp.Header.Get("Location"))
2149	if err != nil {
2150		t.Fatalf("error parsing location header: %v", err)
2151	}
2152
2153	uuid = path.Base(u.Path)
2154	checkHeaders(t, resp, http.Header{
2155		"Location":           []string{"*"},
2156		"Content-Length":     []string{"0"},
2157		"Docker-Upload-UUID": []string{uuid},
2158	})
2159
2160	return resp.Header.Get("Location"), uuid
2161}
2162
2163// doPushLayer pushes the layer content returning the url on success returning
2164// the response. If you're only expecting a successful response, use pushLayer.
2165func doPushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst digest.Digest, uploadURLBase string, body io.Reader) (*http.Response, error) {
2166	u, err := url.Parse(uploadURLBase)
2167	if err != nil {
2168		t.Fatalf("unexpected error parsing pushLayer url: %v", err)
2169	}
2170
2171	u.RawQuery = url.Values{
2172		"_state": u.Query()["_state"],
2173		"digest": []string{dgst.String()},
2174	}.Encode()
2175
2176	uploadURL := u.String()
2177
2178	// Just do a monolithic upload
2179	req, err := http.NewRequest("PUT", uploadURL, body)
2180	if err != nil {
2181		t.Fatalf("unexpected error creating new request: %v", err)
2182	}
2183
2184	return http.DefaultClient.Do(req)
2185}
2186
2187// pushLayer pushes the layer content returning the url on success.
2188func pushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst digest.Digest, uploadURLBase string, body io.Reader) string {
2189	digester := digest.Canonical.Digester()
2190
2191	resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, io.TeeReader(body, digester.Hash()))
2192	if err != nil {
2193		t.Fatalf("unexpected error doing push layer request: %v", err)
2194	}
2195	defer resp.Body.Close()
2196
2197	checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
2198
2199	if err != nil {
2200		t.Fatalf("error generating sha256 digest of body")
2201	}
2202
2203	sha256Dgst := digester.Digest()
2204
2205	ref, _ := reference.WithDigest(name, sha256Dgst)
2206	expectedLayerURL, err := ub.BuildBlobURL(ref)
2207	if err != nil {
2208		t.Fatalf("error building expected layer url: %v", err)
2209	}
2210
2211	checkHeaders(t, resp, http.Header{
2212		"Location":              []string{expectedLayerURL},
2213		"Content-Length":        []string{"0"},
2214		"Docker-Content-Digest": []string{sha256Dgst.String()},
2215	})
2216
2217	return resp.Header.Get("Location")
2218}
2219
2220func finishUpload(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLBase string, dgst digest.Digest) string {
2221	resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, nil)
2222	if err != nil {
2223		t.Fatalf("unexpected error doing push layer request: %v", err)
2224	}
2225	defer resp.Body.Close()
2226
2227	checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
2228
2229	ref, _ := reference.WithDigest(name, dgst)
2230	expectedLayerURL, err := ub.BuildBlobURL(ref)
2231	if err != nil {
2232		t.Fatalf("error building expected layer url: %v", err)
2233	}
2234
2235	checkHeaders(t, resp, http.Header{
2236		"Location":              []string{expectedLayerURL},
2237		"Content-Length":        []string{"0"},
2238		"Docker-Content-Digest": []string{dgst.String()},
2239	})
2240
2241	return resp.Header.Get("Location")
2242}
2243
2244func doPushChunk(t *testing.T, uploadURLBase string, body io.Reader) (*http.Response, digest.Digest, error) {
2245	u, err := url.Parse(uploadURLBase)
2246	if err != nil {
2247		t.Fatalf("unexpected error parsing pushLayer url: %v", err)
2248	}
2249
2250	u.RawQuery = url.Values{
2251		"_state": u.Query()["_state"],
2252	}.Encode()
2253
2254	uploadURL := u.String()
2255
2256	digester := digest.Canonical.Digester()
2257
2258	req, err := http.NewRequest("PATCH", uploadURL, io.TeeReader(body, digester.Hash()))
2259	if err != nil {
2260		t.Fatalf("unexpected error creating new request: %v", err)
2261	}
2262	req.Header.Set("Content-Type", "application/octet-stream")
2263
2264	resp, err := http.DefaultClient.Do(req)
2265
2266	return resp, digester.Digest(), err
2267}
2268
2269func pushChunk(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLBase string, body io.Reader, length int64) (string, digest.Digest) {
2270	resp, dgst, err := doPushChunk(t, uploadURLBase, body)
2271	if err != nil {
2272		t.Fatalf("unexpected error doing push layer request: %v", err)
2273	}
2274	defer resp.Body.Close()
2275
2276	checkResponse(t, "putting chunk", resp, http.StatusAccepted)
2277
2278	if err != nil {
2279		t.Fatalf("error generating sha256 digest of body")
2280	}
2281
2282	checkHeaders(t, resp, http.Header{
2283		"Range":          []string{fmt.Sprintf("0-%d", length-1)},
2284		"Content-Length": []string{"0"},
2285	})
2286
2287	return resp.Header.Get("Location"), dgst
2288}
2289
2290func checkResponse(t *testing.T, msg string, resp *http.Response, expectedStatus int) {
2291	if resp.StatusCode != expectedStatus {
2292		t.Logf("unexpected status %s: %v != %v", msg, resp.StatusCode, expectedStatus)
2293		maybeDumpResponse(t, resp)
2294
2295		t.FailNow()
2296	}
2297
2298	// We expect the headers included in the configuration, unless the
2299	// status code is 405 (Method Not Allowed), which means the handler
2300	// doesn't even get called.
2301	if resp.StatusCode != 405 && !reflect.DeepEqual(resp.Header["X-Content-Type-Options"], []string{"nosniff"}) {
2302		t.Logf("missing or incorrect header X-Content-Type-Options %s", msg)
2303		maybeDumpResponse(t, resp)
2304
2305		t.FailNow()
2306	}
2307}
2308
2309// checkBodyHasErrorCodes ensures the body is an error body and has the
2310// expected error codes, returning the error structure, the json slice and a
2311// count of the errors by code.
2312func checkBodyHasErrorCodes(t *testing.T, msg string, resp *http.Response, errorCodes ...errcode.ErrorCode) (errcode.Errors, []byte, map[errcode.ErrorCode]int) {
2313	p, err := ioutil.ReadAll(resp.Body)
2314	if err != nil {
2315		t.Fatalf("unexpected error reading body %s: %v", msg, err)
2316	}
2317
2318	var errs errcode.Errors
2319	if err := json.Unmarshal(p, &errs); err != nil {
2320		t.Fatalf("unexpected error decoding error response: %v", err)
2321	}
2322
2323	if len(errs) == 0 {
2324		t.Fatalf("expected errors in response")
2325	}
2326
2327	// TODO(stevvooe): Shoot. The error setup is not working out. The content-
2328	// type headers are being set after writing the status code.
2329	// if resp.Header.Get("Content-Type") != "application/json; charset=utf-8" {
2330	// 	t.Fatalf("unexpected content type: %v != 'application/json'",
2331	// 		resp.Header.Get("Content-Type"))
2332	// }
2333
2334	expected := map[errcode.ErrorCode]struct{}{}
2335	counts := map[errcode.ErrorCode]int{}
2336
2337	// Initialize map with zeros for expected
2338	for _, code := range errorCodes {
2339		expected[code] = struct{}{}
2340		counts[code] = 0
2341	}
2342
2343	for _, e := range errs {
2344		err, ok := e.(errcode.ErrorCoder)
2345		if !ok {
2346			t.Fatalf("not an ErrorCoder: %#v", e)
2347		}
2348		if _, ok := expected[err.ErrorCode()]; !ok {
2349			t.Fatalf("unexpected error code %v encountered during %s: %s ", err.ErrorCode(), msg, string(p))
2350		}
2351		counts[err.ErrorCode()]++
2352	}
2353
2354	// Ensure that counts of expected errors were all non-zero
2355	for code := range expected {
2356		if counts[code] == 0 {
2357			t.Fatalf("expected error code %v not encounterd during %s: %s", code, msg, string(p))
2358		}
2359	}
2360
2361	return errs, p, counts
2362}
2363
2364func maybeDumpResponse(t *testing.T, resp *http.Response) {
2365	if d, err := httputil.DumpResponse(resp, true); err != nil {
2366		t.Logf("error dumping response: %v", err)
2367	} else {
2368		t.Logf("response:\n%s", string(d))
2369	}
2370}
2371
2372// matchHeaders checks that the response has at least the headers. If not, the
2373// test will fail. If a passed in header value is "*", any non-zero value will
2374// suffice as a match.
2375func checkHeaders(t *testing.T, resp *http.Response, headers http.Header) {
2376	for k, vs := range headers {
2377		if resp.Header.Get(k) == "" {
2378			t.Fatalf("response missing header %q", k)
2379		}
2380
2381		for _, v := range vs {
2382			if v == "*" {
2383				// Just ensure there is some value.
2384				if len(resp.Header[http.CanonicalHeaderKey(k)]) > 0 {
2385					continue
2386				}
2387			}
2388
2389			for _, hv := range resp.Header[http.CanonicalHeaderKey(k)] {
2390				if hv != v {
2391					t.Fatalf("%+v %v header value not matched in response: %q != %q", resp.Header, k, hv, v)
2392				}
2393			}
2394		}
2395	}
2396}
2397
2398func checkErr(t *testing.T, err error, msg string) {
2399	if err != nil {
2400		t.Fatalf("unexpected error %s: %v", msg, err)
2401	}
2402}
2403
2404func createRepository(env *testEnv, t *testing.T, imageName string, tag string) digest.Digest {
2405	imageNameRef, err := reference.WithName(imageName)
2406	if err != nil {
2407		t.Fatalf("unable to parse reference: %v", err)
2408	}
2409
2410	unsignedManifest := &schema1.Manifest{
2411		Versioned: manifest.Versioned{
2412			SchemaVersion: 1,
2413		},
2414		Name: imageName,
2415		Tag:  tag,
2416		FSLayers: []schema1.FSLayer{
2417			{
2418				BlobSum: "asdf",
2419			},
2420		},
2421		History: []schema1.History{
2422			{
2423				V1Compatibility: "",
2424			},
2425		},
2426	}
2427
2428	// Push 2 random layers
2429	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
2430
2431	for i := range unsignedManifest.FSLayers {
2432		rs, dgst, err := testutil.CreateRandomTarFile()
2433		if err != nil {
2434			t.Fatalf("error creating random layer %d: %v", i, err)
2435		}
2436
2437		expectedLayers[dgst] = rs
2438		unsignedManifest.FSLayers[i].BlobSum = dgst
2439		uploadURLBase, _ := startPushLayer(t, env, imageNameRef)
2440		pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs)
2441	}
2442
2443	signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
2444	if err != nil {
2445		t.Fatalf("unexpected error signing manifest: %v", err)
2446	}
2447
2448	dgst := digest.FromBytes(signedManifest.Canonical)
2449
2450	// Create this repository by tag to ensure the tag mapping is made in the registry
2451	tagRef, _ := reference.WithTag(imageNameRef, tag)
2452	manifestDigestURL, err := env.builder.BuildManifestURL(tagRef)
2453	checkErr(t, err, "building manifest url")
2454
2455	digestRef, _ := reference.WithDigest(imageNameRef, dgst)
2456	location, err := env.builder.BuildManifestURL(digestRef)
2457	checkErr(t, err, "building location URL")
2458
2459	resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
2460	checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
2461	checkHeaders(t, resp, http.Header{
2462		"Location":              []string{location},
2463		"Docker-Content-Digest": []string{dgst.String()},
2464	})
2465	return dgst
2466}
2467
2468// Test mutation operations on a registry configured as a cache.  Ensure that they return
2469// appropriate errors.
2470func TestRegistryAsCacheMutationAPIs(t *testing.T) {
2471	deleteEnabled := true
2472	env := newTestEnvMirror(t, deleteEnabled)
2473	defer env.Shutdown()
2474
2475	imageName, _ := reference.WithName("foo/bar")
2476	tag := "latest"
2477	tagRef, _ := reference.WithTag(imageName, tag)
2478	manifestURL, err := env.builder.BuildManifestURL(tagRef)
2479	if err != nil {
2480		t.Fatalf("unexpected error building base url: %v", err)
2481	}
2482
2483	// Manifest upload
2484	m := &schema1.Manifest{
2485		Versioned: manifest.Versioned{
2486			SchemaVersion: 1,
2487		},
2488		Name:     imageName.Name(),
2489		Tag:      tag,
2490		FSLayers: []schema1.FSLayer{},
2491		History:  []schema1.History{},
2492	}
2493
2494	sm, err := schema1.Sign(m, env.pk)
2495	if err != nil {
2496		t.Fatalf("error signing manifest: %v", err)
2497	}
2498
2499	resp := putManifest(t, "putting unsigned manifest", manifestURL, "", sm)
2500	checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2501
2502	// Manifest Delete
2503	resp, _ = httpDelete(manifestURL)
2504	checkResponse(t, "deleting signed manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2505
2506	// Blob upload initialization
2507	layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
2508	if err != nil {
2509		t.Fatalf("unexpected error building layer upload url: %v", err)
2510	}
2511
2512	resp, err = http.Post(layerUploadURL, "", nil)
2513	if err != nil {
2514		t.Fatalf("unexpected error starting layer push: %v", err)
2515	}
2516	defer resp.Body.Close()
2517
2518	checkResponse(t, fmt.Sprintf("starting layer push to cache %v", imageName), resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2519
2520	// Blob Delete
2521	ref, _ := reference.WithDigest(imageName, digestSha256EmptyTar)
2522	blobURL, _ := env.builder.BuildBlobURL(ref)
2523	resp, _ = httpDelete(blobURL)
2524	checkResponse(t, "deleting blob from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
2525
2526}
2527
2528func TestProxyManifestGetByTag(t *testing.T) {
2529	truthConfig := configuration.Configuration{
2530		Storage: configuration.Storage{
2531			"testdriver": configuration.Parameters{},
2532			"maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
2533				"enabled": false,
2534			}},
2535		},
2536	}
2537	truthConfig.Compatibility.Schema1.Enabled = true
2538	truthConfig.HTTP.Headers = headerConfig
2539
2540	imageName, _ := reference.WithName("foo/bar")
2541	tag := "latest"
2542
2543	truthEnv := newTestEnvWithConfig(t, &truthConfig)
2544	defer truthEnv.Shutdown()
2545	// create a repository in the truth registry
2546	dgst := createRepository(truthEnv, t, imageName.Name(), tag)
2547
2548	proxyConfig := configuration.Configuration{
2549		Storage: configuration.Storage{
2550			"testdriver": configuration.Parameters{},
2551		},
2552		Proxy: configuration.Proxy{
2553			RemoteURL: truthEnv.server.URL,
2554		},
2555	}
2556	proxyConfig.Compatibility.Schema1.Enabled = true
2557	proxyConfig.HTTP.Headers = headerConfig
2558
2559	proxyEnv := newTestEnvWithConfig(t, &proxyConfig)
2560	defer proxyEnv.Shutdown()
2561
2562	digestRef, _ := reference.WithDigest(imageName, dgst)
2563	manifestDigestURL, err := proxyEnv.builder.BuildManifestURL(digestRef)
2564	checkErr(t, err, "building manifest url")
2565
2566	resp, err := http.Get(manifestDigestURL)
2567	checkErr(t, err, "fetching manifest from proxy by digest")
2568	defer resp.Body.Close()
2569
2570	tagRef, _ := reference.WithTag(imageName, tag)
2571	manifestTagURL, err := proxyEnv.builder.BuildManifestURL(tagRef)
2572	checkErr(t, err, "building manifest url")
2573
2574	resp, err = http.Get(manifestTagURL)
2575	checkErr(t, err, "fetching manifest from proxy by tag (error check 1)")
2576	defer resp.Body.Close()
2577	checkResponse(t, "fetching manifest from proxy by tag (response check 1)", resp, http.StatusOK)
2578	checkHeaders(t, resp, http.Header{
2579		"Docker-Content-Digest": []string{dgst.String()},
2580	})
2581
2582	// Create another manifest in the remote with the same image/tag pair
2583	newDigest := createRepository(truthEnv, t, imageName.Name(), tag)
2584	if dgst == newDigest {
2585		t.Fatalf("non-random test data")
2586	}
2587
2588	// fetch it with the same proxy URL as before.  Ensure the updated content is at the same tag
2589	resp, err = http.Get(manifestTagURL)
2590	checkErr(t, err, "fetching manifest from proxy by tag (error check 2)")
2591	defer resp.Body.Close()
2592	checkResponse(t, "fetching manifest from proxy by tag (response check 2)", resp, http.StatusOK)
2593	checkHeaders(t, resp, http.Header{
2594		"Docker-Content-Digest": []string{newDigest.String()},
2595	})
2596}
2597