Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: distribution/reference
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.5.0
Choose a base ref
...
head repository: distribution/reference
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.6.0
Choose a head ref
  • 8 commits
  • 5 files changed
  • 5 contributors

Commits on Aug 31, 2023

  1. remove deprecated SplitHostname

    It was deprecated since distribution [v2.7.0-rc.0][1]
    
    [1]: distribution/distribution@9a43b8f
    
    Signed-off-by: Sebastiaan van Stijn <[email protected]>
    thaJeztah committed Aug 31, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    thaJeztah Sebastiaan van Stijn
    Copy the full SHA
    4894124 View commit details

Commits on Sep 1, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a3fb784 View commit details

Commits on Sep 2, 2023

  1. refactor splitDockerDomain to include more documentation

    The splitDockerDomain attempts to determine whether the given name
    contains a domain, or should be used as a "remote-name". The logic used
    in doing so is based on (legacy) conventions, which have not always been
    properly documented.
    
    The logic used in this function was also optimized for "brevity", but
    not easy to ready, due to the combination of multiple boolean conditions
    combined on a single line, and some "double negatives".
    
    More documentation may still be needed, but let's start with documenting
    the logic used in this function;
    
    - Use `strings.Cut()` instead of  `strings.IndexRune()`, which allows us to
      use descriptive variable names, and prevents use of the magic `-1` value.
    - Split the conditions into a switch, so that each of them can be documented
      separately. While this makes the  code more verbose (and introduces some
      duplication), it should not impact performance, as only one condition
      would ever be met (performance may even be better, as the old code
      combined multiple conditions with `&&`).
    - Introduce a fast-path for single-element ("familiar") names. These names
      can be canonicalized early, without doing further handling.
    
    While working on the code, I also discovered an existing bug (or omission)
    where the code would not handle bare _domain names_. Ironically, the
    TestParseDockerRef test has a test-case name "hostname only", but which does
    not cover that case. THat test-case was transferred from containerd/cri,
    and does not describe this scenario (possibly was left as a "further exercise");
    containerd/cri@25fdf72
    
    Let keep it as a further exercise, but add a "TODO" to remind us doing so.
    
    Signed-off-by: Sebastiaan van Stijn <[email protected]>
    thaJeztah committed Sep 2, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    thaJeztah Sebastiaan van Stijn
    Copy the full SHA
    89ee7ec View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8507c7f View commit details

Commits on Feb 14, 2024

  1. Exclude domain from name length check

    Signed-off-by: Ozair <[email protected]>
    ozairasim committed Feb 14, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    aaca75e View commit details

Commits on Mar 20, 2024

  1. fix typo in readme

    Signed-off-by: Christoph Mewes <[email protected]>
    xrstf committed Mar 20, 2024
    Copy the full SHA
    094e717 View commit details
  2. Merge pull request #10 from xrstf/patch-1

    milosgajdos authored Mar 20, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    2a66312 View commit details
  3. Merge pull request #9 from ozairasim/exclude-domain-from-name-length-…

    …validation
    
    Exclude domain from name length check
    milosgajdos authored Mar 20, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    ff14faf View commit details
Showing with 149 additions and 82 deletions.
  1. +1 −1 README.md
  2. +45 −14 normalize.go
  3. +48 −16 normalize_test.go
  4. +20 −24 reference.go
  5. +35 −27 reference_test.go
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ Go library to handle references to container images.
[![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield)

This repository contains a library for handling refrences to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details.
This repository contains a library for handling references to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details.

## Contribution

59 changes: 45 additions & 14 deletions normalize.go
Original file line number Diff line number Diff line change
@@ -123,20 +123,51 @@ func ParseDockerRef(ref string) (Named, error) {
// splitDockerDomain splits a repository name to domain and remote-name.
// If no valid domain is found, the default domain is used. Repository name
// needs to be already validated before.
func splitDockerDomain(name string) (domain, remainder string) {
i := strings.IndexRune(name, '/')
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != localhost && strings.ToLower(name[:i]) == name[:i]) {
domain, remainder = defaultDomain, name
} else {
domain, remainder = name[:i], name[i+1:]
}
if domain == legacyDefaultDomain {
domain = defaultDomain
}
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
remainder = officialRepoPrefix + remainder
}
return
func splitDockerDomain(name string) (domain, remoteName string) {
maybeDomain, maybeRemoteName, ok := strings.Cut(name, "/")
if !ok {
// Fast-path for single element ("familiar" names), such as "ubuntu"
// or "ubuntu:latest". Familiar names must be handled separately, to
// prevent them from being handled as "hostname:port".
//
// Canonicalize them as "docker.io/library/name[:tag]"

// FIXME(thaJeztah): account for bare "localhost" or "example.com" names, which SHOULD be considered a domain.
return defaultDomain, officialRepoPrefix + name
}

switch {
case maybeDomain == localhost:
// localhost is a reserved namespace and always considered a domain.
domain, remoteName = maybeDomain, maybeRemoteName
case maybeDomain == legacyDefaultDomain:
// canonicalize the Docker Hub and legacy "Docker Index" domains.
domain, remoteName = defaultDomain, maybeRemoteName
case strings.ContainsAny(maybeDomain, ".:"):
// Likely a domain or IP-address:
//
// - contains a "." (e.g., "example.com" or "127.0.0.1")
// - contains a ":" (e.g., "example:5000", "::1", or "[::1]:5000")
domain, remoteName = maybeDomain, maybeRemoteName
case strings.ToLower(maybeDomain) != maybeDomain:
// Uppercase namespaces are not allowed, so if the first element
// is not lowercase, we assume it to be a domain-name.
domain, remoteName = maybeDomain, maybeRemoteName
default:
// None of the above: it's not a domain, so use the default, and
// use the name input the remote-name.
domain, remoteName = defaultDomain, name
}

if domain == defaultDomain && !strings.ContainsRune(remoteName, '/') {
// Canonicalize "familiar" names, but only on Docker Hub, not
// on other domains:
//
// "docker.io/ubuntu[:tag]" => "docker.io/library/ubuntu[:tag]"
remoteName = officialRepoPrefix + remoteName
}

return domain, remoteName
}

// familiarizeName returns a shortened version of the name familiar
64 changes: 48 additions & 16 deletions normalize_test.go
Original file line number Diff line number Diff line change
@@ -13,6 +13,10 @@ func TestValidateReferenceName(t *testing.T) {
"docker/docker",
"library/debian",
"debian",
"localhost/library/debian",
"localhost/debian",
"LOCALDOMAIN/library/debian",
"LOCALDOMAIN/debian",
"docker.io/docker/docker",
"docker.io/library/debian",
"docker.io/debian",
@@ -147,6 +151,34 @@ func TestParseRepositoryInfo(t *testing.T) {
}

tests := []tcase{
{
RemoteName: "fooo",
FamiliarName: "localhost/fooo",
FullName: "localhost/fooo",
AmbiguousName: "localhost/fooo",
Domain: "localhost",
},
{
RemoteName: "fooo/bar",
FamiliarName: "localhost/fooo/bar",
FullName: "localhost/fooo/bar",
AmbiguousName: "localhost/fooo/bar",
Domain: "localhost",
},
{
RemoteName: "fooo",
FamiliarName: "LOCALDOMAIN/fooo",
FullName: "LOCALDOMAIN/fooo",
AmbiguousName: "LOCALDOMAIN/fooo",
Domain: "LOCALDOMAIN",
},
{
RemoteName: "fooo/bar",
FamiliarName: "LOCALDOMAIN/fooo/bar",
FullName: "LOCALDOMAIN/fooo/bar",
AmbiguousName: "LOCALDOMAIN/fooo/bar",
Domain: "LOCALDOMAIN",
},
{
RemoteName: "fooo/bar",
FamiliarName: "fooo/bar",
@@ -466,62 +498,62 @@ func TestNormalizedSplitHostname(t *testing.T) {
tests := []struct {
input string
domain string
name string
path string
}{
{
input: "test.com/foo",
domain: "test.com",
name: "foo",
path: "foo",
},
{
input: "test_com/foo",
domain: "docker.io",
name: "test_com/foo",
path: "test_com/foo",
},
{
input: "docker/migrator",
domain: "docker.io",
name: "docker/migrator",
path: "docker/migrator",
},
{
input: "test.com:8080/foo",
domain: "test.com:8080",
name: "foo",
path: "foo",
},
{
input: "test-com:8080/foo",
domain: "test-com:8080",
name: "foo",
path: "foo",
},
{
input: "foo",
domain: "docker.io",
name: "library/foo",
path: "library/foo",
},
{
input: "xn--n3h.com/foo",
domain: "xn--n3h.com",
name: "foo",
path: "foo",
},
{
input: "xn--n3h.com:18080/foo",
domain: "xn--n3h.com:18080",
name: "foo",
path: "foo",
},
{
input: "docker.io/foo",
domain: "docker.io",
name: "library/foo",
path: "library/foo",
},
{
input: "docker.io/library/foo",
domain: "docker.io",
name: "library/foo",
path: "library/foo",
},
{
input: "docker.io/library/foo/bar",
domain: "docker.io",
name: "library/foo/bar",
path: "library/foo/bar",
},
}
for _, tc := range tests {
@@ -532,12 +564,12 @@ func TestNormalizedSplitHostname(t *testing.T) {
if err != nil {
t.Errorf("error parsing name: %s", err)
}
domain, name := SplitHostname(named) //nolint:staticcheck // Ignore SA1019: SplitHostname is deprecated.
if domain != tc.domain {

if domain := Domain(named); domain != tc.domain {
t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
}
if name != tc.name {
t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
if path := Path(named); path != tc.path {
t.Errorf("unexpected name: got %q, expected %q", path, tc.path)
}
})
}
44 changes: 20 additions & 24 deletions reference.go
Original file line number Diff line number Diff line change
@@ -35,8 +35,13 @@ import (
)

const (
// RepositoryNameTotalLengthMax is the maximum total number of characters in a repository name.
RepositoryNameTotalLengthMax = 255

// NameTotalLengthMax is the maximum total number of characters in a repository name.
NameTotalLengthMax = 255
//
// Deprecated: use [RepositoryNameTotalLengthMax] instead.
NameTotalLengthMax = RepositoryNameTotalLengthMax
)

var (
@@ -55,8 +60,8 @@ var (
// ErrNameEmpty is returned for empty, invalid repository names.
ErrNameEmpty = errors.New("repository name must have at least one component")

// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
// ErrNameTooLong is returned when a repository name is longer than RepositoryNameTotalLengthMax.
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax)

// ErrNameNotCanonical is returned when a name is not canonical.
ErrNameNotCanonical = errors.New("repository name must be canonical")
@@ -165,6 +170,9 @@ func Path(named Named) (name string) {
return path
}

// splitDomain splits a named reference into a hostname and path string.
// If no valid hostname is found, the hostname is empty and the full value
// is returned as name
func splitDomain(name string) (string, string) {
match := anchoredNameRegexp.FindStringSubmatch(name)
if len(match) != 3 {
@@ -173,19 +181,6 @@ func splitDomain(name string) (string, string) {
return match[1], match[2]
}

// SplitHostname splits a named reference into a
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
//
// Deprecated: Use [Domain] or [Path].
func SplitHostname(named Named) (string, string) {
if r, ok := named.(namedRepository); ok {
return r.Domain(), r.Path()
}
return splitDomain(named.Name())
}

// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
func Parse(s string) (Reference, error) {
@@ -200,10 +195,6 @@ func Parse(s string) (Reference, error) {
return nil, ErrReferenceInvalidFormat
}

if len(matches[1]) > NameTotalLengthMax {
return nil, ErrNameTooLong
}

var repo repository

nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
@@ -215,6 +206,10 @@ func Parse(s string) (Reference, error) {
repo.path = matches[1]
}

if len(repo.path) > RepositoryNameTotalLengthMax {
return nil, ErrNameTooLong
}

ref := reference{
namedRepository: repo,
tag: matches[2],
@@ -253,14 +248,15 @@ func ParseNamed(s string) (Named, error) {
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
if len(name) > NameTotalLengthMax {
return nil, ErrNameTooLong
}

match := anchoredNameRegexp.FindStringSubmatch(name)
if match == nil || len(match) != 3 {
return nil, ErrReferenceInvalidFormat
}

if len(match[2]) > RepositoryNameTotalLengthMax {
return nil, ErrNameTooLong
}

return repository{
domain: match[1],
path: match[2],
Loading