1 package reference
2
3 import (
4 _ "crypto/sha256"
5 _ "crypto/sha512"
6 "encoding/json"
7 "strings"
8 "testing"
9
10 "github.com/opencontainers/go-digest"
11 )
12
13 func TestReferenceParse(t *testing.T) {
14 t.Parallel()
15
16
17 tests := []struct {
18
19 input string
20
21 err error
22
23 repository string
24
25 domain string
26
27 tag string
28
29 digest string
30 }{
31 {
32 input: "test_com",
33 repository: "test_com",
34 },
35 {
36 input: "test.com:tag",
37 repository: "test.com",
38 tag: "tag",
39 },
40 {
41 input: "test.com:5000",
42 repository: "test.com",
43 tag: "5000",
44 },
45 {
46 input: "test.com/repo:tag",
47 domain: "test.com",
48 repository: "test.com/repo",
49 tag: "tag",
50 },
51 {
52 input: "test:5000/repo",
53 domain: "test:5000",
54 repository: "test:5000/repo",
55 },
56 {
57 input: "test:5000/repo:tag",
58 domain: "test:5000",
59 repository: "test:5000/repo",
60 tag: "tag",
61 },
62 {
63 input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
64 domain: "test:5000",
65 repository: "test:5000/repo",
66 digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
67 },
68 {
69 input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
70 domain: "test:5000",
71 repository: "test:5000/repo",
72 tag: "tag",
73 digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
74 },
75 {
76 input: "test:5000/repo",
77 domain: "test:5000",
78 repository: "test:5000/repo",
79 },
80 {
81 input: "",
82 err: ErrNameEmpty,
83 },
84 {
85 input: ":justtag",
86 err: ErrReferenceInvalidFormat,
87 },
88 {
89 input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
90 err: ErrReferenceInvalidFormat,
91 },
92 {
93 input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
94 err: digest.ErrDigestInvalidLength,
95 },
96 {
97 input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
98 err: digest.ErrDigestUnsupported,
99 },
100 {
101 input: "Uppercase:tag",
102 err: ErrNameContainsUppercase,
103 },
104
105
106
107
108
109
110 {
111 input: "test:5000/Uppercase/lowercase:tag",
112 err: ErrNameContainsUppercase,
113 },
114 {
115 input: "lowercase:Uppercase",
116 repository: "lowercase",
117 tag: "Uppercase",
118 },
119 {
120 input: strings.Repeat("a/", 128) + "a:tag",
121 err: ErrNameTooLong,
122 },
123 {
124 input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
125 domain: "a",
126 repository: strings.Repeat("a/", 127) + "a",
127 tag: "tag-puts-this-over-max",
128 },
129 {
130 input: "aa/asdf$$^/aa",
131 err: ErrReferenceInvalidFormat,
132 },
133 {
134 input: "sub-dom1.foo.com/bar/baz/quux",
135 domain: "sub-dom1.foo.com",
136 repository: "sub-dom1.foo.com/bar/baz/quux",
137 },
138 {
139 input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
140 domain: "sub-dom1.foo.com",
141 repository: "sub-dom1.foo.com/bar/baz/quux",
142 tag: "some-long-tag",
143 },
144 {
145 input: "b.gcr.io/test.example.com/my-app:test.example.com",
146 domain: "b.gcr.io",
147 repository: "b.gcr.io/test.example.com/my-app",
148 tag: "test.example.com",
149 },
150 {
151 input: "xn--n3h.com/myimage:xn--n3h.com",
152 domain: "xn--n3h.com",
153 repository: "xn--n3h.com/myimage",
154 tag: "xn--n3h.com",
155 },
156 {
157 input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
158 domain: "xn--7o8h.com",
159 repository: "xn--7o8h.com/myimage",
160 tag: "xn--7o8h.com",
161 digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
162 },
163 {
164 input: "foo_bar.com:8080",
165 repository: "foo_bar.com",
166 tag: "8080",
167 },
168 {
169 input: "foo/foo_bar.com:8080",
170 domain: "foo",
171 repository: "foo/foo_bar.com",
172 tag: "8080",
173 },
174 {
175 input: "192.168.1.1",
176 repository: "192.168.1.1",
177 },
178 {
179 input: "192.168.1.1:tag",
180 repository: "192.168.1.1",
181 tag: "tag",
182 },
183 {
184 input: "192.168.1.1:5000",
185 repository: "192.168.1.1",
186 tag: "5000",
187 },
188 {
189 input: "192.168.1.1/repo",
190 domain: "192.168.1.1",
191 repository: "192.168.1.1/repo",
192 },
193 {
194 input: "192.168.1.1:5000/repo",
195 domain: "192.168.1.1:5000",
196 repository: "192.168.1.1:5000/repo",
197 },
198 {
199 input: "192.168.1.1:5000/repo:5050",
200 domain: "192.168.1.1:5000",
201 repository: "192.168.1.1:5000/repo",
202 tag: "5050",
203 },
204 {
205 input: "[2001:db8::1]",
206 err: ErrReferenceInvalidFormat,
207 },
208 {
209 input: "[2001:db8::1]:5000",
210 err: ErrReferenceInvalidFormat,
211 },
212 {
213 input: "[2001:db8::1]:tag",
214 err: ErrReferenceInvalidFormat,
215 },
216 {
217 input: "[2001:db8::1]/repo",
218 domain: "[2001:db8::1]",
219 repository: "[2001:db8::1]/repo",
220 },
221 {
222 input: "[2001:db8:1:2:3:4:5:6]/repo:tag",
223 domain: "[2001:db8:1:2:3:4:5:6]",
224 repository: "[2001:db8:1:2:3:4:5:6]/repo",
225 tag: "tag",
226 },
227 {
228 input: "[2001:db8::1]:5000/repo",
229 domain: "[2001:db8::1]:5000",
230 repository: "[2001:db8::1]:5000/repo",
231 },
232 {
233 input: "[2001:db8::1]:5000/repo:tag",
234 domain: "[2001:db8::1]:5000",
235 repository: "[2001:db8::1]:5000/repo",
236 tag: "tag",
237 },
238 {
239 input: "[2001:db8::1]:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
240 domain: "[2001:db8::1]:5000",
241 repository: "[2001:db8::1]:5000/repo",
242 digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
243 },
244 {
245 input: "[2001:db8::1]:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
246 domain: "[2001:db8::1]:5000",
247 repository: "[2001:db8::1]:5000/repo",
248 tag: "tag",
249 digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
250 },
251 {
252 input: "[2001:db8::]:5000/repo",
253 domain: "[2001:db8::]:5000",
254 repository: "[2001:db8::]:5000/repo",
255 },
256 {
257 input: "[::1]:5000/repo",
258 domain: "[::1]:5000",
259 repository: "[::1]:5000/repo",
260 },
261 {
262 input: "[fe80::1%eth0]:5000/repo",
263 err: ErrReferenceInvalidFormat,
264 },
265 {
266 input: "[fe80::1%@invalidzone]:5000/repo",
267 err: ErrReferenceInvalidFormat,
268 },
269 }
270 for _, tc := range tests {
271 tc := tc
272 t.Run(tc.input, func(t *testing.T) {
273 t.Parallel()
274 repo, err := Parse(tc.input)
275 if tc.err != nil {
276 if err == nil {
277 t.Errorf("missing expected error: %v", tc.err)
278 } else if tc.err != err {
279 t.Errorf("mismatched error: got %v, expected %v", err, tc.err)
280 }
281 return
282 } else if err != nil {
283 t.Errorf("unexpected parse error: %v", err)
284 return
285 }
286 if repo.String() != tc.input {
287 t.Errorf("mismatched repo: got %q, expected %q", repo.String(), tc.input)
288 }
289
290 if named, ok := repo.(Named); ok {
291 if named.Name() != tc.repository {
292 t.Errorf("unexpected repository: got %q, expected %q", named.Name(), tc.repository)
293 }
294 domain, _ := SplitHostname(named)
295 if domain != tc.domain {
296 t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
297 }
298 } else if tc.repository != "" || tc.domain != "" {
299 t.Errorf("expected named type, got %T", repo)
300 }
301
302 tagged, ok := repo.(Tagged)
303 if tc.tag != "" {
304 if ok {
305 if tagged.Tag() != tc.tag {
306 t.Errorf("unexpected tag: got %q, expected %q", tagged.Tag(), tc.tag)
307 }
308 } else {
309 t.Errorf("expected tagged type, got %T", repo)
310 }
311 } else if ok {
312 t.Errorf("unexpected tagged type")
313 }
314
315 digested, ok := repo.(Digested)
316 if tc.digest != "" {
317 if ok {
318 if digested.Digest().String() != tc.digest {
319 t.Errorf("unexpected digest: got %q, expected %q", digested.Digest().String(), tc.digest)
320 }
321 } else {
322 t.Errorf("expected digested type, got %T", repo)
323 }
324 } else if ok {
325 t.Errorf("unexpected digested type")
326 }
327 })
328 }
329 }
330
331
332
333 func TestWithNameFailure(t *testing.T) {
334 t.Parallel()
335 tests := []struct {
336 input string
337 err error
338 }{
339 {
340 input: "",
341 err: ErrNameEmpty,
342 },
343 {
344 input: ":justtag",
345 err: ErrReferenceInvalidFormat,
346 },
347 {
348 input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
349 err: ErrReferenceInvalidFormat,
350 },
351 {
352 input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
353 err: ErrReferenceInvalidFormat,
354 },
355 {
356 input: strings.Repeat("a/", 128) + "a:tag",
357 err: ErrNameTooLong,
358 },
359 {
360 input: "aa/asdf$$^/aa",
361 err: ErrReferenceInvalidFormat,
362 },
363 }
364 for _, tc := range tests {
365 tc := tc
366 t.Run(tc.input, func(t *testing.T) {
367 t.Parallel()
368 _, err := WithName(tc.input)
369 if err == nil {
370 t.Errorf("no error parsing name. expected: %s", tc.err)
371 }
372 })
373 }
374 }
375
376 func TestSplitHostname(t *testing.T) {
377 t.Parallel()
378 tests := []struct {
379 input string
380 domain string
381 name string
382 }{
383 {
384 input: "test.com/foo",
385 domain: "test.com",
386 name: "foo",
387 },
388 {
389 input: "test_com/foo",
390 domain: "",
391 name: "test_com/foo",
392 },
393 {
394 input: "test:8080/foo",
395 domain: "test:8080",
396 name: "foo",
397 },
398 {
399 input: "test.com:8080/foo",
400 domain: "test.com:8080",
401 name: "foo",
402 },
403 {
404 input: "test-com:8080/foo",
405 domain: "test-com:8080",
406 name: "foo",
407 },
408 {
409 input: "xn--n3h.com:18080/foo",
410 domain: "xn--n3h.com:18080",
411 name: "foo",
412 },
413 }
414 for _, tc := range tests {
415 tc := tc
416 t.Run(tc.input, func(t *testing.T) {
417 t.Parallel()
418 named, err := WithName(tc.input)
419 if err != nil {
420 t.Errorf("error parsing name: %s", err)
421 }
422 domain, name := SplitHostname(named)
423 if domain != tc.domain {
424 t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
425 }
426 if name != tc.name {
427 t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
428 }
429 })
430 }
431 }
432
433 type serializationType struct {
434 Description string
435 Field Field
436 }
437
438 func TestSerialization(t *testing.T) {
439 t.Parallel()
440 tests := []struct {
441 description string
442 input string
443 name string
444 tag string
445 digest string
446 err error
447 }{
448 {
449 description: "empty value",
450 err: ErrNameEmpty,
451 },
452 {
453 description: "just a name",
454 input: "example.com:8000/named",
455 name: "example.com:8000/named",
456 },
457 {
458 description: "name with a tag",
459 input: "example.com:8000/named:tagged",
460 name: "example.com:8000/named",
461 tag: "tagged",
462 },
463 {
464 description: "name with digest",
465 input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
466 name: "other.com/named",
467 digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112",
468 },
469 }
470 for _, tc := range tests {
471 tc := tc
472 t.Run(tc.description, func(t *testing.T) {
473 t.Parallel()
474 m := map[string]string{
475 "Description": tc.description,
476 "Field": tc.input,
477 }
478 b, err := json.Marshal(m)
479 if err != nil {
480 t.Errorf("error marshalling: %v", err)
481 }
482 st := serializationType{}
483
484 if err := json.Unmarshal(b, &st); err != nil {
485 if tc.err == nil {
486 t.Errorf("error unmarshalling: %v", err)
487 }
488 if err != tc.err {
489 t.Errorf("wrong error, expected %v, got %v", tc.err, err)
490 }
491
492 return
493 } else if tc.err != nil {
494 t.Errorf("expected error unmarshalling: %v", tc.err)
495 }
496
497 if st.Description != tc.description {
498 t.Errorf("wrong description, expected %q, got %q", tc.description, st.Description)
499 }
500
501 ref := st.Field.Reference()
502
503 if named, ok := ref.(Named); ok {
504 if named.Name() != tc.name {
505 t.Errorf("unexpected repository: got %q, expected %q", named.Name(), tc.name)
506 }
507 } else if tc.name != "" {
508 t.Errorf("expected named type, got %T", ref)
509 }
510
511 tagged, ok := ref.(Tagged)
512 if tc.tag != "" {
513 if ok {
514 if tagged.Tag() != tc.tag {
515 t.Errorf("unexpected tag: got %q, expected %q", tagged.Tag(), tc.tag)
516 }
517 } else {
518 t.Errorf("expected tagged type, got %T", ref)
519 }
520 } else if ok {
521 t.Errorf("unexpected tagged type")
522 }
523
524 digested, ok := ref.(Digested)
525 if tc.digest != "" {
526 if ok {
527 if digested.Digest().String() != tc.digest {
528 t.Errorf("unexpected digest: got %q, expected %q", digested.Digest().String(), tc.digest)
529 }
530 } else {
531 t.Errorf("expected digested type, got %T", ref)
532 }
533 } else if ok {
534 t.Errorf("unexpected digested type")
535 }
536
537 st = serializationType{
538 Description: tc.description,
539 Field: AsField(ref),
540 }
541
542 b2, err := json.Marshal(st)
543 if err != nil {
544 t.Errorf("error marshing serialization type: %v", err)
545 }
546
547 if string(b) != string(b2) {
548 t.Errorf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
549 }
550
551
552
553 var fieldInterface interface{} = st.Field
554 if _, ok := fieldInterface.(Reference); ok {
555 t.Errorf("field should not implement Reference interface")
556 }
557 })
558 }
559 }
560
561 func TestWithTag(t *testing.T) {
562 t.Parallel()
563 tests := []struct {
564 name string
565 digest digest.Digest
566 tag string
567 combined string
568 }{
569 {
570 name: "test.com/foo",
571 tag: "tag",
572 combined: "test.com/foo:tag",
573 },
574 {
575 name: "foo",
576 tag: "tag2",
577 combined: "foo:tag2",
578 },
579 {
580 name: "test.com:8000/foo",
581 tag: "tag4",
582 combined: "test.com:8000/foo:tag4",
583 },
584 {
585 name: "test.com:8000/foo",
586 tag: "TAG5",
587 combined: "test.com:8000/foo:TAG5",
588 },
589 {
590 name: "test.com:8000/foo",
591 digest: "sha256:1234567890098765432112345667890098765",
592 tag: "TAG5",
593 combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
594 },
595 }
596 for _, tc := range tests {
597 tc := tc
598 t.Run(tc.combined, func(t *testing.T) {
599 t.Parallel()
600 named, err := WithName(tc.name)
601 if err != nil {
602 t.Errorf("error parsing name: %s", err)
603 }
604 if tc.digest != "" {
605 canonical, err := WithDigest(named, tc.digest)
606 if err != nil {
607 t.Errorf("error adding digest")
608 }
609 named = canonical
610 }
611
612 tagged, err := WithTag(named, tc.tag)
613 if err != nil {
614 t.Errorf("WithTag failed: %s", err)
615 }
616 if tagged.String() != tc.combined {
617 t.Errorf("unexpected: got %q, expected %q", tagged.String(), tc.combined)
618 }
619 })
620 }
621 }
622
623 func TestWithDigest(t *testing.T) {
624 t.Parallel()
625 tests := []struct {
626 name string
627 digest digest.Digest
628 tag string
629 combined string
630 }{
631 {
632 name: "test.com/foo",
633 digest: "sha256:1234567890098765432112345667890098765",
634 combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
635 },
636 {
637 name: "foo",
638 digest: "sha256:1234567890098765432112345667890098765",
639 combined: "foo@sha256:1234567890098765432112345667890098765",
640 },
641 {
642 name: "test.com:8000/foo",
643 digest: "sha256:1234567890098765432112345667890098765",
644 combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
645 },
646 {
647 name: "test.com:8000/foo",
648 digest: "sha256:1234567890098765432112345667890098765",
649 tag: "latest",
650 combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
651 },
652 }
653 for _, tc := range tests {
654 tc := tc
655 t.Run(tc.combined, func(t *testing.T) {
656 t.Parallel()
657 named, err := WithName(tc.name)
658 if err != nil {
659 t.Errorf("error parsing name: %s", err)
660 }
661 if tc.tag != "" {
662 tagged, err := WithTag(named, tc.tag)
663 if err != nil {
664 t.Errorf("error adding tag")
665 }
666 named = tagged
667 }
668 digested, err := WithDigest(named, tc.digest)
669 if err != nil {
670 t.Errorf("WithDigest failed: %s", err)
671 }
672 if digested.String() != tc.combined {
673 t.Errorf("unexpected: got %q, expected %q", digested.String(), tc.combined)
674 }
675 })
676 }
677 }
678
679 func TestParseNamed(t *testing.T) {
680 t.Parallel()
681 tests := []struct {
682 input string
683 domain string
684 name string
685 err error
686 }{
687 {
688 input: "test.com/foo",
689 domain: "test.com",
690 name: "foo",
691 },
692 {
693 input: "test:8080/foo",
694 domain: "test:8080",
695 name: "foo",
696 },
697 {
698 input: "test_com/foo",
699 err: ErrNameNotCanonical,
700 },
701 {
702 input: "test.com",
703 err: ErrNameNotCanonical,
704 },
705 {
706 input: "foo",
707 err: ErrNameNotCanonical,
708 },
709 {
710 input: "library/foo",
711 err: ErrNameNotCanonical,
712 },
713 {
714 input: "docker.io/library/foo",
715 domain: "docker.io",
716 name: "library/foo",
717 },
718
719 {
720 input: "docker.io/foo",
721 err: ErrNameNotCanonical,
722 },
723 }
724 for _, tc := range tests {
725 tc := tc
726 t.Run(tc.input, func(t *testing.T) {
727 t.Parallel()
728 named, err := ParseNamed(tc.input)
729 if err != nil && tc.err == nil {
730 t.Errorf("error parsing name: %s", err)
731 return
732 } else if err == nil && tc.err != nil {
733 t.Errorf("parsing succeeded: expected error %v", tc.err)
734 return
735 } else if err != tc.err {
736 t.Errorf("unexpected error %v, expected %v", err, tc.err)
737 return
738 } else if err != nil {
739 return
740 }
741
742 domain, name := SplitHostname(named)
743 if domain != tc.domain {
744 t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
745 }
746 if name != tc.name {
747 t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
748 }
749 })
750 }
751 }
752
View as plain text