...

Source file src/github.com/distribution/reference/normalize_test.go

Documentation: github.com/distribution/reference

     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  		// This test case was moved from invalid to valid since it is valid input
    35  		// when specified with a hostname, it removes the ambiguity from about
    36  		// whether the value is an identifier or repository name
    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  		// Sanity check.
    77  		"docker/docker",
    78  
    79  		// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
    80  		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
    81  
    82  		// Allow embedded hyphens.
    83  		"docker-rules/docker",
    84  
    85  		// Allow multiple hyphens as well.
    86  		"docker---rules/docker",
    87  
    88  		// Username doc and image name docker being tested.
    89  		"doc/docker",
    90  
    91  		// single character names are now allowed.
    92  		"d/docker",
    93  		"jess/t",
    94  
    95  		// Consecutive underscores.
    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  		// Disallow capital letters.
   107  		"docker/Docker",
   108  
   109  		// Only allow one slash.
   110  		"docker///docker",
   111  
   112  		// Disallow 64-character hexadecimal.
   113  		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
   114  
   115  		// Disallow leading and trailing hyphens in namespace.
   116  		"-docker/docker",
   117  		"docker-/docker",
   118  		"-docker-/docker",
   119  
   120  		// Don't allow underscores everywhere (as opposed to hyphens).
   121  		"____/____",
   122  
   123  		"_docker/_docker",
   124  
   125  		// Disallow consecutive periods.
   126  		"dock..er/docker",
   127  		"dock_.er/docker",
   128  		"dock-.er/docker",
   129  
   130  		// No repository.
   131  		"docker/",
   132  
   133  		// namespace too long
   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) //nolint:staticcheck // Ignore SA1019: SplitHostname is deprecated.
   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