1 package reference
2
3 import (
4 "strconv"
5 "testing"
6
7 "github.com/opencontainers/go-digest"
8 )
9
10 func TestValidateReferenceName(t *testing.T) {
11 t.Parallel()
12 validRepoNames := []string{
13 "docker/docker",
14 "library/debian",
15 "debian",
16 "docker.io/docker/docker",
17 "docker.io/library/debian",
18 "docker.io/debian",
19 "index.docker.io/docker/docker",
20 "index.docker.io/library/debian",
21 "index.docker.io/debian",
22 "127.0.0.1:5000/docker/docker",
23 "127.0.0.1:5000/library/debian",
24 "127.0.0.1:5000/debian",
25 "192.168.0.1",
26 "192.168.0.1:80",
27 "192.168.0.1:8/debian",
28 "192.168.0.2:25000/debian",
29 "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
30 "[fc00::1]:5000/docker",
31 "[fc00::1]:5000/docker/docker",
32 "[fc00:1:2:3:4:5:6:7]:5000/library/debian",
33
34
35
36
37 "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
38 "Docker/docker",
39 "DOCKER/docker",
40 }
41 invalidRepoNames := []string{
42 "https://github.com/docker/docker",
43 "docker/Docker",
44 "-docker",
45 "-docker/docker",
46 "-docker.io/docker/docker",
47 "docker///docker",
48 "docker.io/docker/Docker",
49 "docker.io/docker///docker",
50 "[fc00::1]",
51 "[fc00::1]:5000",
52 "fc00::1:5000/debian",
53 "[fe80::1%eth0]:5000/debian",
54 "[2001:db8:3:4::192.0.2.33]:5000/debian",
55 "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
56 }
57
58 for _, name := range invalidRepoNames {
59 _, err := ParseNormalizedNamed(name)
60 if err == nil {
61 t.Fatalf("Expected invalid repo name for %q", name)
62 }
63 }
64
65 for _, name := range validRepoNames {
66 _, err := ParseNormalizedNamed(name)
67 if err != nil {
68 t.Fatalf("Error parsing repo name %s, got: %q", name, err)
69 }
70 }
71 }
72
73 func TestValidateRemoteName(t *testing.T) {
74 t.Parallel()
75 validRepositoryNames := []string{
76
77 "docker/docker",
78
79
80 "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
81
82
83 "docker-rules/docker",
84
85
86 "docker---rules/docker",
87
88
89 "doc/docker",
90
91
92 "d/docker",
93 "jess/t",
94
95
96 "dock__er/docker",
97 }
98 for _, repositoryName := range validRepositoryNames {
99 _, err := ParseNormalizedNamed(repositoryName)
100 if err != nil {
101 t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
102 }
103 }
104
105 invalidRepositoryNames := []string{
106
107 "docker/Docker",
108
109
110 "docker///docker",
111
112
113 "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
114
115
116 "-docker/docker",
117 "docker-/docker",
118 "-docker-/docker",
119
120
121 "____/____",
122
123 "_docker/_docker",
124
125
126 "dock..er/docker",
127 "dock_.er/docker",
128 "dock-.er/docker",
129
130
131 "docker/",
132
133
134 "this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
135 }
136 for _, repositoryName := range invalidRepositoryNames {
137 if _, err := ParseNormalizedNamed(repositoryName); err == nil {
138 t.Errorf("Repository name should be invalid: %v", repositoryName)
139 }
140 }
141 }
142
143 func TestParseRepositoryInfo(t *testing.T) {
144 t.Parallel()
145 type tcase struct {
146 RemoteName, FamiliarName, FullName, AmbiguousName, Domain string
147 }
148
149 tests := []tcase{
150 {
151 RemoteName: "fooo/bar",
152 FamiliarName: "fooo/bar",
153 FullName: "docker.io/fooo/bar",
154 AmbiguousName: "index.docker.io/fooo/bar",
155 Domain: "docker.io",
156 },
157 {
158 RemoteName: "library/ubuntu",
159 FamiliarName: "ubuntu",
160 FullName: "docker.io/library/ubuntu",
161 AmbiguousName: "library/ubuntu",
162 Domain: "docker.io",
163 },
164 {
165 RemoteName: "nonlibrary/ubuntu",
166 FamiliarName: "nonlibrary/ubuntu",
167 FullName: "docker.io/nonlibrary/ubuntu",
168 AmbiguousName: "",
169 Domain: "docker.io",
170 },
171 {
172 RemoteName: "other/library",
173 FamiliarName: "other/library",
174 FullName: "docker.io/other/library",
175 AmbiguousName: "",
176 Domain: "docker.io",
177 },
178 {
179 RemoteName: "private/moonbase",
180 FamiliarName: "127.0.0.1:8000/private/moonbase",
181 FullName: "127.0.0.1:8000/private/moonbase",
182 AmbiguousName: "",
183 Domain: "127.0.0.1:8000",
184 },
185 {
186 RemoteName: "privatebase",
187 FamiliarName: "127.0.0.1:8000/privatebase",
188 FullName: "127.0.0.1:8000/privatebase",
189 AmbiguousName: "",
190 Domain: "127.0.0.1:8000",
191 },
192 {
193 RemoteName: "private/moonbase",
194 FamiliarName: "example.com/private/moonbase",
195 FullName: "example.com/private/moonbase",
196 AmbiguousName: "",
197 Domain: "example.com",
198 },
199 {
200 RemoteName: "privatebase",
201 FamiliarName: "example.com/privatebase",
202 FullName: "example.com/privatebase",
203 AmbiguousName: "",
204 Domain: "example.com",
205 },
206 {
207 RemoteName: "private/moonbase",
208 FamiliarName: "example.com:8000/private/moonbase",
209 FullName: "example.com:8000/private/moonbase",
210 AmbiguousName: "",
211 Domain: "example.com:8000",
212 },
213 {
214 RemoteName: "privatebasee",
215 FamiliarName: "example.com:8000/privatebasee",
216 FullName: "example.com:8000/privatebasee",
217 AmbiguousName: "",
218 Domain: "example.com:8000",
219 },
220 {
221 RemoteName: "library/ubuntu-12.04-base",
222 FamiliarName: "ubuntu-12.04-base",
223 FullName: "docker.io/library/ubuntu-12.04-base",
224 AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
225 Domain: "docker.io",
226 },
227 {
228 RemoteName: "library/foo",
229 FamiliarName: "foo",
230 FullName: "docker.io/library/foo",
231 AmbiguousName: "docker.io/foo",
232 Domain: "docker.io",
233 },
234 {
235 RemoteName: "library/foo/bar",
236 FamiliarName: "library/foo/bar",
237 FullName: "docker.io/library/foo/bar",
238 AmbiguousName: "",
239 Domain: "docker.io",
240 },
241 {
242 RemoteName: "store/foo/bar",
243 FamiliarName: "store/foo/bar",
244 FullName: "docker.io/store/foo/bar",
245 AmbiguousName: "",
246 Domain: "docker.io",
247 },
248 {
249 RemoteName: "bar",
250 FamiliarName: "Foo/bar",
251 FullName: "Foo/bar",
252 AmbiguousName: "",
253 Domain: "Foo",
254 },
255 {
256 RemoteName: "bar",
257 FamiliarName: "FOO/bar",
258 FullName: "FOO/bar",
259 AmbiguousName: "",
260 Domain: "FOO",
261 },
262 }
263
264 for i, tc := range tests {
265 tc := tc
266 refStrings := []string{tc.FamiliarName, tc.FullName}
267 if tc.AmbiguousName != "" {
268 refStrings = append(refStrings, tc.AmbiguousName)
269 }
270
271 for _, r := range refStrings {
272 r := r
273 t.Run(strconv.Itoa(i)+"/"+r, func(t *testing.T) {
274 t.Parallel()
275 named, err := ParseNormalizedNamed(r)
276 if err != nil {
277 t.Fatalf("ref=%s: %v", r, err)
278 }
279 t.Run("FamiliarName", func(t *testing.T) {
280 if expected, actual := tc.FamiliarName, FamiliarName(named); expected != actual {
281 t.Errorf("Invalid familiar name for %q. Expected %q, got %q", named, expected, actual)
282 }
283 })
284 t.Run("FullName", func(t *testing.T) {
285 if expected, actual := tc.FullName, named.String(); expected != actual {
286 t.Errorf("Invalid canonical reference for %q. Expected %q, got %q", named, expected, actual)
287 }
288 })
289 t.Run("Domain", func(t *testing.T) {
290 if expected, actual := tc.Domain, Domain(named); expected != actual {
291 t.Errorf("Invalid domain for %q. Expected %q, got %q", named, expected, actual)
292 }
293 })
294 t.Run("RemoteName", func(t *testing.T) {
295 if expected, actual := tc.RemoteName, Path(named); expected != actual {
296 t.Errorf("Invalid remoteName for %q. Expected %q, got %q", named, expected, actual)
297 }
298 })
299 })
300 }
301 }
302 }
303
304 func TestParseReferenceWithTagAndDigest(t *testing.T) {
305 t.Parallel()
306 shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
307 ref, err := ParseNormalizedNamed(shortRef)
308 if err != nil {
309 t.Fatal(err)
310 }
311 if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
312 t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
313 }
314
315 if _, isTagged := ref.(NamedTagged); !isTagged {
316 t.Fatalf("Reference from %q should support tag", ref)
317 }
318 if _, isCanonical := ref.(Canonical); !isCanonical {
319 t.Fatalf("Reference from %q should support digest", ref)
320 }
321 if expected, actual := shortRef, FamiliarString(ref); actual != expected {
322 t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
323 }
324 }
325
326 func TestInvalidReferenceComponents(t *testing.T) {
327 t.Parallel()
328 if _, err := ParseNormalizedNamed("-foo"); err == nil {
329 t.Fatal("Expected WithName to detect invalid name")
330 }
331 ref, err := ParseNormalizedNamed("busybox")
332 if err != nil {
333 t.Fatal(err)
334 }
335 if _, err := WithTag(ref, "-foo"); err == nil {
336 t.Fatal("Expected WithName to detect invalid tag")
337 }
338 if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
339 t.Fatal("Expected WithDigest to detect invalid digest")
340 }
341 }
342
343 func equalReference(r1, r2 Reference) bool {
344 switch v1 := r1.(type) {
345 case digestReference:
346 if v2, ok := r2.(digestReference); ok {
347 return v1 == v2
348 }
349 case repository:
350 if v2, ok := r2.(repository); ok {
351 return v1 == v2
352 }
353 case taggedReference:
354 if v2, ok := r2.(taggedReference); ok {
355 return v1 == v2
356 }
357 case canonicalReference:
358 if v2, ok := r2.(canonicalReference); ok {
359 return v1 == v2
360 }
361 case reference:
362 if v2, ok := r2.(reference); ok {
363 return v1 == v2
364 }
365 }
366 return false
367 }
368
369 func TestParseAnyReference(t *testing.T) {
370 t.Parallel()
371 tests := []struct {
372 Reference string
373 Equivalent string
374 Expected Reference
375 }{
376 {
377 Reference: "redis",
378 Equivalent: "docker.io/library/redis",
379 },
380 {
381 Reference: "redis:latest",
382 Equivalent: "docker.io/library/redis:latest",
383 },
384 {
385 Reference: "docker.io/library/redis:latest",
386 Equivalent: "docker.io/library/redis:latest",
387 },
388 {
389 Reference: "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
390 Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
391 },
392 {
393 Reference: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
394 Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
395 },
396 {
397 Reference: "dmcgowan/myapp",
398 Equivalent: "docker.io/dmcgowan/myapp",
399 },
400 {
401 Reference: "dmcgowan/myapp:latest",
402 Equivalent: "docker.io/dmcgowan/myapp:latest",
403 },
404 {
405 Reference: "docker.io/mcgowan/myapp:latest",
406 Equivalent: "docker.io/mcgowan/myapp:latest",
407 },
408 {
409 Reference: "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
410 Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
411 },
412 {
413 Reference: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
414 Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
415 },
416 {
417 Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
418 Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
419 Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
420 },
421 {
422 Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
423 Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
424 Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
425 },
426 {
427 Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
428 Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
429 },
430 {
431 Reference: "dbcc1",
432 Equivalent: "docker.io/library/dbcc1",
433 },
434 }
435
436 for _, tc := range tests {
437 tc := tc
438 t.Run(tc.Reference, func(t *testing.T) {
439 t.Parallel()
440 var ref Reference
441 var err error
442 ref, err = ParseAnyReference(tc.Reference)
443 if err != nil {
444 t.Fatalf("Error parsing reference %s: %v", tc.Reference, err)
445 }
446 if ref.String() != tc.Equivalent {
447 t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tc.Equivalent)
448 }
449
450 expected := tc.Expected
451 if expected == nil {
452 expected, err = Parse(tc.Equivalent)
453 if err != nil {
454 t.Fatalf("Error parsing reference %s: %v", tc.Equivalent, err)
455 }
456 }
457 if !equalReference(ref, expected) {
458 t.Errorf("Unexpected reference %#v, expected %#v", ref, expected)
459 }
460 })
461 }
462 }
463
464 func TestNormalizedSplitHostname(t *testing.T) {
465 t.Parallel()
466 tests := []struct {
467 input string
468 domain string
469 name string
470 }{
471 {
472 input: "test.com/foo",
473 domain: "test.com",
474 name: "foo",
475 },
476 {
477 input: "test_com/foo",
478 domain: "docker.io",
479 name: "test_com/foo",
480 },
481 {
482 input: "docker/migrator",
483 domain: "docker.io",
484 name: "docker/migrator",
485 },
486 {
487 input: "test.com:8080/foo",
488 domain: "test.com:8080",
489 name: "foo",
490 },
491 {
492 input: "test-com:8080/foo",
493 domain: "test-com:8080",
494 name: "foo",
495 },
496 {
497 input: "foo",
498 domain: "docker.io",
499 name: "library/foo",
500 },
501 {
502 input: "xn--n3h.com/foo",
503 domain: "xn--n3h.com",
504 name: "foo",
505 },
506 {
507 input: "xn--n3h.com:18080/foo",
508 domain: "xn--n3h.com:18080",
509 name: "foo",
510 },
511 {
512 input: "docker.io/foo",
513 domain: "docker.io",
514 name: "library/foo",
515 },
516 {
517 input: "docker.io/library/foo",
518 domain: "docker.io",
519 name: "library/foo",
520 },
521 {
522 input: "docker.io/library/foo/bar",
523 domain: "docker.io",
524 name: "library/foo/bar",
525 },
526 }
527 for _, tc := range tests {
528 tc := tc
529 t.Run(tc.input, func(t *testing.T) {
530 t.Parallel()
531 named, err := ParseNormalizedNamed(tc.input)
532 if err != nil {
533 t.Errorf("error parsing name: %s", err)
534 }
535 domain, name := SplitHostname(named)
536 if domain != tc.domain {
537 t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
538 }
539 if name != tc.name {
540 t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
541 }
542 })
543 }
544 }
545
546 func TestMatchError(t *testing.T) {
547 t.Parallel()
548 named, err := ParseAnyReference("foo")
549 if err != nil {
550 t.Fatal(err)
551 }
552 _, err = FamiliarMatch("[-x]", named)
553 if err == nil {
554 t.Fatalf("expected an error, got nothing")
555 }
556 }
557
558 func TestMatch(t *testing.T) {
559 t.Parallel()
560 tests := []struct {
561 reference string
562 pattern string
563 expected bool
564 }{
565 {
566 reference: "foo",
567 pattern: "foo/**/ba[rz]",
568 expected: false,
569 },
570 {
571 reference: "foo/any/bat",
572 pattern: "foo/**/ba[rz]",
573 expected: false,
574 },
575 {
576 reference: "foo/a/bar",
577 pattern: "foo/**/ba[rz]",
578 expected: true,
579 },
580 {
581 reference: "foo/b/baz",
582 pattern: "foo/**/ba[rz]",
583 expected: true,
584 },
585 {
586 reference: "foo/c/baz:tag",
587 pattern: "foo/**/ba[rz]",
588 expected: true,
589 },
590 {
591 reference: "foo/c/baz:tag",
592 pattern: "foo/*/baz:tag",
593 expected: true,
594 },
595 {
596 reference: "foo/c/baz:tag",
597 pattern: "foo/c/baz:tag",
598 expected: true,
599 },
600 {
601 reference: "example.com/foo/c/baz:tag",
602 pattern: "*/foo/c/baz",
603 expected: true,
604 },
605 {
606 reference: "example.com/foo/c/baz:tag",
607 pattern: "example.com/foo/c/baz",
608 expected: true,
609 },
610 }
611 for _, tc := range tests {
612 tc := tc
613 t.Run(tc.reference, func(t *testing.T) {
614 t.Parallel()
615 named, err := ParseAnyReference(tc.reference)
616 if err != nil {
617 t.Fatal(err)
618 }
619 actual, err := FamiliarMatch(tc.pattern, named)
620 if err != nil {
621 t.Fatal(err)
622 }
623 if actual != tc.expected {
624 t.Fatalf("expected %s match %s to be %v, was %v", tc.reference, tc.pattern, tc.expected, actual)
625 }
626 })
627 }
628 }
629
630 func TestParseDockerRef(t *testing.T) {
631 t.Parallel()
632 tests := []struct {
633 name string
634 input string
635 expected string
636 }{
637 {
638 name: "nothing",
639 input: "busybox",
640 expected: "docker.io/library/busybox:latest",
641 },
642 {
643 name: "tag only",
644 input: "busybox:latest",
645 expected: "docker.io/library/busybox:latest",
646 },
647 {
648 name: "digest only",
649 input: "busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
650 expected: "docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
651 },
652 {
653 name: "path only",
654 input: "library/busybox",
655 expected: "docker.io/library/busybox:latest",
656 },
657 {
658 name: "hostname only",
659 input: "docker.io/busybox",
660 expected: "docker.io/library/busybox:latest",
661 },
662 {
663 name: "no tag",
664 input: "docker.io/library/busybox",
665 expected: "docker.io/library/busybox:latest",
666 },
667 {
668 name: "no path",
669 input: "docker.io/busybox:latest",
670 expected: "docker.io/library/busybox:latest",
671 },
672 {
673 name: "no hostname",
674 input: "library/busybox:latest",
675 expected: "docker.io/library/busybox:latest",
676 },
677 {
678 name: "full reference with tag",
679 input: "docker.io/library/busybox:latest",
680 expected: "docker.io/library/busybox:latest",
681 },
682 {
683 name: "gcr reference without tag",
684 input: "gcr.io/library/busybox",
685 expected: "gcr.io/library/busybox:latest",
686 },
687 {
688 name: "both tag and digest",
689 input: "gcr.io/library/busybox:latest@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
690 expected: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
691 },
692 }
693 for _, tc := range tests {
694 tc := tc
695 t.Run(tc.name, func(t *testing.T) {
696 t.Parallel()
697 normalized, err := ParseDockerRef(tc.input)
698 if err != nil {
699 t.Fatal(err)
700 }
701 output := normalized.String()
702 if output != tc.expected {
703 t.Fatalf("expected %q to be parsed as %v, got %v", tc.input, tc.expected, output)
704 }
705 _, err = Parse(output)
706 if err != nil {
707 t.Fatalf("%q should be a valid reference, but got an error: %v", output, err)
708 }
709 })
710 }
711 }
712
View as plain text