...

Source file src/cuelabs.dev/go/oci/ociregistry/ociref/reference_test.go

Documentation: cuelabs.dev/go/oci/ociregistry/ociref

     1  // Copyright 2023 CUE Labs AG
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ociref
    16  
    17  import (
    18  	_ "crypto/sha256"
    19  	_ "crypto/sha512"
    20  	"fmt"
    21  	"regexp"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/go-quicktest/qt"
    26  )
    27  
    28  var parseReferenceTests = []struct {
    29  	testName string
    30  	// input is the repository name or name component testcase
    31  	input string
    32  	// err is the error expected from Parse, or nil
    33  	wantErr string
    34  	wantRef Reference
    35  }{
    36  	{
    37  		input: "test_com",
    38  		wantRef: Reference{
    39  			Repository: "test_com",
    40  		},
    41  	},
    42  	{
    43  		input: "test.com:tag",
    44  		wantRef: Reference{
    45  			Repository: "test.com",
    46  			Tag:        "tag",
    47  		},
    48  	},
    49  	{
    50  		input: "test.com:5000",
    51  		wantRef: Reference{
    52  			Repository: "test.com",
    53  			Tag:        "5000",
    54  		},
    55  	},
    56  	{
    57  		input: "test.com/repo:tag",
    58  		wantRef: Reference{
    59  			Host:       "test.com",
    60  			Repository: "repo",
    61  			Tag:        "tag",
    62  		},
    63  	},
    64  	{
    65  		input: "test:5000/repo",
    66  		wantRef: Reference{
    67  			Host:       "test:5000",
    68  			Repository: "repo",
    69  		},
    70  	},
    71  	{
    72  		input: "test:5000/repo:tag",
    73  		wantRef: Reference{
    74  			Host:       "test:5000",
    75  			Repository: "repo",
    76  			Tag:        "tag",
    77  		},
    78  	},
    79  	{
    80  		input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    81  		wantRef: Reference{
    82  			Host:       "test:5000",
    83  			Repository: "repo",
    84  			Digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    85  		},
    86  	},
    87  	{
    88  		input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    89  		wantRef: Reference{
    90  			Host:       "test:5000",
    91  			Repository: "repo",
    92  			Tag:        "tag",
    93  			Digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    94  		},
    95  	},
    96  	{
    97  		input: "test:5000/repo",
    98  		wantRef: Reference{
    99  			Host:       "test:5000",
   100  			Repository: "repo",
   101  		},
   102  	},
   103  	{
   104  		testName: "EmptyString",
   105  		input:    "",
   106  		wantErr:  `invalid reference syntax`,
   107  	},
   108  	{
   109  		input:   ":justtag",
   110  		wantErr: `invalid reference syntax`,
   111  	},
   112  	{
   113  		input:   "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   114  		wantErr: `invalid reference syntax`,
   115  	},
   116  	{
   117  		input:   "repo@sha256:ffffffffffffffffffffffffffffffffff",
   118  		wantErr: `invalid digest "sha256:ffffffffffffffffffffffffffffffffff": invalid checksum digest length`,
   119  	},
   120  	{
   121  		input:   "validname@invalidDigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   122  		wantErr: `invalid digest "invalidDigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff": invalid checksum digest format`,
   123  	},
   124  	{
   125  		input:   "Uppercase:tag",
   126  		wantErr: `invalid reference syntax`,
   127  	},
   128  	// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
   129  	// See https://github.com/distribution/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
   130  	// {
   131  	//	input: "Uppercase/lowercase:tag",
   132  	//	err:   ErrNameContainsUppercase,
   133  	// },
   134  	{
   135  		input:   "test:5000/Uppercase/lowercase:tag",
   136  		wantErr: `tag "5000/Uppercase/lowercase:tag" contains invalid invalid character '/'`,
   137  	},
   138  	{
   139  		input: "lowercase:Uppercase",
   140  		wantRef: Reference{
   141  			Repository: "lowercase",
   142  			Tag:        "Uppercase",
   143  		},
   144  	},
   145  	{
   146  		testName: "RepoTooLong",
   147  		input:    strings.Repeat("a/", 128) + "a:tag",
   148  		wantErr:  `repository name too long`,
   149  	},
   150  	{
   151  		testName: "RepoAlmostTooLong",
   152  		input:    strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
   153  		wantRef: Reference{
   154  			// Note: docker/reference parses Host as "a".
   155  			Repository: strings.Repeat("a/", 127) + "a",
   156  			Tag:        "tag-puts-this-over-max",
   157  		},
   158  	},
   159  	{
   160  		input:   "aa/asdf$$^/aa",
   161  		wantErr: `invalid reference syntax`,
   162  	},
   163  	{
   164  		input: "sub-dom1.foo.com/bar/baz/quux",
   165  		wantRef: Reference{
   166  			Host:       "sub-dom1.foo.com",
   167  			Repository: "bar/baz/quux",
   168  		},
   169  	},
   170  	{
   171  		input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
   172  		wantRef: Reference{
   173  			Host:       "sub-dom1.foo.com",
   174  			Repository: "bar/baz/quux",
   175  			Tag:        "some-long-tag",
   176  		},
   177  	},
   178  	{
   179  		input: "b.gcr.io/test.example.com/my-app:test.example.com",
   180  		wantRef: Reference{
   181  			Host:       "b.gcr.io",
   182  			Repository: "test.example.com/my-app",
   183  			Tag:        "test.example.com",
   184  		},
   185  	},
   186  	{
   187  		input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
   188  		wantRef: Reference{
   189  			Host:       "xn--n3h.com",
   190  			Repository: "myimage",
   191  			Tag:        "xn--n3h.com",
   192  		},
   193  	},
   194  	{
   195  		input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
   196  		wantRef: Reference{
   197  			Host:       "xn--7o8h.com",
   198  			Repository: "myimage",
   199  			Tag:        "xn--7o8h.com",
   200  			Digest:     "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   201  		},
   202  	},
   203  	{
   204  		input: "foo_bar.com:8080",
   205  		wantRef: Reference{
   206  			Repository: "foo_bar.com",
   207  			Tag:        "8080",
   208  		},
   209  	},
   210  	{
   211  		input: "foo.com/bar:8080",
   212  		wantRef: Reference{
   213  			Host:       "foo.com",
   214  			Repository: "bar",
   215  			Tag:        "8080",
   216  		},
   217  	},
   218  	{
   219  		input: "foo/foo_bar.com:8080",
   220  		wantRef: Reference{
   221  			Repository: "foo/foo_bar.com",
   222  			Tag:        "8080",
   223  		},
   224  	},
   225  	{
   226  		input: "192.168.1.1",
   227  		wantRef: Reference{
   228  			Repository: "192.168.1.1",
   229  		},
   230  	},
   231  	{
   232  		input: "192.168.1.1:tag",
   233  		wantRef: Reference{
   234  			Repository: "192.168.1.1",
   235  			Tag:        "tag",
   236  		},
   237  	},
   238  	{
   239  		input: "192.168.1.1:5000",
   240  		wantRef: Reference{
   241  			Repository: "192.168.1.1",
   242  			Tag:        "5000",
   243  		},
   244  	},
   245  	{
   246  		input: "192.168.1.1/repo",
   247  		wantRef: Reference{
   248  			Host:       "192.168.1.1",
   249  			Repository: "repo",
   250  		},
   251  	},
   252  	{
   253  		input: "192.168.1.1:5000/repo",
   254  		wantRef: Reference{
   255  			Host:       "192.168.1.1:5000",
   256  			Repository: "repo",
   257  		},
   258  	},
   259  	{
   260  		input: "192.168.1.1:5000/repo:5050",
   261  		wantRef: Reference{
   262  			Host:       "192.168.1.1:5000",
   263  			Repository: "repo",
   264  			Tag:        "5050",
   265  		},
   266  	},
   267  	{
   268  		input:   "[2001:db8::1]",
   269  		wantErr: `invalid reference syntax`,
   270  	},
   271  	{
   272  		input:   "[2001:db8::1]:5000",
   273  		wantErr: `invalid reference syntax`,
   274  	},
   275  	{
   276  		input:   "[2001:db8::1]:tag",
   277  		wantErr: `invalid reference syntax`,
   278  	},
   279  	{
   280  		input: "[2001:db8::1]/repo",
   281  		wantRef: Reference{
   282  			Host:       "[2001:db8::1]",
   283  			Repository: "repo",
   284  		},
   285  	},
   286  	{
   287  		input: "[2001:db8:1:2:3:4:5:6]/repo:tag",
   288  		wantRef: Reference{
   289  			Host:       "[2001:db8:1:2:3:4:5:6]",
   290  			Repository: "repo",
   291  			Tag:        "tag",
   292  		},
   293  	},
   294  	{
   295  		input: "[2001:db8::1]:5000/repo",
   296  		wantRef: Reference{
   297  			Host:       "[2001:db8::1]:5000",
   298  			Repository: "repo",
   299  		},
   300  	},
   301  	{
   302  		input: "[2001:db8::1]:5000/repo:tag",
   303  		wantRef: Reference{
   304  			Host:       "[2001:db8::1]:5000",
   305  			Repository: "repo",
   306  			Tag:        "tag",
   307  		},
   308  	},
   309  	{
   310  		input: "[2001:db8::1]:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   311  		wantRef: Reference{
   312  			Host:       "[2001:db8::1]:5000",
   313  			Repository: "repo",
   314  			Digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   315  		},
   316  	},
   317  	{
   318  		input: "[2001:db8::1]:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   319  		wantRef: Reference{
   320  			Host:       "[2001:db8::1]:5000",
   321  			Repository: "repo",
   322  			Tag:        "tag",
   323  			Digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   324  		},
   325  	},
   326  	{
   327  		input: "[2001:db8::]:5000/repo",
   328  		wantRef: Reference{
   329  			Host:       "[2001:db8::]:5000",
   330  			Repository: "repo",
   331  		},
   332  	},
   333  	{
   334  		input: "[::1]:5000/repo",
   335  		wantRef: Reference{
   336  			Host:       "[::1]:5000",
   337  			Repository: "repo",
   338  		},
   339  	},
   340  	{
   341  		input:   "[fe80::1%eth0]:5000/repo",
   342  		wantErr: `invalid reference syntax`,
   343  	},
   344  	{
   345  		input:   "[fe80::1%@invalidzone]:5000/repo",
   346  		wantErr: `invalid reference syntax`,
   347  	},
   348  }
   349  
   350  func TestParseReference(t *testing.T) {
   351  	for _, test := range parseReferenceTests {
   352  		if test.testName == "" {
   353  			test.testName = test.input
   354  		}
   355  		t.Run(test.testName, func(t *testing.T) {
   356  			ref, err := ParseRelative(test.input)
   357  			t.Logf("ref: %#v", ref)
   358  			if test.wantErr != "" {
   359  				if test.wantErr == "invalid reference syntax" {
   360  					test.wantErr += regexp.QuoteMeta(fmt.Sprintf(" (%q)", test.input))
   361  				}
   362  				qt.Assert(t, qt.ErrorMatches(err, test.wantErr))
   363  				return
   364  			}
   365  			qt.Assert(t, qt.IsNil(err))
   366  			qt.Check(t, qt.Equals(ref, test.wantRef))
   367  			qt.Check(t, qt.Equals(ref.String(), test.input))
   368  			if test.wantRef.Host != "" {
   369  				ref1, err := Parse(test.input)
   370  				qt.Assert(t, qt.IsNil(err))
   371  				qt.Check(t, qt.Equals(ref1, test.wantRef))
   372  			} else {
   373  				_, err := Parse(test.input)
   374  				qt.Assert(t, qt.ErrorMatches(err, `reference does not contain host name`))
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  var isValidHostTests = []struct {
   381  	host string
   382  	want bool
   383  }{{
   384  	host: "foo.com:5000",
   385  	want: true,
   386  }, {
   387  	host: "foo.com",
   388  	want: true,
   389  }, {
   390  	host: "localhost:1234",
   391  	want: true,
   392  }, {
   393  	host: "localhost",
   394  	want: false,
   395  }, {
   396  	host: "foo",
   397  	want: false,
   398  }, {
   399  	host: "foo..com",
   400  	want: false,
   401  }, {
   402  	host: "[::1]",
   403  	want: true,
   404  }, {
   405  	host: "[::1]:3456",
   406  	want: true,
   407  }}
   408  
   409  func TestIsValidHost(t *testing.T) {
   410  	for _, test := range isValidHostTests {
   411  		t.Run(test.host, func(t *testing.T) {
   412  			qt.Assert(t, qt.Equals(IsValidHost(test.host), test.want))
   413  		})
   414  	}
   415  }
   416  
   417  var isValidRepositoryTests = []struct {
   418  	repo string
   419  	want bool
   420  }{{
   421  	repo: "foo",
   422  	want: true,
   423  }, {
   424  	repo: "00123456789abcdefghijklmnopqrstuvwxyz",
   425  	want: true,
   426  }, {
   427  	repo: "a-b---c",
   428  	want: true,
   429  }, {
   430  	repo: "a.b.c9",
   431  	want: true,
   432  }, {
   433  	repo: "a..b",
   434  	want: false,
   435  }, {
   436  	repo: ".a",
   437  	want: false,
   438  }, {
   439  	repo: "a.",
   440  	want: false,
   441  }, {
   442  	repo: "-a",
   443  	want: false,
   444  }, {
   445  	repo: "a-",
   446  	want: false,
   447  }, {
   448  	repo: "A",
   449  	want: false,
   450  }, {
   451  	repo: "_a",
   452  	want: false,
   453  }, {
   454  	repo: "a_",
   455  	want: false,
   456  }, {
   457  	repo: "foo/bar/baz",
   458  	want: true,
   459  }, {
   460  	repo: "café",
   461  	want: false,
   462  }, {
   463  	repo: "foo@bar",
   464  	want: false,
   465  }}
   466  
   467  func TestIsValidRepository(t *testing.T) {
   468  	for _, test := range isValidRepositoryTests {
   469  		t.Run(test.repo, func(t *testing.T) {
   470  			qt.Assert(t, qt.Equals(IsValidRepository(test.repo), test.want))
   471  		})
   472  	}
   473  }
   474  
   475  var isValidTagTests = []struct {
   476  	tag  string
   477  	want bool
   478  }{{
   479  	tag:  "hello",
   480  	want: true,
   481  }, {
   482  	tag:  "v1.2.3-alpha.0",
   483  	want: true,
   484  }, {
   485  	tag:  "foo",
   486  	want: true,
   487  }, {
   488  	tag:  "_",
   489  	want: true,
   490  }, {
   491  	tag:  "_x",
   492  	want: true,
   493  }, {
   494  	tag:  "___",
   495  	want: true,
   496  }, {
   497  	tag:  "ABC_D",
   498  	want: true,
   499  }, {
   500  	tag:  strings.Repeat("a", 128),
   501  	want: true,
   502  }, {
   503  	tag:  strings.Repeat("a", 129),
   504  	want: false,
   505  }, {
   506  	tag:  "foo....___---",
   507  	want: true,
   508  }, {
   509  	tag:  "café",
   510  	want: false,
   511  }, {
   512  	tag:  "foo@bar",
   513  	want: false,
   514  }, {
   515  	tag:  "v4.3+something",
   516  	want: false,
   517  }, {
   518  	tag:  "xxx",
   519  	want: true,
   520  }}
   521  
   522  func TestIsValidTag(t *testing.T) {
   523  	for _, test := range isValidTagTests {
   524  		t.Run(test.tag, func(t *testing.T) {
   525  			qt.Assert(t, qt.Equals(IsValidTag(test.tag), test.want))
   526  		})
   527  	}
   528  }
   529  

View as plain text