1package registry
2
3import (
4	"fmt"
5	"net/http"
6	"net/http/httputil"
7	"net/url"
8	"strings"
9	"testing"
10
11	"github.com/docker/distribution/reference"
12	"github.com/docker/distribution/registry/client/transport"
13	"github.com/docker/docker/api/types"
14	registrytypes "github.com/docker/docker/api/types/registry"
15	"github.com/stretchr/testify/assert"
16)
17
18var (
19	token = []string{"fake-token"}
20)
21
22const (
23	imageID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d"
24	REPO    = "foo42/bar"
25)
26
27func spawnTestRegistrySession(t *testing.T) *Session {
28	authConfig := &types.AuthConfig{}
29	endpoint, err := NewV1Endpoint(makeIndex("/v1/"), "", nil)
30	if err != nil {
31		t.Fatal(err)
32	}
33	userAgent := "docker test client"
34	var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log}
35	tr = transport.NewTransport(AuthTransport(tr, authConfig, false), Headers(userAgent, nil)...)
36	client := HTTPClient(tr)
37	r, err := NewSession(client, authConfig, endpoint)
38	if err != nil {
39		t.Fatal(err)
40	}
41	// In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
42	// header while authenticating, in order to retrieve a token that can be later used to
43	// perform authenticated actions.
44	//
45	// The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
46	// it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
47	//
48	// Because we know that the client's transport is an `*authTransport` we simply cast it,
49	// in order to set the internal cached token to the fake token, and thus send that fake token
50	// upon every subsequent requests.
51	r.client.Transport.(*authTransport).token = token
52	return r
53}
54
55func TestPingRegistryEndpoint(t *testing.T) {
56	testPing := func(index *registrytypes.IndexInfo, expectedStandalone bool, assertMessage string) {
57		ep, err := NewV1Endpoint(index, "", nil)
58		if err != nil {
59			t.Fatal(err)
60		}
61		regInfo, err := ep.Ping()
62		if err != nil {
63			t.Fatal(err)
64		}
65
66		assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
67	}
68
69	testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
70	testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)")
71	testPing(makePublicIndex(), false, "Expected standalone to be false for public index")
72}
73
74func TestEndpoint(t *testing.T) {
75	// Simple wrapper to fail test if err != nil
76	expandEndpoint := func(index *registrytypes.IndexInfo) *V1Endpoint {
77		endpoint, err := NewV1Endpoint(index, "", nil)
78		if err != nil {
79			t.Fatal(err)
80		}
81		return endpoint
82	}
83
84	assertInsecureIndex := func(index *registrytypes.IndexInfo) {
85		index.Secure = true
86		_, err := NewV1Endpoint(index, "", nil)
87		assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
88		assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry  error for insecure index")
89		index.Secure = false
90	}
91
92	assertSecureIndex := func(index *registrytypes.IndexInfo) {
93		index.Secure = true
94		_, err := NewV1Endpoint(index, "", nil)
95		assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
96		assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
97		index.Secure = false
98	}
99
100	index := &registrytypes.IndexInfo{}
101	index.Name = makeURL("/v1/")
102	endpoint := expandEndpoint(index)
103	assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
104	assertInsecureIndex(index)
105
106	index.Name = makeURL("")
107	endpoint = expandEndpoint(index)
108	assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
109	assertInsecureIndex(index)
110
111	httpURL := makeURL("")
112	index.Name = strings.SplitN(httpURL, "://", 2)[1]
113	endpoint = expandEndpoint(index)
114	assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
115	assertInsecureIndex(index)
116
117	index.Name = makeHTTPSURL("/v1/")
118	endpoint = expandEndpoint(index)
119	assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
120	assertSecureIndex(index)
121
122	index.Name = makeHTTPSURL("")
123	endpoint = expandEndpoint(index)
124	assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
125	assertSecureIndex(index)
126
127	httpsURL := makeHTTPSURL("")
128	index.Name = strings.SplitN(httpsURL, "://", 2)[1]
129	endpoint = expandEndpoint(index)
130	assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
131	assertSecureIndex(index)
132
133	badEndpoints := []string{
134		"http://127.0.0.1/v1/",
135		"https://127.0.0.1/v1/",
136		"http://127.0.0.1",
137		"https://127.0.0.1",
138		"127.0.0.1",
139	}
140	for _, address := range badEndpoints {
141		index.Name = address
142		_, err := NewV1Endpoint(index, "", nil)
143		checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
144	}
145}
146
147func TestGetRemoteHistory(t *testing.T) {
148	r := spawnTestRegistrySession(t)
149	hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
150	if err != nil {
151		t.Fatal(err)
152	}
153	assertEqual(t, len(hist), 2, "Expected 2 images in history")
154	assertEqual(t, hist[0], imageID, "Expected "+imageID+"as first ancestry")
155	assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
156		"Unexpected second ancestry")
157}
158
159func TestLookupRemoteImage(t *testing.T) {
160	r := spawnTestRegistrySession(t)
161	err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
162	assertEqual(t, err, nil, "Expected error of remote lookup to nil")
163	if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
164		t.Fatal("Expected error of remote lookup to not nil")
165	}
166}
167
168func TestGetRemoteImageJSON(t *testing.T) {
169	r := spawnTestRegistrySession(t)
170	json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
171	if err != nil {
172		t.Fatal(err)
173	}
174	assertEqual(t, size, int64(154), "Expected size 154")
175	if len(json) == 0 {
176		t.Fatal("Expected non-empty json")
177	}
178
179	_, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
180	if err == nil {
181		t.Fatal("Expected image not found error")
182	}
183}
184
185func TestGetRemoteImageLayer(t *testing.T) {
186	r := spawnTestRegistrySession(t)
187	data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
188	if err != nil {
189		t.Fatal(err)
190	}
191	if data == nil {
192		t.Fatal("Expected non-nil data result")
193	}
194
195	_, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
196	if err == nil {
197		t.Fatal("Expected image not found error")
198	}
199}
200
201func TestGetRemoteTag(t *testing.T) {
202	r := spawnTestRegistrySession(t)
203	repoRef, err := reference.ParseNormalizedNamed(REPO)
204	if err != nil {
205		t.Fatal(err)
206	}
207	tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, repoRef, "test")
208	if err != nil {
209		t.Fatal(err)
210	}
211	assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID)
212
213	bazRef, err := reference.ParseNormalizedNamed("foo42/baz")
214	if err != nil {
215		t.Fatal(err)
216	}
217	_, err = r.GetRemoteTag([]string{makeURL("/v1/")}, bazRef, "foo")
218	if err != ErrRepoNotFound {
219		t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo")
220	}
221}
222
223func TestGetRemoteTags(t *testing.T) {
224	r := spawnTestRegistrySession(t)
225	repoRef, err := reference.ParseNormalizedNamed(REPO)
226	if err != nil {
227		t.Fatal(err)
228	}
229	tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, repoRef)
230	if err != nil {
231		t.Fatal(err)
232	}
233	assertEqual(t, len(tags), 2, "Expected two tags")
234	assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
235	assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID)
236
237	bazRef, err := reference.ParseNormalizedNamed("foo42/baz")
238	if err != nil {
239		t.Fatal(err)
240	}
241	_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, bazRef)
242	if err != ErrRepoNotFound {
243		t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo")
244	}
245}
246
247func TestGetRepositoryData(t *testing.T) {
248	r := spawnTestRegistrySession(t)
249	parsedURL, err := url.Parse(makeURL("/v1/"))
250	if err != nil {
251		t.Fatal(err)
252	}
253	host := "http://" + parsedURL.Host + "/v1/"
254	repoRef, err := reference.ParseNormalizedNamed(REPO)
255	if err != nil {
256		t.Fatal(err)
257	}
258	data, err := r.GetRepositoryData(repoRef)
259	if err != nil {
260		t.Fatal(err)
261	}
262	assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList")
263	assertEqual(t, len(data.Endpoints), 2,
264		fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints)))
265	assertEqual(t, data.Endpoints[0], host,
266		fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0]))
267	assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/",
268		fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1]))
269
270}
271
272func TestPushImageJSONRegistry(t *testing.T) {
273	r := spawnTestRegistrySession(t)
274	imgData := &ImgData{
275		ID:       "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
276		Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
277	}
278
279	err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
280	if err != nil {
281		t.Fatal(err)
282	}
283}
284
285func TestPushImageLayerRegistry(t *testing.T) {
286	r := spawnTestRegistrySession(t)
287	layer := strings.NewReader("")
288	_, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
289	if err != nil {
290		t.Fatal(err)
291	}
292}
293
294func TestParseRepositoryInfo(t *testing.T) {
295	type staticRepositoryInfo struct {
296		Index         *registrytypes.IndexInfo
297		RemoteName    string
298		CanonicalName string
299		LocalName     string
300		Official      bool
301	}
302
303	expectedRepoInfos := map[string]staticRepositoryInfo{
304		"fooo/bar": {
305			Index: &registrytypes.IndexInfo{
306				Name:     IndexName,
307				Official: true,
308			},
309			RemoteName:    "fooo/bar",
310			LocalName:     "fooo/bar",
311			CanonicalName: "docker.io/fooo/bar",
312			Official:      false,
313		},
314		"library/ubuntu": {
315			Index: &registrytypes.IndexInfo{
316				Name:     IndexName,
317				Official: true,
318			},
319			RemoteName:    "library/ubuntu",
320			LocalName:     "ubuntu",
321			CanonicalName: "docker.io/library/ubuntu",
322			Official:      true,
323		},
324		"nonlibrary/ubuntu": {
325			Index: &registrytypes.IndexInfo{
326				Name:     IndexName,
327				Official: true,
328			},
329			RemoteName:    "nonlibrary/ubuntu",
330			LocalName:     "nonlibrary/ubuntu",
331			CanonicalName: "docker.io/nonlibrary/ubuntu",
332			Official:      false,
333		},
334		"ubuntu": {
335			Index: &registrytypes.IndexInfo{
336				Name:     IndexName,
337				Official: true,
338			},
339			RemoteName:    "library/ubuntu",
340			LocalName:     "ubuntu",
341			CanonicalName: "docker.io/library/ubuntu",
342			Official:      true,
343		},
344		"other/library": {
345			Index: &registrytypes.IndexInfo{
346				Name:     IndexName,
347				Official: true,
348			},
349			RemoteName:    "other/library",
350			LocalName:     "other/library",
351			CanonicalName: "docker.io/other/library",
352			Official:      false,
353		},
354		"127.0.0.1:8000/private/moonbase": {
355			Index: &registrytypes.IndexInfo{
356				Name:     "127.0.0.1:8000",
357				Official: false,
358			},
359			RemoteName:    "private/moonbase",
360			LocalName:     "127.0.0.1:8000/private/moonbase",
361			CanonicalName: "127.0.0.1:8000/private/moonbase",
362			Official:      false,
363		},
364		"127.0.0.1:8000/privatebase": {
365			Index: &registrytypes.IndexInfo{
366				Name:     "127.0.0.1:8000",
367				Official: false,
368			},
369			RemoteName:    "privatebase",
370			LocalName:     "127.0.0.1:8000/privatebase",
371			CanonicalName: "127.0.0.1:8000/privatebase",
372			Official:      false,
373		},
374		"localhost:8000/private/moonbase": {
375			Index: &registrytypes.IndexInfo{
376				Name:     "localhost:8000",
377				Official: false,
378			},
379			RemoteName:    "private/moonbase",
380			LocalName:     "localhost:8000/private/moonbase",
381			CanonicalName: "localhost:8000/private/moonbase",
382			Official:      false,
383		},
384		"localhost:8000/privatebase": {
385			Index: &registrytypes.IndexInfo{
386				Name:     "localhost:8000",
387				Official: false,
388			},
389			RemoteName:    "privatebase",
390			LocalName:     "localhost:8000/privatebase",
391			CanonicalName: "localhost:8000/privatebase",
392			Official:      false,
393		},
394		"example.com/private/moonbase": {
395			Index: &registrytypes.IndexInfo{
396				Name:     "example.com",
397				Official: false,
398			},
399			RemoteName:    "private/moonbase",
400			LocalName:     "example.com/private/moonbase",
401			CanonicalName: "example.com/private/moonbase",
402			Official:      false,
403		},
404		"example.com/privatebase": {
405			Index: &registrytypes.IndexInfo{
406				Name:     "example.com",
407				Official: false,
408			},
409			RemoteName:    "privatebase",
410			LocalName:     "example.com/privatebase",
411			CanonicalName: "example.com/privatebase",
412			Official:      false,
413		},
414		"example.com:8000/private/moonbase": {
415			Index: &registrytypes.IndexInfo{
416				Name:     "example.com:8000",
417				Official: false,
418			},
419			RemoteName:    "private/moonbase",
420			LocalName:     "example.com:8000/private/moonbase",
421			CanonicalName: "example.com:8000/private/moonbase",
422			Official:      false,
423		},
424		"example.com:8000/privatebase": {
425			Index: &registrytypes.IndexInfo{
426				Name:     "example.com:8000",
427				Official: false,
428			},
429			RemoteName:    "privatebase",
430			LocalName:     "example.com:8000/privatebase",
431			CanonicalName: "example.com:8000/privatebase",
432			Official:      false,
433		},
434		"localhost/private/moonbase": {
435			Index: &registrytypes.IndexInfo{
436				Name:     "localhost",
437				Official: false,
438			},
439			RemoteName:    "private/moonbase",
440			LocalName:     "localhost/private/moonbase",
441			CanonicalName: "localhost/private/moonbase",
442			Official:      false,
443		},
444		"localhost/privatebase": {
445			Index: &registrytypes.IndexInfo{
446				Name:     "localhost",
447				Official: false,
448			},
449			RemoteName:    "privatebase",
450			LocalName:     "localhost/privatebase",
451			CanonicalName: "localhost/privatebase",
452			Official:      false,
453		},
454		IndexName + "/public/moonbase": {
455			Index: &registrytypes.IndexInfo{
456				Name:     IndexName,
457				Official: true,
458			},
459			RemoteName:    "public/moonbase",
460			LocalName:     "public/moonbase",
461			CanonicalName: "docker.io/public/moonbase",
462			Official:      false,
463		},
464		"index." + IndexName + "/public/moonbase": {
465			Index: &registrytypes.IndexInfo{
466				Name:     IndexName,
467				Official: true,
468			},
469			RemoteName:    "public/moonbase",
470			LocalName:     "public/moonbase",
471			CanonicalName: "docker.io/public/moonbase",
472			Official:      false,
473		},
474		"ubuntu-12.04-base": {
475			Index: &registrytypes.IndexInfo{
476				Name:     IndexName,
477				Official: true,
478			},
479			RemoteName:    "library/ubuntu-12.04-base",
480			LocalName:     "ubuntu-12.04-base",
481			CanonicalName: "docker.io/library/ubuntu-12.04-base",
482			Official:      true,
483		},
484		IndexName + "/ubuntu-12.04-base": {
485			Index: &registrytypes.IndexInfo{
486				Name:     IndexName,
487				Official: true,
488			},
489			RemoteName:    "library/ubuntu-12.04-base",
490			LocalName:     "ubuntu-12.04-base",
491			CanonicalName: "docker.io/library/ubuntu-12.04-base",
492			Official:      true,
493		},
494		"index." + IndexName + "/ubuntu-12.04-base": {
495			Index: &registrytypes.IndexInfo{
496				Name:     IndexName,
497				Official: true,
498			},
499			RemoteName:    "library/ubuntu-12.04-base",
500			LocalName:     "ubuntu-12.04-base",
501			CanonicalName: "docker.io/library/ubuntu-12.04-base",
502			Official:      true,
503		},
504	}
505
506	for reposName, expectedRepoInfo := range expectedRepoInfos {
507		named, err := reference.ParseNormalizedNamed(reposName)
508		if err != nil {
509			t.Error(err)
510		}
511
512		repoInfo, err := ParseRepositoryInfo(named)
513		if err != nil {
514			t.Error(err)
515		} else {
516			checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName)
517			checkEqual(t, reference.Path(repoInfo.Name), expectedRepoInfo.RemoteName, reposName)
518			checkEqual(t, reference.FamiliarName(repoInfo.Name), expectedRepoInfo.LocalName, reposName)
519			checkEqual(t, repoInfo.Name.Name(), expectedRepoInfo.CanonicalName, reposName)
520			checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
521			checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
522		}
523	}
524}
525
526func TestNewIndexInfo(t *testing.T) {
527	testIndexInfo := func(config *serviceConfig, expectedIndexInfos map[string]*registrytypes.IndexInfo) {
528		for indexName, expectedIndexInfo := range expectedIndexInfos {
529			index, err := newIndexInfo(config, indexName)
530			if err != nil {
531				t.Fatal(err)
532			} else {
533				checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name")
534				checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official")
535				checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure")
536				checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors")
537			}
538		}
539	}
540
541	config := emptyServiceConfig
542	noMirrors := []string{}
543	expectedIndexInfos := map[string]*registrytypes.IndexInfo{
544		IndexName: {
545			Name:     IndexName,
546			Official: true,
547			Secure:   true,
548			Mirrors:  noMirrors,
549		},
550		"index." + IndexName: {
551			Name:     IndexName,
552			Official: true,
553			Secure:   true,
554			Mirrors:  noMirrors,
555		},
556		"example.com": {
557			Name:     "example.com",
558			Official: false,
559			Secure:   true,
560			Mirrors:  noMirrors,
561		},
562		"127.0.0.1:5000": {
563			Name:     "127.0.0.1:5000",
564			Official: false,
565			Secure:   false,
566			Mirrors:  noMirrors,
567		},
568	}
569	testIndexInfo(config, expectedIndexInfos)
570
571	publicMirrors := []string{"http://mirror1.local", "http://mirror2.local"}
572	var err error
573	config, err = makeServiceConfig(publicMirrors, []string{"example.com"})
574	if err != nil {
575		t.Fatal(err)
576	}
577
578	expectedIndexInfos = map[string]*registrytypes.IndexInfo{
579		IndexName: {
580			Name:     IndexName,
581			Official: true,
582			Secure:   true,
583			Mirrors:  publicMirrors,
584		},
585		"index." + IndexName: {
586			Name:     IndexName,
587			Official: true,
588			Secure:   true,
589			Mirrors:  publicMirrors,
590		},
591		"example.com": {
592			Name:     "example.com",
593			Official: false,
594			Secure:   false,
595			Mirrors:  noMirrors,
596		},
597		"example.com:5000": {
598			Name:     "example.com:5000",
599			Official: false,
600			Secure:   true,
601			Mirrors:  noMirrors,
602		},
603		"127.0.0.1": {
604			Name:     "127.0.0.1",
605			Official: false,
606			Secure:   false,
607			Mirrors:  noMirrors,
608		},
609		"127.0.0.1:5000": {
610			Name:     "127.0.0.1:5000",
611			Official: false,
612			Secure:   false,
613			Mirrors:  noMirrors,
614		},
615		"other.com": {
616			Name:     "other.com",
617			Official: false,
618			Secure:   true,
619			Mirrors:  noMirrors,
620		},
621	}
622	testIndexInfo(config, expectedIndexInfos)
623
624	config, err = makeServiceConfig(nil, []string{"42.42.0.0/16"})
625	if err != nil {
626		t.Fatal(err)
627	}
628	expectedIndexInfos = map[string]*registrytypes.IndexInfo{
629		"example.com": {
630			Name:     "example.com",
631			Official: false,
632			Secure:   false,
633			Mirrors:  noMirrors,
634		},
635		"example.com:5000": {
636			Name:     "example.com:5000",
637			Official: false,
638			Secure:   false,
639			Mirrors:  noMirrors,
640		},
641		"127.0.0.1": {
642			Name:     "127.0.0.1",
643			Official: false,
644			Secure:   false,
645			Mirrors:  noMirrors,
646		},
647		"127.0.0.1:5000": {
648			Name:     "127.0.0.1:5000",
649			Official: false,
650			Secure:   false,
651			Mirrors:  noMirrors,
652		},
653		"other.com": {
654			Name:     "other.com",
655			Official: false,
656			Secure:   true,
657			Mirrors:  noMirrors,
658		},
659	}
660	testIndexInfo(config, expectedIndexInfos)
661}
662
663func TestMirrorEndpointLookup(t *testing.T) {
664	containsMirror := func(endpoints []APIEndpoint) bool {
665		for _, pe := range endpoints {
666			if pe.URL.Host == "my.mirror" {
667				return true
668			}
669		}
670		return false
671	}
672	cfg, err := makeServiceConfig([]string{"https://my.mirror"}, nil)
673	if err != nil {
674		t.Fatal(err)
675	}
676	s := DefaultService{config: cfg}
677
678	imageName, err := reference.WithName(IndexName + "/test/image")
679	if err != nil {
680		t.Error(err)
681	}
682	pushAPIEndpoints, err := s.LookupPushEndpoints(reference.Domain(imageName))
683	if err != nil {
684		t.Fatal(err)
685	}
686	if containsMirror(pushAPIEndpoints) {
687		t.Fatal("Push endpoint should not contain mirror")
688	}
689
690	pullAPIEndpoints, err := s.LookupPullEndpoints(reference.Domain(imageName))
691	if err != nil {
692		t.Fatal(err)
693	}
694	if !containsMirror(pullAPIEndpoints) {
695		t.Fatal("Pull endpoint should contain mirror")
696	}
697}
698
699func TestPushRegistryTag(t *testing.T) {
700	r := spawnTestRegistrySession(t)
701	repoRef, err := reference.ParseNormalizedNamed(REPO)
702	if err != nil {
703		t.Fatal(err)
704	}
705	err = r.PushRegistryTag(repoRef, imageID, "stable", makeURL("/v1/"))
706	if err != nil {
707		t.Fatal(err)
708	}
709}
710
711func TestPushImageJSONIndex(t *testing.T) {
712	r := spawnTestRegistrySession(t)
713	imgData := []*ImgData{
714		{
715			ID:       "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
716			Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
717		},
718		{
719			ID:       "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
720			Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
721		},
722	}
723	repoRef, err := reference.ParseNormalizedNamed(REPO)
724	if err != nil {
725		t.Fatal(err)
726	}
727	repoData, err := r.PushImageJSONIndex(repoRef, imgData, false, nil)
728	if err != nil {
729		t.Fatal(err)
730	}
731	if repoData == nil {
732		t.Fatal("Expected RepositoryData object")
733	}
734	repoData, err = r.PushImageJSONIndex(repoRef, imgData, true, []string{r.indexEndpoint.String()})
735	if err != nil {
736		t.Fatal(err)
737	}
738	if repoData == nil {
739		t.Fatal("Expected RepositoryData object")
740	}
741}
742
743func TestSearchRepositories(t *testing.T) {
744	r := spawnTestRegistrySession(t)
745	results, err := r.SearchRepositories("fakequery", 25)
746	if err != nil {
747		t.Fatal(err)
748	}
749	if results == nil {
750		t.Fatal("Expected non-nil SearchResults object")
751	}
752	assertEqual(t, results.NumResults, 1, "Expected 1 search results")
753	assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query")
754	assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
755}
756
757func TestTrustedLocation(t *testing.T) {
758	for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} {
759		req, _ := http.NewRequest("GET", url, nil)
760		assert.False(t, trustedLocation(req))
761	}
762
763	for _, url := range []string{"https://docker.io", "https://test.docker.com:80"} {
764		req, _ := http.NewRequest("GET", url, nil)
765		assert.True(t, trustedLocation(req))
766	}
767}
768
769func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
770	for _, urls := range [][]string{
771		{"http://docker.io", "https://docker.com"},
772		{"https://foo.docker.io:7777", "http://bar.docker.com"},
773		{"https://foo.docker.io", "https://example.com"},
774	} {
775		reqFrom, _ := http.NewRequest("GET", urls[0], nil)
776		reqFrom.Header.Add("Content-Type", "application/json")
777		reqFrom.Header.Add("Authorization", "super_secret")
778		reqTo, _ := http.NewRequest("GET", urls[1], nil)
779
780		addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
781
782		if len(reqTo.Header) != 1 {
783			t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
784		}
785
786		if reqTo.Header.Get("Content-Type") != "application/json" {
787			t.Fatal("'Content-Type' should be 'application/json'")
788		}
789
790		if reqTo.Header.Get("Authorization") != "" {
791			t.Fatal("'Authorization' should be empty")
792		}
793	}
794
795	for _, urls := range [][]string{
796		{"https://docker.io", "https://docker.com"},
797		{"https://foo.docker.io:7777", "https://bar.docker.com"},
798	} {
799		reqFrom, _ := http.NewRequest("GET", urls[0], nil)
800		reqFrom.Header.Add("Content-Type", "application/json")
801		reqFrom.Header.Add("Authorization", "super_secret")
802		reqTo, _ := http.NewRequest("GET", urls[1], nil)
803
804		addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
805
806		if len(reqTo.Header) != 2 {
807			t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
808		}
809
810		if reqTo.Header.Get("Content-Type") != "application/json" {
811			t.Fatal("'Content-Type' should be 'application/json'")
812		}
813
814		if reqTo.Header.Get("Authorization") != "super_secret" {
815			t.Fatal("'Authorization' should be 'super_secret'")
816		}
817	}
818}
819
820func TestAllowNondistributableArtifacts(t *testing.T) {
821	tests := []struct {
822		addr       string
823		registries []string
824		expected   bool
825	}{
826		{IndexName, nil, false},
827		{"example.com", []string{}, false},
828		{"example.com", []string{"example.com"}, true},
829		{"localhost", []string{"localhost:5000"}, false},
830		{"localhost:5000", []string{"localhost:5000"}, true},
831		{"localhost", []string{"example.com"}, false},
832		{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, true},
833		{"localhost", nil, false},
834		{"localhost:5000", nil, false},
835		{"127.0.0.1", nil, false},
836		{"localhost", []string{"example.com"}, false},
837		{"127.0.0.1", []string{"example.com"}, false},
838		{"example.com", nil, false},
839		{"example.com", []string{"example.com"}, true},
840		{"127.0.0.1", []string{"example.com"}, false},
841		{"127.0.0.1:5000", []string{"example.com"}, false},
842		{"example.com:5000", []string{"42.42.0.0/16"}, true},
843		{"example.com", []string{"42.42.0.0/16"}, true},
844		{"example.com:5000", []string{"42.42.42.42/8"}, true},
845		{"127.0.0.1:5000", []string{"127.0.0.0/8"}, true},
846		{"42.42.42.42:5000", []string{"42.1.1.1/8"}, true},
847		{"invalid.domain.com", []string{"42.42.0.0/16"}, false},
848		{"invalid.domain.com", []string{"invalid.domain.com"}, true},
849		{"invalid.domain.com:5000", []string{"invalid.domain.com"}, false},
850		{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, true},
851	}
852	for _, tt := range tests {
853		config, err := newServiceConfig(ServiceOptions{
854			AllowNondistributableArtifacts: tt.registries,
855		})
856		if err != nil {
857			t.Error(err)
858		}
859		if v := allowNondistributableArtifacts(config, tt.addr); v != tt.expected {
860			t.Errorf("allowNondistributableArtifacts failed for %q %v, expected %v got %v", tt.addr, tt.registries, tt.expected, v)
861		}
862	}
863}
864
865func TestIsSecureIndex(t *testing.T) {
866	tests := []struct {
867		addr               string
868		insecureRegistries []string
869		expected           bool
870	}{
871		{IndexName, nil, true},
872		{"example.com", []string{}, true},
873		{"example.com", []string{"example.com"}, false},
874		{"localhost", []string{"localhost:5000"}, false},
875		{"localhost:5000", []string{"localhost:5000"}, false},
876		{"localhost", []string{"example.com"}, false},
877		{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false},
878		{"localhost", nil, false},
879		{"localhost:5000", nil, false},
880		{"127.0.0.1", nil, false},
881		{"localhost", []string{"example.com"}, false},
882		{"127.0.0.1", []string{"example.com"}, false},
883		{"example.com", nil, true},
884		{"example.com", []string{"example.com"}, false},
885		{"127.0.0.1", []string{"example.com"}, false},
886		{"127.0.0.1:5000", []string{"example.com"}, false},
887		{"example.com:5000", []string{"42.42.0.0/16"}, false},
888		{"example.com", []string{"42.42.0.0/16"}, false},
889		{"example.com:5000", []string{"42.42.42.42/8"}, false},
890		{"127.0.0.1:5000", []string{"127.0.0.0/8"}, false},
891		{"42.42.42.42:5000", []string{"42.1.1.1/8"}, false},
892		{"invalid.domain.com", []string{"42.42.0.0/16"}, true},
893		{"invalid.domain.com", []string{"invalid.domain.com"}, false},
894		{"invalid.domain.com:5000", []string{"invalid.domain.com"}, true},
895		{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false},
896	}
897	for _, tt := range tests {
898		config, err := makeServiceConfig(nil, tt.insecureRegistries)
899		if err != nil {
900			t.Error(err)
901		}
902		if sec := isSecureIndex(config, tt.addr); sec != tt.expected {
903			t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
904		}
905	}
906}
907
908type debugTransport struct {
909	http.RoundTripper
910	log func(...interface{})
911}
912
913func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
914	dump, err := httputil.DumpRequestOut(req, false)
915	if err != nil {
916		tr.log("could not dump request")
917	}
918	tr.log(string(dump))
919	resp, err := tr.RoundTripper.RoundTrip(req)
920	if err != nil {
921		return nil, err
922	}
923	dump, err = httputil.DumpResponse(resp, false)
924	if err != nil {
925		tr.log("could not dump response")
926	}
927	tr.log(string(dump))
928	return resp, err
929}
930