Merge pull request #4063 from thaJeztah/2.8_backport_switch_reference
[release/2.8] deprecate reference package, migrate to github.com/distribution/reference
This commit is contained in:
commit
11eb4194f6
57 changed files with 959 additions and 2160 deletions
2
blobs.go
2
blobs.go
|
@ -8,7 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,9 +3,9 @@ package schema1
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,9 +3,9 @@ package notifications
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage"
|
"github.com/docker/distribution/registry/storage"
|
||||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
|
34
reference/helpers_deprecated.go
Normal file
34
reference/helpers_deprecated.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import "github.com/distribution/reference"
|
||||||
|
|
||||||
|
// IsNameOnly returns true if reference only contains a repo name.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.IsNameOnly].
|
||||||
|
func IsNameOnly(ref reference.Named) bool {
|
||||||
|
return reference.IsNameOnly(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FamiliarName returns the familiar name string
|
||||||
|
// for the given named, familiarizing if needed.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.FamiliarName].
|
||||||
|
func FamiliarName(ref reference.Named) string {
|
||||||
|
return reference.FamiliarName(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FamiliarString returns the familiar string representation
|
||||||
|
// for the given reference, familiarizing if needed.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.FamiliarString].
|
||||||
|
func FamiliarString(ref reference.Reference) string {
|
||||||
|
return reference.FamiliarString(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FamiliarMatch reports whether ref matches the specified pattern.
|
||||||
|
// See [path.Match] for supported patterns.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.FamiliarMatch].
|
||||||
|
func FamiliarMatch(pattern string, ref reference.Reference) (bool, error) {
|
||||||
|
return reference.FamiliarMatch(pattern, ref)
|
||||||
|
}
|
92
reference/normalize_deprecated.go
Normal file
92
reference/normalize_deprecated.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/distribution/digestset"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseNormalizedNamed parses a string into a named reference
|
||||||
|
// transforming a familiar name from Docker UI to a fully
|
||||||
|
// qualified reference. If the value may be an identifier
|
||||||
|
// use ParseAnyReference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ParseNormalizedNamed].
|
||||||
|
func ParseNormalizedNamed(s string) (reference.Named, error) {
|
||||||
|
return reference.ParseNormalizedNamed(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDockerRef normalizes the image reference following the docker convention,
|
||||||
|
// which allows for references to contain both a tag and a digest.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ParseDockerRef].
|
||||||
|
func ParseDockerRef(ref string) (reference.Named, error) {
|
||||||
|
return reference.ParseDockerRef(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagNameOnly adds the default tag "latest" to a reference if it only has
|
||||||
|
// a repo name.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.TagNameOnly].
|
||||||
|
func TagNameOnly(ref reference.Named) reference.Named {
|
||||||
|
return reference.TagNameOnly(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnyReference parses a reference string as a possible identifier,
|
||||||
|
// full digest, or familiar name.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ParseAnyReference].
|
||||||
|
func ParseAnyReference(ref string) (reference.Reference, error) {
|
||||||
|
return reference.ParseAnyReference(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions and types below have been removed in distribution v3 and
|
||||||
|
// have not been ported to github.com/distribution/reference. See
|
||||||
|
// https://github.com/distribution/distribution/pull/3774
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ShortIdentifierRegexp is the format used to represent a prefix
|
||||||
|
// of an identifier. A prefix may be used to match a sha256 identifier
|
||||||
|
// within a list of trusted identifiers.
|
||||||
|
//
|
||||||
|
// Deprecated: support for short-identifiers is deprecated, and will be removed in v3.
|
||||||
|
ShortIdentifierRegexp = regexp.MustCompile(shortIdentifier)
|
||||||
|
|
||||||
|
shortIdentifier = `([a-f0-9]{6,64})`
|
||||||
|
|
||||||
|
// anchoredShortIdentifierRegexp is used to check if a value
|
||||||
|
// is a possible identifier prefix, anchored at start and end
|
||||||
|
// of string.
|
||||||
|
anchoredShortIdentifierRegexp = regexp.MustCompile(`^` + shortIdentifier + `$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type digestReference digest.Digest
|
||||||
|
|
||||||
|
func (d digestReference) String() string {
|
||||||
|
return digest.Digest(d).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d digestReference) Digest() digest.Digest {
|
||||||
|
return digest.Digest(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnyReferenceWithSet parses a reference string as a possible short
|
||||||
|
// identifier to be matched in a digest set, a full digest, or familiar name.
|
||||||
|
//
|
||||||
|
// Deprecated: support for short-identifiers is deprecated, and will be removed in v3.
|
||||||
|
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
|
||||||
|
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
|
||||||
|
dgst, err := ds.Lookup(ref)
|
||||||
|
if err == nil {
|
||||||
|
return digestReference(dgst), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dgst, err := digest.Parse(ref); err == nil {
|
||||||
|
return digestReference(dgst), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reference.ParseNormalizedNamed(ref)
|
||||||
|
}
|
|
@ -1,705 +0,0 @@
|
||||||
package reference
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/distribution/digestset"
|
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestValidateReferenceName(t *testing.T) {
|
|
||||||
validRepoNames := []string{
|
|
||||||
"docker/docker",
|
|
||||||
"library/debian",
|
|
||||||
"debian",
|
|
||||||
"docker.io/docker/docker",
|
|
||||||
"docker.io/library/debian",
|
|
||||||
"docker.io/debian",
|
|
||||||
"index.docker.io/docker/docker",
|
|
||||||
"index.docker.io/library/debian",
|
|
||||||
"index.docker.io/debian",
|
|
||||||
"127.0.0.1:5000/docker/docker",
|
|
||||||
"127.0.0.1:5000/library/debian",
|
|
||||||
"127.0.0.1:5000/debian",
|
|
||||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
|
||||||
|
|
||||||
// This test case was moved from invalid to valid since it is valid input
|
|
||||||
// when specified with a hostname, it removes the ambiguity from about
|
|
||||||
// whether the value is an identifier or repository name
|
|
||||||
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
|
||||||
}
|
|
||||||
invalidRepoNames := []string{
|
|
||||||
"https://github.com/docker/docker",
|
|
||||||
"docker/Docker",
|
|
||||||
"-docker",
|
|
||||||
"-docker/docker",
|
|
||||||
"-docker.io/docker/docker",
|
|
||||||
"docker///docker",
|
|
||||||
"docker.io/docker/Docker",
|
|
||||||
"docker.io/docker///docker",
|
|
||||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range invalidRepoNames {
|
|
||||||
_, err := ParseNormalizedNamed(name)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected invalid repo name for %q", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range validRepoNames {
|
|
||||||
_, err := ParseNormalizedNamed(name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error parsing repo name %s, got: %q", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateRemoteName(t *testing.T) {
|
|
||||||
validRepositoryNames := []string{
|
|
||||||
// Sanity check.
|
|
||||||
"docker/docker",
|
|
||||||
|
|
||||||
// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
|
|
||||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
|
||||||
|
|
||||||
// Allow embedded hyphens.
|
|
||||||
"docker-rules/docker",
|
|
||||||
|
|
||||||
// Allow multiple hyphens as well.
|
|
||||||
"docker---rules/docker",
|
|
||||||
|
|
||||||
//Username doc and image name docker being tested.
|
|
||||||
"doc/docker",
|
|
||||||
|
|
||||||
// single character names are now allowed.
|
|
||||||
"d/docker",
|
|
||||||
"jess/t",
|
|
||||||
|
|
||||||
// Consecutive underscores.
|
|
||||||
"dock__er/docker",
|
|
||||||
}
|
|
||||||
for _, repositoryName := range validRepositoryNames {
|
|
||||||
_, err := ParseNormalizedNamed(repositoryName)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidRepositoryNames := []string{
|
|
||||||
// Disallow capital letters.
|
|
||||||
"docker/Docker",
|
|
||||||
|
|
||||||
// Only allow one slash.
|
|
||||||
"docker///docker",
|
|
||||||
|
|
||||||
// Disallow 64-character hexadecimal.
|
|
||||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
|
||||||
|
|
||||||
// Disallow leading and trailing hyphens in namespace.
|
|
||||||
"-docker/docker",
|
|
||||||
"docker-/docker",
|
|
||||||
"-docker-/docker",
|
|
||||||
|
|
||||||
// Don't allow underscores everywhere (as opposed to hyphens).
|
|
||||||
"____/____",
|
|
||||||
|
|
||||||
"_docker/_docker",
|
|
||||||
|
|
||||||
// Disallow consecutive periods.
|
|
||||||
"dock..er/docker",
|
|
||||||
"dock_.er/docker",
|
|
||||||
"dock-.er/docker",
|
|
||||||
|
|
||||||
// No repository.
|
|
||||||
"docker/",
|
|
||||||
|
|
||||||
//namespace too long
|
|
||||||
"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",
|
|
||||||
}
|
|
||||||
for _, repositoryName := range invalidRepositoryNames {
|
|
||||||
if _, err := ParseNormalizedNamed(repositoryName); err == nil {
|
|
||||||
t.Errorf("Repository name should be invalid: %v", repositoryName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseRepositoryInfo(t *testing.T) {
|
|
||||||
type tcase struct {
|
|
||||||
RemoteName, FamiliarName, FullName, AmbiguousName, Domain string
|
|
||||||
}
|
|
||||||
|
|
||||||
tcases := []tcase{
|
|
||||||
{
|
|
||||||
RemoteName: "fooo/bar",
|
|
||||||
FamiliarName: "fooo/bar",
|
|
||||||
FullName: "docker.io/fooo/bar",
|
|
||||||
AmbiguousName: "index.docker.io/fooo/bar",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "library/ubuntu",
|
|
||||||
FamiliarName: "ubuntu",
|
|
||||||
FullName: "docker.io/library/ubuntu",
|
|
||||||
AmbiguousName: "library/ubuntu",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "nonlibrary/ubuntu",
|
|
||||||
FamiliarName: "nonlibrary/ubuntu",
|
|
||||||
FullName: "docker.io/nonlibrary/ubuntu",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "other/library",
|
|
||||||
FamiliarName: "other/library",
|
|
||||||
FullName: "docker.io/other/library",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "private/moonbase",
|
|
||||||
FamiliarName: "127.0.0.1:8000/private/moonbase",
|
|
||||||
FullName: "127.0.0.1:8000/private/moonbase",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "127.0.0.1:8000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "privatebase",
|
|
||||||
FamiliarName: "127.0.0.1:8000/privatebase",
|
|
||||||
FullName: "127.0.0.1:8000/privatebase",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "127.0.0.1:8000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "private/moonbase",
|
|
||||||
FamiliarName: "example.com/private/moonbase",
|
|
||||||
FullName: "example.com/private/moonbase",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "privatebase",
|
|
||||||
FamiliarName: "example.com/privatebase",
|
|
||||||
FullName: "example.com/privatebase",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "private/moonbase",
|
|
||||||
FamiliarName: "example.com:8000/private/moonbase",
|
|
||||||
FullName: "example.com:8000/private/moonbase",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "example.com:8000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "privatebasee",
|
|
||||||
FamiliarName: "example.com:8000/privatebasee",
|
|
||||||
FullName: "example.com:8000/privatebasee",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "example.com:8000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "library/ubuntu-12.04-base",
|
|
||||||
FamiliarName: "ubuntu-12.04-base",
|
|
||||||
FullName: "docker.io/library/ubuntu-12.04-base",
|
|
||||||
AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "library/foo",
|
|
||||||
FamiliarName: "foo",
|
|
||||||
FullName: "docker.io/library/foo",
|
|
||||||
AmbiguousName: "docker.io/foo",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "library/foo/bar",
|
|
||||||
FamiliarName: "library/foo/bar",
|
|
||||||
FullName: "docker.io/library/foo/bar",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RemoteName: "store/foo/bar",
|
|
||||||
FamiliarName: "store/foo/bar",
|
|
||||||
FullName: "docker.io/store/foo/bar",
|
|
||||||
AmbiguousName: "",
|
|
||||||
Domain: "docker.io",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tcase := range tcases {
|
|
||||||
refStrings := []string{tcase.FamiliarName, tcase.FullName}
|
|
||||||
if tcase.AmbiguousName != "" {
|
|
||||||
refStrings = append(refStrings, tcase.AmbiguousName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var refs []Named
|
|
||||||
for _, r := range refStrings {
|
|
||||||
named, err := ParseNormalizedNamed(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
refs = append(refs, named)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range refs {
|
|
||||||
if expected, actual := tcase.FamiliarName, FamiliarName(r); expected != actual {
|
|
||||||
t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := tcase.FullName, r.String(); expected != actual {
|
|
||||||
t.Fatalf("Invalid canonical reference for %q. Expected %q, got %q", r, expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := tcase.Domain, Domain(r); expected != actual {
|
|
||||||
t.Fatalf("Invalid domain for %q. Expected %q, got %q", r, expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := tcase.RemoteName, Path(r); expected != actual {
|
|
||||||
t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseReferenceWithTagAndDigest(t *testing.T) {
|
|
||||||
shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
|
|
||||||
ref, err := ParseNormalizedNamed(shortRef)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
|
|
||||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, isTagged := ref.(NamedTagged); !isTagged {
|
|
||||||
t.Fatalf("Reference from %q should support tag", ref)
|
|
||||||
}
|
|
||||||
if _, isCanonical := ref.(Canonical); !isCanonical {
|
|
||||||
t.Fatalf("Reference from %q should support digest", ref)
|
|
||||||
}
|
|
||||||
if expected, actual := shortRef, FamiliarString(ref); actual != expected {
|
|
||||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidReferenceComponents(t *testing.T) {
|
|
||||||
if _, err := ParseNormalizedNamed("-foo"); err == nil {
|
|
||||||
t.Fatal("Expected WithName to detect invalid name")
|
|
||||||
}
|
|
||||||
ref, err := ParseNormalizedNamed("busybox")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err := WithTag(ref, "-foo"); err == nil {
|
|
||||||
t.Fatal("Expected WithName to detect invalid tag")
|
|
||||||
}
|
|
||||||
if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
|
|
||||||
t.Fatal("Expected WithDigest to detect invalid digest")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalReference(r1, r2 Reference) bool {
|
|
||||||
switch v1 := r1.(type) {
|
|
||||||
case digestReference:
|
|
||||||
if v2, ok := r2.(digestReference); ok {
|
|
||||||
return v1 == v2
|
|
||||||
}
|
|
||||||
case repository:
|
|
||||||
if v2, ok := r2.(repository); ok {
|
|
||||||
return v1 == v2
|
|
||||||
}
|
|
||||||
case taggedReference:
|
|
||||||
if v2, ok := r2.(taggedReference); ok {
|
|
||||||
return v1 == v2
|
|
||||||
}
|
|
||||||
case canonicalReference:
|
|
||||||
if v2, ok := r2.(canonicalReference); ok {
|
|
||||||
return v1 == v2
|
|
||||||
}
|
|
||||||
case reference:
|
|
||||||
if v2, ok := r2.(reference); ok {
|
|
||||||
return v1 == v2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseAnyReference(t *testing.T) {
|
|
||||||
tcases := []struct {
|
|
||||||
Reference string
|
|
||||||
Equivalent string
|
|
||||||
Expected Reference
|
|
||||||
Digests []digest.Digest
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Reference: "redis",
|
|
||||||
Equivalent: "docker.io/library/redis",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "redis:latest",
|
|
||||||
Equivalent: "docker.io/library/redis:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "docker.io/library/redis:latest",
|
|
||||||
Equivalent: "docker.io/library/redis:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dmcgowan/myapp",
|
|
||||||
Equivalent: "docker.io/dmcgowan/myapp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dmcgowan/myapp:latest",
|
|
||||||
Equivalent: "docker.io/dmcgowan/myapp:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "docker.io/mcgowan/myapp:latest",
|
|
||||||
Equivalent: "docker.io/mcgowan/myapp:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
||||||
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
||||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Digests: []digest.Digest{
|
|
||||||
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
||||||
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
||||||
Digests: []digest.Digest{
|
|
||||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1c",
|
|
||||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
||||||
Digests: []digest.Digest{
|
|
||||||
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1",
|
|
||||||
Equivalent: "docker.io/library/dbcc1",
|
|
||||||
Digests: []digest.Digest{
|
|
||||||
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Reference: "dbcc1c",
|
|
||||||
Equivalent: "docker.io/library/dbcc1c",
|
|
||||||
Digests: []digest.Digest{
|
|
||||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tcase := range tcases {
|
|
||||||
var ref Reference
|
|
||||||
var err error
|
|
||||||
if len(tcase.Digests) == 0 {
|
|
||||||
ref, err = ParseAnyReference(tcase.Reference)
|
|
||||||
} else {
|
|
||||||
ds := digestset.NewSet()
|
|
||||||
for _, dgst := range tcase.Digests {
|
|
||||||
if err := ds.Add(dgst); err != nil {
|
|
||||||
t.Fatalf("Error adding digest %s: %v", dgst.String(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref, err = ParseAnyReferenceWithSet(tcase.Reference, ds)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error parsing reference %s: %v", tcase.Reference, err)
|
|
||||||
}
|
|
||||||
if ref.String() != tcase.Equivalent {
|
|
||||||
t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tcase.Equivalent)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := tcase.Expected
|
|
||||||
if expected == nil {
|
|
||||||
expected, err = Parse(tcase.Equivalent)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error parsing reference %s: %v", tcase.Equivalent, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !equalReference(ref, expected) {
|
|
||||||
t.Errorf("Unexpected reference %#v, expected %#v", ref, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNormalizedSplitHostname(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
input string
|
|
||||||
domain string
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "test.com/foo",
|
|
||||||
domain: "test.com",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test_com/foo",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "test_com/foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker/migrator",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "docker/migrator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com:8080/foo",
|
|
||||||
domain: "test.com:8080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test-com:8080/foo",
|
|
||||||
domain: "test-com:8080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "library/foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com/foo",
|
|
||||||
domain: "xn--n3h.com",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com:18080/foo",
|
|
||||||
domain: "xn--n3h.com:18080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker.io/foo",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "library/foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker.io/library/foo",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "library/foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker.io/library/foo/bar",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "library/foo/bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
named, err := ParseNormalizedNamed(testcase.input) //nolint:staticcheck // Ignore SA1019: SplitHostname is deprecated.
|
|
||||||
if err != nil {
|
|
||||||
failf("error parsing name: %s", err)
|
|
||||||
}
|
|
||||||
domain, name := SplitHostname(named)
|
|
||||||
if domain != testcase.domain {
|
|
||||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
|
||||||
}
|
|
||||||
if name != testcase.name {
|
|
||||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatchError(t *testing.T) {
|
|
||||||
named, err := ParseAnyReference("foo")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = FamiliarMatch("[-x]", named)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected an error, got nothing")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
|
||||||
matchCases := []struct {
|
|
||||||
reference string
|
|
||||||
pattern string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
reference: "foo",
|
|
||||||
pattern: "foo/**/ba[rz]",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "foo/any/bat",
|
|
||||||
pattern: "foo/**/ba[rz]",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "foo/a/bar",
|
|
||||||
pattern: "foo/**/ba[rz]",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "foo/b/baz",
|
|
||||||
pattern: "foo/**/ba[rz]",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "foo/c/baz:tag",
|
|
||||||
pattern: "foo/**/ba[rz]",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "foo/c/baz:tag",
|
|
||||||
pattern: "foo/*/baz:tag",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "foo/c/baz:tag",
|
|
||||||
pattern: "foo/c/baz:tag",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "example.com/foo/c/baz:tag",
|
|
||||||
pattern: "*/foo/c/baz",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reference: "example.com/foo/c/baz:tag",
|
|
||||||
pattern: "example.com/foo/c/baz",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, c := range matchCases {
|
|
||||||
named, err := ParseAnyReference(c.reference)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
actual, err := FamiliarMatch(c.pattern, named)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if actual != c.expected {
|
|
||||||
t.Fatalf("expected %s match %s to be %v, was %v", c.reference, c.pattern, c.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDockerRef(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nothing",
|
|
||||||
input: "busybox",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tag only",
|
|
||||||
input: "busybox:latest",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "digest only",
|
|
||||||
input: "busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
expected: "docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "path only",
|
|
||||||
input: "library/busybox",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "hostname only",
|
|
||||||
input: "docker.io/busybox",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no tag",
|
|
||||||
input: "docker.io/library/busybox",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no path",
|
|
||||||
input: "docker.io/busybox:latest",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no hostname",
|
|
||||||
input: "library/busybox:latest",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "full reference with tag",
|
|
||||||
input: "docker.io/library/busybox:latest",
|
|
||||||
expected: "docker.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "gcr reference without tag",
|
|
||||||
input: "gcr.io/library/busybox",
|
|
||||||
expected: "gcr.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "both tag and digest",
|
|
||||||
input: "gcr.io/library/busybox:latest@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
expected: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range testcases {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
normalized, err := ParseDockerRef(test.input)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
output := normalized.String()
|
|
||||||
if output != test.expected {
|
|
||||||
t.Fatalf("expected %q to be parsed as %v, got %v", test.input, test.expected, output)
|
|
||||||
}
|
|
||||||
_, err = Parse(output)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%q should be a valid reference, but got an error: %v", output, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
172
reference/reference_deprecated.go
Normal file
172
reference/reference_deprecated.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// Package reference is deprecated, and has moved to github.com/distribution/reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use github.com/distribution/reference instead.
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NameTotalLengthMax is the maximum total number of characters in a repository name.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.NameTotalLengthMax].
|
||||||
|
NameTotalLengthMax = reference.NameTotalLengthMax
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrReferenceInvalidFormat].
|
||||||
|
ErrReferenceInvalidFormat = reference.ErrReferenceInvalidFormat
|
||||||
|
|
||||||
|
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrTagInvalidFormat].
|
||||||
|
ErrTagInvalidFormat = reference.ErrTagInvalidFormat
|
||||||
|
|
||||||
|
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrDigestInvalidFormat].
|
||||||
|
ErrDigestInvalidFormat = reference.ErrDigestInvalidFormat
|
||||||
|
|
||||||
|
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrNameContainsUppercase].
|
||||||
|
ErrNameContainsUppercase = reference.ErrNameContainsUppercase
|
||||||
|
|
||||||
|
// ErrNameEmpty is returned for empty, invalid repository names.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrNameEmpty].
|
||||||
|
ErrNameEmpty = reference.ErrNameEmpty
|
||||||
|
|
||||||
|
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrNameTooLong].
|
||||||
|
ErrNameTooLong = reference.ErrNameTooLong
|
||||||
|
|
||||||
|
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ErrNameNotCanonical].
|
||||||
|
ErrNameNotCanonical = reference.ErrNameNotCanonical
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference is an opaque object reference identifier that may include
|
||||||
|
// modifiers such as a hostname, name, tag, and digest.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Reference].
|
||||||
|
type Reference = reference.Reference
|
||||||
|
|
||||||
|
// Field provides a wrapper type for resolving correct reference types when
|
||||||
|
// working with encoding.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Field].
|
||||||
|
type Field = reference.Field
|
||||||
|
|
||||||
|
// AsField wraps a reference in a Field for encoding.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.AsField].
|
||||||
|
func AsField(ref reference.Reference) reference.Field {
|
||||||
|
return reference.AsField(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named is an object with a full name
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Named].
|
||||||
|
type Named = reference.Named
|
||||||
|
|
||||||
|
// Tagged is an object which has a tag
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Tagged].
|
||||||
|
type Tagged = reference.Tagged
|
||||||
|
|
||||||
|
// NamedTagged is an object including a name and tag.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.NamedTagged].
|
||||||
|
type NamedTagged reference.NamedTagged
|
||||||
|
|
||||||
|
// Digested is an object which has a digest
|
||||||
|
// in which it can be referenced by
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Digested].
|
||||||
|
type Digested reference.Digested
|
||||||
|
|
||||||
|
// Canonical reference is an object with a fully unique
|
||||||
|
// name including a name with domain and digest
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Canonical].
|
||||||
|
type Canonical reference.Canonical
|
||||||
|
|
||||||
|
// Domain returns the domain part of the [Named] reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Domain].
|
||||||
|
func Domain(named reference.Named) string {
|
||||||
|
return reference.Domain(named)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the name without the domain part of the [Named] reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Path].
|
||||||
|
func Path(named reference.Named) (name string) {
|
||||||
|
return reference.Path(named)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 [reference.Domain] or [reference.Path].
|
||||||
|
func SplitHostname(named reference.Named) (string, string) {
|
||||||
|
return reference.SplitHostname(named)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses s and returns a syntactically valid Reference.
|
||||||
|
// If an error was encountered it is returned, along with a nil Reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Parse].
|
||||||
|
func Parse(s string) (reference.Reference, error) {
|
||||||
|
return reference.Parse(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNamed parses s and returns a syntactically valid reference implementing
|
||||||
|
// the Named interface. The reference must have a name and be in the canonical
|
||||||
|
// form, otherwise an error is returned.
|
||||||
|
// If an error was encountered it is returned, along with a nil Reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ParseNamed].
|
||||||
|
func ParseNamed(s string) (reference.Named, error) {
|
||||||
|
return reference.ParseNamed(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName returns a named object representing the given string. If the input
|
||||||
|
// is invalid ErrReferenceInvalidFormat will be returned.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.WithName].
|
||||||
|
func WithName(name string) (reference.Named, error) {
|
||||||
|
return reference.WithName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTag combines the name from "name" and the tag from "tag" to form a
|
||||||
|
// reference incorporating both the name and the tag.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.WithTag].
|
||||||
|
func WithTag(name reference.Named, tag string) (reference.NamedTagged, error) {
|
||||||
|
return reference.WithTag(name, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDigest combines the name from "name" and the digest from "digest" to form
|
||||||
|
// a reference incorporating both the name and the digest.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.WithDigest].
|
||||||
|
func WithDigest(name reference.Named, digest digest.Digest) (reference.Canonical, error) {
|
||||||
|
return reference.WithDigest(name, digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimNamed removes any tag or digest from the named reference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.TrimNamed].
|
||||||
|
func TrimNamed(ref reference.Named) reference.Named {
|
||||||
|
return reference.TrimNamed(ref)
|
||||||
|
}
|
|
@ -1,659 +0,0 @@
|
||||||
package reference
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReferenceParse(t *testing.T) {
|
|
||||||
// referenceTestcases is a unified set of testcases for
|
|
||||||
// testing the parsing of references
|
|
||||||
referenceTestcases := []struct {
|
|
||||||
// input is the repository name or name component testcase
|
|
||||||
input string
|
|
||||||
// err is the error expected from Parse, or nil
|
|
||||||
err error
|
|
||||||
// repository is the string representation for the reference
|
|
||||||
repository string
|
|
||||||
// domain is the domain expected in the reference
|
|
||||||
domain string
|
|
||||||
// tag is the tag for the reference
|
|
||||||
tag string
|
|
||||||
// digest is the digest for the reference (enforces digest reference)
|
|
||||||
digest string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "test_com",
|
|
||||||
repository: "test_com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com:tag",
|
|
||||||
repository: "test.com",
|
|
||||||
tag: "tag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com:5000",
|
|
||||||
repository: "test.com",
|
|
||||||
tag: "5000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com/repo:tag",
|
|
||||||
domain: "test.com",
|
|
||||||
repository: "test.com/repo",
|
|
||||||
tag: "tag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:5000/repo",
|
|
||||||
domain: "test:5000",
|
|
||||||
repository: "test:5000/repo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:5000/repo:tag",
|
|
||||||
domain: "test:5000",
|
|
||||||
repository: "test:5000/repo",
|
|
||||||
tag: "tag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
domain: "test:5000",
|
|
||||||
repository: "test:5000/repo",
|
|
||||||
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
domain: "test:5000",
|
|
||||||
repository: "test:5000/repo",
|
|
||||||
tag: "tag",
|
|
||||||
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:5000/repo",
|
|
||||||
domain: "test:5000",
|
|
||||||
repository: "test:5000/repo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "",
|
|
||||||
err: ErrNameEmpty,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: ":justtag",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
|
|
||||||
err: digest.ErrDigestInvalidLength,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
err: digest.ErrDigestUnsupported,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "Uppercase:tag",
|
|
||||||
err: ErrNameContainsUppercase,
|
|
||||||
},
|
|
||||||
// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
|
|
||||||
// See https://github.com/docker/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
|
|
||||||
//{
|
|
||||||
// input: "Uppercase/lowercase:tag",
|
|
||||||
// err: ErrNameContainsUppercase,
|
|
||||||
//},
|
|
||||||
{
|
|
||||||
input: "test:5000/Uppercase/lowercase:tag",
|
|
||||||
err: ErrNameContainsUppercase,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "lowercase:Uppercase",
|
|
||||||
repository: "lowercase",
|
|
||||||
tag: "Uppercase",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: strings.Repeat("a/", 128) + "a:tag",
|
|
||||||
err: ErrNameTooLong,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
|
|
||||||
domain: "a",
|
|
||||||
repository: strings.Repeat("a/", 127) + "a",
|
|
||||||
tag: "tag-puts-this-over-max",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/asdf$$^/aa",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
|
||||||
domain: "sub-dom1.foo.com",
|
|
||||||
repository: "sub-dom1.foo.com/bar/baz/quux",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
|
|
||||||
domain: "sub-dom1.foo.com",
|
|
||||||
repository: "sub-dom1.foo.com/bar/baz/quux",
|
|
||||||
tag: "some-long-tag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "b.gcr.io/test.example.com/my-app:test.example.com",
|
|
||||||
domain: "b.gcr.io",
|
|
||||||
repository: "b.gcr.io/test.example.com/my-app",
|
|
||||||
tag: "test.example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
|
|
||||||
domain: "xn--n3h.com",
|
|
||||||
repository: "xn--n3h.com/myimage",
|
|
||||||
tag: "xn--n3h.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
|
|
||||||
domain: "xn--7o8h.com",
|
|
||||||
repository: "xn--7o8h.com/myimage",
|
|
||||||
tag: "xn--7o8h.com",
|
|
||||||
digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo_bar.com:8080",
|
|
||||||
repository: "foo_bar.com",
|
|
||||||
tag: "8080",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo/foo_bar.com:8080",
|
|
||||||
domain: "foo",
|
|
||||||
repository: "foo/foo_bar.com",
|
|
||||||
tag: "8080",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range referenceTestcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := Parse(testcase.input)
|
|
||||||
if testcase.err != nil {
|
|
||||||
if err == nil {
|
|
||||||
failf("missing expected error: %v", testcase.err)
|
|
||||||
} else if testcase.err != err {
|
|
||||||
failf("mismatched error: got %v, expected %v", err, testcase.err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
failf("unexpected parse error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if repo.String() != testcase.input {
|
|
||||||
failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input)
|
|
||||||
}
|
|
||||||
|
|
||||||
if named, ok := repo.(Named); ok {
|
|
||||||
if named.Name() != testcase.repository {
|
|
||||||
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository)
|
|
||||||
}
|
|
||||||
domain, _ := SplitHostname(named)
|
|
||||||
if domain != testcase.domain {
|
|
||||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
|
||||||
}
|
|
||||||
} else if testcase.repository != "" || testcase.domain != "" {
|
|
||||||
failf("expected named type, got %T", repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagged, ok := repo.(Tagged)
|
|
||||||
if testcase.tag != "" {
|
|
||||||
if ok {
|
|
||||||
if tagged.Tag() != testcase.tag {
|
|
||||||
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failf("expected tagged type, got %T", repo)
|
|
||||||
}
|
|
||||||
} else if ok {
|
|
||||||
failf("unexpected tagged type")
|
|
||||||
}
|
|
||||||
|
|
||||||
digested, ok := repo.(Digested)
|
|
||||||
if testcase.digest != "" {
|
|
||||||
if ok {
|
|
||||||
if digested.Digest().String() != testcase.digest {
|
|
||||||
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failf("expected digested type, got %T", repo)
|
|
||||||
}
|
|
||||||
} else if ok {
|
|
||||||
failf("unexpected digested type")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestWithNameFailure tests cases where WithName should fail. Cases where it
|
|
||||||
// should succeed are covered by TestSplitHostname, below.
|
|
||||||
func TestWithNameFailure(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
input string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "",
|
|
||||||
err: ErrNameEmpty,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: ":justtag",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: strings.Repeat("a/", 128) + "a:tag",
|
|
||||||
err: ErrNameTooLong,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/asdf$$^/aa",
|
|
||||||
err: ErrReferenceInvalidFormat,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := WithName(testcase.input)
|
|
||||||
if err == nil {
|
|
||||||
failf("no error parsing name. expected: %s", testcase.err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplitHostname(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
input string
|
|
||||||
domain string
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "test.com/foo",
|
|
||||||
domain: "test.com",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test_com/foo",
|
|
||||||
domain: "",
|
|
||||||
name: "test_com/foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:8080/foo",
|
|
||||||
domain: "test:8080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com:8080/foo",
|
|
||||||
domain: "test.com:8080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test-com:8080/foo",
|
|
||||||
domain: "test-com:8080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com:18080/foo",
|
|
||||||
domain: "xn--n3h.com:18080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
named, err := WithName(testcase.input)
|
|
||||||
if err != nil {
|
|
||||||
failf("error parsing name: %s", err)
|
|
||||||
}
|
|
||||||
domain, name := SplitHostname(named)
|
|
||||||
if domain != testcase.domain {
|
|
||||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
|
||||||
}
|
|
||||||
if name != testcase.name {
|
|
||||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type serializationType struct {
|
|
||||||
Description string
|
|
||||||
Field Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerialization(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
description string
|
|
||||||
input string
|
|
||||||
name string
|
|
||||||
tag string
|
|
||||||
digest string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "empty value",
|
|
||||||
err: ErrNameEmpty,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "just a name",
|
|
||||||
input: "example.com:8000/named",
|
|
||||||
name: "example.com:8000/named",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "name with a tag",
|
|
||||||
input: "example.com:8000/named:tagged",
|
|
||||||
name: "example.com:8000/named",
|
|
||||||
tag: "tagged",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "name with digest",
|
|
||||||
input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
|
|
||||||
name: "other.com/named",
|
|
||||||
digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
m := map[string]string{
|
|
||||||
"Description": testcase.description,
|
|
||||||
"Field": testcase.input,
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
failf("error marshalling: %v", err)
|
|
||||||
}
|
|
||||||
t := serializationType{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &t); err != nil {
|
|
||||||
if testcase.err == nil {
|
|
||||||
failf("error unmarshalling: %v", err)
|
|
||||||
}
|
|
||||||
if err != testcase.err {
|
|
||||||
failf("wrong error, expected %v, got %v", testcase.err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
} else if testcase.err != nil {
|
|
||||||
failf("expected error unmarshalling: %v", testcase.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Description != testcase.description {
|
|
||||||
failf("wrong description, expected %q, got %q", testcase.description, t.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := t.Field.Reference()
|
|
||||||
|
|
||||||
if named, ok := ref.(Named); ok {
|
|
||||||
if named.Name() != testcase.name {
|
|
||||||
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name)
|
|
||||||
}
|
|
||||||
} else if testcase.name != "" {
|
|
||||||
failf("expected named type, got %T", ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagged, ok := ref.(Tagged)
|
|
||||||
if testcase.tag != "" {
|
|
||||||
if ok {
|
|
||||||
if tagged.Tag() != testcase.tag {
|
|
||||||
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failf("expected tagged type, got %T", ref)
|
|
||||||
}
|
|
||||||
} else if ok {
|
|
||||||
failf("unexpected tagged type")
|
|
||||||
}
|
|
||||||
|
|
||||||
digested, ok := ref.(Digested)
|
|
||||||
if testcase.digest != "" {
|
|
||||||
if ok {
|
|
||||||
if digested.Digest().String() != testcase.digest {
|
|
||||||
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failf("expected digested type, got %T", ref)
|
|
||||||
}
|
|
||||||
} else if ok {
|
|
||||||
failf("unexpected digested type")
|
|
||||||
}
|
|
||||||
|
|
||||||
t = serializationType{
|
|
||||||
Description: testcase.description,
|
|
||||||
Field: AsField(ref),
|
|
||||||
}
|
|
||||||
|
|
||||||
b2, err := json.Marshal(t)
|
|
||||||
if err != nil {
|
|
||||||
failf("error marshing serialization type: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(b) != string(b2) {
|
|
||||||
failf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure t.Field is not implementing "Reference" directly, getting
|
|
||||||
// around the Reference type system
|
|
||||||
var fieldInterface interface{} = t.Field
|
|
||||||
if _, ok := fieldInterface.(Reference); ok {
|
|
||||||
failf("field should not implement Reference interface")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithTag(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
name string
|
|
||||||
digest digest.Digest
|
|
||||||
tag string
|
|
||||||
combined string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test.com/foo",
|
|
||||||
tag: "tag",
|
|
||||||
combined: "test.com/foo:tag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "foo",
|
|
||||||
tag: "tag2",
|
|
||||||
combined: "foo:tag2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test.com:8000/foo",
|
|
||||||
tag: "tag4",
|
|
||||||
combined: "test.com:8000/foo:tag4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test.com:8000/foo",
|
|
||||||
tag: "TAG5",
|
|
||||||
combined: "test.com:8000/foo:TAG5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test.com:8000/foo",
|
|
||||||
digest: "sha256:1234567890098765432112345667890098765",
|
|
||||||
tag: "TAG5",
|
|
||||||
combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
named, err := WithName(testcase.name)
|
|
||||||
if err != nil {
|
|
||||||
failf("error parsing name: %s", err)
|
|
||||||
}
|
|
||||||
if testcase.digest != "" {
|
|
||||||
canonical, err := WithDigest(named, testcase.digest)
|
|
||||||
if err != nil {
|
|
||||||
failf("error adding digest")
|
|
||||||
}
|
|
||||||
named = canonical
|
|
||||||
}
|
|
||||||
|
|
||||||
tagged, err := WithTag(named, testcase.tag)
|
|
||||||
if err != nil {
|
|
||||||
failf("WithTag failed: %s", err)
|
|
||||||
}
|
|
||||||
if tagged.String() != testcase.combined {
|
|
||||||
failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithDigest(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
name string
|
|
||||||
digest digest.Digest
|
|
||||||
tag string
|
|
||||||
combined string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test.com/foo",
|
|
||||||
digest: "sha256:1234567890098765432112345667890098765",
|
|
||||||
combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "foo",
|
|
||||||
digest: "sha256:1234567890098765432112345667890098765",
|
|
||||||
combined: "foo@sha256:1234567890098765432112345667890098765",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test.com:8000/foo",
|
|
||||||
digest: "sha256:1234567890098765432112345667890098765",
|
|
||||||
combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test.com:8000/foo",
|
|
||||||
digest: "sha256:1234567890098765432112345667890098765",
|
|
||||||
tag: "latest",
|
|
||||||
combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
named, err := WithName(testcase.name)
|
|
||||||
if err != nil {
|
|
||||||
failf("error parsing name: %s", err)
|
|
||||||
}
|
|
||||||
if testcase.tag != "" {
|
|
||||||
tagged, err := WithTag(named, testcase.tag)
|
|
||||||
if err != nil {
|
|
||||||
failf("error adding tag")
|
|
||||||
}
|
|
||||||
named = tagged
|
|
||||||
}
|
|
||||||
digested, err := WithDigest(named, testcase.digest)
|
|
||||||
if err != nil {
|
|
||||||
failf("WithDigest failed: %s", err)
|
|
||||||
}
|
|
||||||
if digested.String() != testcase.combined {
|
|
||||||
failf("unexpected: got %q, expected %q", digested.String(), testcase.combined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseNamed(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
input string
|
|
||||||
domain string
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "test.com/foo",
|
|
||||||
domain: "test.com",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test:8080/foo",
|
|
||||||
domain: "test:8080",
|
|
||||||
name: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test_com/foo",
|
|
||||||
err: ErrNameNotCanonical,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com",
|
|
||||||
err: ErrNameNotCanonical,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo",
|
|
||||||
err: ErrNameNotCanonical,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "library/foo",
|
|
||||||
err: ErrNameNotCanonical,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker.io/library/foo",
|
|
||||||
domain: "docker.io",
|
|
||||||
name: "library/foo",
|
|
||||||
},
|
|
||||||
// Ambiguous case, parser will add "library/" to foo
|
|
||||||
{
|
|
||||||
input: "docker.io/foo",
|
|
||||||
err: ErrNameNotCanonical,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
named, err := ParseNamed(testcase.input)
|
|
||||||
if err != nil && testcase.err == nil {
|
|
||||||
failf("error parsing name: %s", err)
|
|
||||||
continue
|
|
||||||
} else if err == nil && testcase.err != nil {
|
|
||||||
failf("parsing succeeded: expected error %v", testcase.err)
|
|
||||||
continue
|
|
||||||
} else if err != testcase.err {
|
|
||||||
failf("unexpected error %v, expected %v", err, testcase.err)
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
domain, name := SplitHostname(named)
|
|
||||||
if domain != testcase.domain {
|
|
||||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
|
||||||
}
|
|
||||||
if name != testcase.name {
|
|
||||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
package reference
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// alphaNumericRegexp defines the alpha numeric atom, typically a
|
|
||||||
// component of names. This only allows lower case characters and digits.
|
|
||||||
alphaNumericRegexp = match(`[a-z0-9]+`)
|
|
||||||
|
|
||||||
// separatorRegexp defines the separators allowed to be embedded in name
|
|
||||||
// components. This allow one period, one or two underscore and multiple
|
|
||||||
// dashes.
|
|
||||||
separatorRegexp = match(`(?:[._]|__|[-]*)`)
|
|
||||||
|
|
||||||
// nameComponentRegexp restricts registry path component names to start
|
|
||||||
// with at least one letter or number, with following parts able to be
|
|
||||||
// separated by one period, one or two underscore and multiple dashes.
|
|
||||||
nameComponentRegexp = expression(
|
|
||||||
alphaNumericRegexp,
|
|
||||||
optional(repeated(separatorRegexp, alphaNumericRegexp)))
|
|
||||||
|
|
||||||
// domainComponentRegexp restricts the registry domain component of a
|
|
||||||
// repository name to start with a component as defined by DomainRegexp
|
|
||||||
// and followed by an optional port.
|
|
||||||
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
|
|
||||||
|
|
||||||
// DomainRegexp defines the structure of potential domain components
|
|
||||||
// that may be part of image names. This is purposely a subset of what is
|
|
||||||
// allowed by DNS to ensure backwards compatibility with Docker image
|
|
||||||
// names.
|
|
||||||
DomainRegexp = expression(
|
|
||||||
domainComponentRegexp,
|
|
||||||
optional(repeated(literal(`.`), domainComponentRegexp)),
|
|
||||||
optional(literal(`:`), match(`[0-9]+`)))
|
|
||||||
|
|
||||||
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
|
||||||
TagRegexp = match(`[\w][\w.-]{0,127}`)
|
|
||||||
|
|
||||||
// anchoredTagRegexp matches valid tag names, anchored at the start and
|
|
||||||
// end of the matched string.
|
|
||||||
anchoredTagRegexp = anchored(TagRegexp)
|
|
||||||
|
|
||||||
// DigestRegexp matches valid digests.
|
|
||||||
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
|
|
||||||
|
|
||||||
// anchoredDigestRegexp matches valid digests, anchored at the start and
|
|
||||||
// end of the matched string.
|
|
||||||
anchoredDigestRegexp = anchored(DigestRegexp)
|
|
||||||
|
|
||||||
// NameRegexp is the format for the name component of references. The
|
|
||||||
// regexp has capturing groups for the domain and name part omitting
|
|
||||||
// the separating forward slash from either.
|
|
||||||
NameRegexp = expression(
|
|
||||||
optional(DomainRegexp, literal(`/`)),
|
|
||||||
nameComponentRegexp,
|
|
||||||
optional(repeated(literal(`/`), nameComponentRegexp)))
|
|
||||||
|
|
||||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
|
||||||
// domain and trailing components.
|
|
||||||
anchoredNameRegexp = anchored(
|
|
||||||
optional(capture(DomainRegexp), literal(`/`)),
|
|
||||||
capture(nameComponentRegexp,
|
|
||||||
optional(repeated(literal(`/`), nameComponentRegexp))))
|
|
||||||
|
|
||||||
// ReferenceRegexp is the full supported format of a reference. The regexp
|
|
||||||
// is anchored and has capturing groups for name, tag, and digest
|
|
||||||
// components.
|
|
||||||
ReferenceRegexp = anchored(capture(NameRegexp),
|
|
||||||
optional(literal(":"), capture(TagRegexp)),
|
|
||||||
optional(literal("@"), capture(DigestRegexp)))
|
|
||||||
|
|
||||||
// IdentifierRegexp is the format for string identifier used as a
|
|
||||||
// content addressable identifier using sha256. These identifiers
|
|
||||||
// are like digests without the algorithm, since sha256 is used.
|
|
||||||
IdentifierRegexp = match(`([a-f0-9]{64})`)
|
|
||||||
|
|
||||||
// ShortIdentifierRegexp is the format used to represent a prefix
|
|
||||||
// of an identifier. A prefix may be used to match a sha256 identifier
|
|
||||||
// within a list of trusted identifiers.
|
|
||||||
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
|
|
||||||
|
|
||||||
// anchoredIdentifierRegexp is used to check or match an
|
|
||||||
// identifier value, anchored at start and end of string.
|
|
||||||
anchoredIdentifierRegexp = anchored(IdentifierRegexp)
|
|
||||||
|
|
||||||
// anchoredShortIdentifierRegexp is used to check if a value
|
|
||||||
// is a possible identifier prefix, anchored at start and end
|
|
||||||
// of string.
|
|
||||||
anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp)
|
|
||||||
)
|
|
||||||
|
|
||||||
// match compiles the string to a regular expression.
|
|
||||||
var match = regexp.MustCompile
|
|
||||||
|
|
||||||
// literal compiles s into a literal regular expression, escaping any regexp
|
|
||||||
// reserved characters.
|
|
||||||
func literal(s string) *regexp.Regexp {
|
|
||||||
re := match(regexp.QuoteMeta(s))
|
|
||||||
|
|
||||||
if _, complete := re.LiteralPrefix(); !complete {
|
|
||||||
panic("must be a literal")
|
|
||||||
}
|
|
||||||
|
|
||||||
return re
|
|
||||||
}
|
|
||||||
|
|
||||||
// expression defines a full expression, where each regular expression must
|
|
||||||
// follow the previous.
|
|
||||||
func expression(res ...*regexp.Regexp) *regexp.Regexp {
|
|
||||||
var s string
|
|
||||||
for _, re := range res {
|
|
||||||
s += re.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return match(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// optional wraps the expression in a non-capturing group and makes the
|
|
||||||
// production optional.
|
|
||||||
func optional(res ...*regexp.Regexp) *regexp.Regexp {
|
|
||||||
return match(group(expression(res...)).String() + `?`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// repeated wraps the regexp in a non-capturing group to get one or more
|
|
||||||
// matches.
|
|
||||||
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
|
|
||||||
return match(group(expression(res...)).String() + `+`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// group wraps the regexp in a non-capturing group.
|
|
||||||
func group(res ...*regexp.Regexp) *regexp.Regexp {
|
|
||||||
return match(`(?:` + expression(res...).String() + `)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// capture wraps the expression in a capturing group.
|
|
||||||
func capture(res ...*regexp.Regexp) *regexp.Regexp {
|
|
||||||
return match(`(` + expression(res...).String() + `)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// anchored anchors the regular expression by adding start and end delimiters.
|
|
||||||
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
|
|
||||||
return match(`^` + expression(res...).String() + `$`)
|
|
||||||
}
|
|
50
reference/regexp_deprecated.go
Normal file
50
reference/regexp_deprecated.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.DigestRegexp].
|
||||||
|
var DigestRegexp = reference.DigestRegexp
|
||||||
|
|
||||||
|
// DomainRegexp matches hostname or IP-addresses, optionally including a port
|
||||||
|
// number. It defines the structure of potential domain components that may be
|
||||||
|
// part of image names. This is purposely a subset of what is allowed by DNS to
|
||||||
|
// ensure backwards compatibility with Docker image names. It may be a subset of
|
||||||
|
// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between
|
||||||
|
// square brackets (excluding zone identifiers as defined by [RFC 6874] or special
|
||||||
|
// addresses such as IPv4-Mapped).
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.DomainRegexp].
|
||||||
|
//
|
||||||
|
// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874.
|
||||||
|
var DomainRegexp = reference.DigestRegexp
|
||||||
|
|
||||||
|
// IdentifierRegexp is the format for string identifier used as a
|
||||||
|
// content addressable identifier using sha256. These identifiers
|
||||||
|
// are like digests without the algorithm, since sha256 is used.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.IdentifierRegexp].
|
||||||
|
var IdentifierRegexp = reference.IdentifierRegexp
|
||||||
|
|
||||||
|
// NameRegexp is the format for the name component of references, including
|
||||||
|
// an optional domain and port, but without tag or digest suffix.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.NameRegexp].
|
||||||
|
var NameRegexp = reference.NameRegexp
|
||||||
|
|
||||||
|
// ReferenceRegexp is the full supported format of a reference. The regexp
|
||||||
|
// is anchored and has capturing groups for name, tag, and digest
|
||||||
|
// components.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.ReferenceRegexp].
|
||||||
|
var ReferenceRegexp = reference.ReferenceRegexp
|
||||||
|
|
||||||
|
// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.TagRegexp].
|
||||||
|
//
|
||||||
|
// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28
|
||||||
|
var TagRegexp = reference.TagRegexp
|
|
@ -1,553 +0,0 @@
|
||||||
package reference
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type regexpMatch struct {
|
|
||||||
input string
|
|
||||||
match bool
|
|
||||||
subs []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
|
|
||||||
matches := r.FindStringSubmatch(m.input)
|
|
||||||
if m.match && matches != nil {
|
|
||||||
if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
|
|
||||||
t.Fatalf("Bad match result %#v for %q", matches, m.input)
|
|
||||||
}
|
|
||||||
if len(matches) < (len(m.subs) + 1) {
|
|
||||||
t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
|
|
||||||
}
|
|
||||||
for i := range m.subs {
|
|
||||||
if m.subs[i] != matches[i+1] {
|
|
||||||
t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if m.match {
|
|
||||||
t.Errorf("Expected match for %q", m.input)
|
|
||||||
} else if matches != nil {
|
|
||||||
t.Errorf("Unexpected match for %q", m.input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDomainRegexp(t *testing.T) {
|
|
||||||
hostcases := []regexpMatch{
|
|
||||||
{
|
|
||||||
input: "test.com",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com:10304",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "test.com:http",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:8080",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a.b",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "ab.cd.com",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a-b.com",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "-ab.com",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "ab-.com",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "ab.c-om",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "ab.-com",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "ab.com-",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "0101.com",
|
|
||||||
match: true, // TODO(dmcgowan): valid if this should be allowed
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "001a.com",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "b.gbc.io:443",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "b.gbc.io",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com", // ☃.com in punycode
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "Asdf.com", // uppercase character
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
|
|
||||||
for i := range hostcases {
|
|
||||||
checkRegexp(t, r, hostcases[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFullNameRegexp(t *testing.T) {
|
|
||||||
if anchoredNameRegexp.NumSubexp() != 2 {
|
|
||||||
t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
|
|
||||||
anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []regexpMatch{
|
|
||||||
{
|
|
||||||
input: "",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "short",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"", "short"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "simple/name",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"simple", "name"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "library/ubuntu",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"library", "ubuntu"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker/stevvooe/app",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"docker", "stevvooe/app"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/aa/bb/bb/bb",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"aa", "aa/bb/bb/bb"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/a/a/a",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"a", "a/a/a"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/a/a/a/",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a//a/a",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"", "a"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/aa",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"a", "aa"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/aa/a",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"a", "aa/a"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"", "foo.com"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com:8080/bar",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"foo.com:8080", "bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com:http/bar",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/bar",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"foo.com", "bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/bar/baz",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"foo.com", "bar/baz"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:8080/bar",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"localhost:8080", "bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"sub-dom1.foo.com", "bar/baz/quux"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "blog.foo.com/bar/baz",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"blog.foo.com", "bar/baz"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a^a",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/asdf$$^/aa",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "asdf$$^/aa",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa-a/a",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"aa-a", "a"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: strings.Repeat("a/", 128) + "a",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"a", strings.Repeat("a/", 127) + "a"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a-/a/a/a",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/a-/a/a",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "-foo/bar",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo/bar-",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo-/bar",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo/-bar",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "_foo/bar",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo_bar",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"", "foo_bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo_bar.com",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"", "foo_bar.com"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo_bar.com:8080",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo_bar.com:8080/app",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/foo_bar",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"foo.com", "foo_bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "____/____",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "_docker/_docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker_/docker_",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "b.gcr.io/test.example.com/my-app",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com/myimage", // ☃.com in punycode
|
|
||||||
match: true,
|
|
||||||
subs: []string{"xn--n3h.com", "myimage"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--7o8h.com/myimage", // 🐳.com in punycode
|
|
||||||
match: true,
|
|
||||||
subs: []string{"xn--7o8h.com", "myimage"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
|
|
||||||
match: true,
|
|
||||||
subs: []string{"example.com", "xn--7o8h.com/myimage"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/some_separator__underscore/myimage",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"example.com", "some_separator__underscore/myimage"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/__underscore/myimage",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/..dots/myimage",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/.dots/myimage",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/nodouble..dots/myimage",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "example.com/nodouble..dots/myimage",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker./docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: ".docker/docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker-/docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "-docker/docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "do..cker/docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "do__cker:8080/docker",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "do__cker/docker",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"", "do__cker/docker"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "b.gcr.io/test.example.com/my-app",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.io/foo/project--id.module--name.ver---sion--name",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "Asdf.com/foo/bar", // uppercase character in hostname
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "Foo/FarB", // uppercase characters in remote name
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range testcases {
|
|
||||||
checkRegexp(t, anchoredNameRegexp, testcases[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReferenceRegexp(t *testing.T) {
|
|
||||||
if ReferenceRegexp.NumSubexp() != 3 {
|
|
||||||
t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
|
|
||||||
ReferenceRegexp, ReferenceRegexp.NumSubexp())
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []regexpMatch{
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp:tag",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"registry.com:8080/myapp", "tag", ""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp@sha256:badbadbadbad",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp:invalid~tag",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "bad_hostname.com:8080/myapp:tag",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input:// localhost treated as name, missing tag with 8080 as tag
|
|
||||||
"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// localhost will be treated as an image name without a host
|
|
||||||
input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
|
||||||
match: true,
|
|
||||||
subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp@bad",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.com:8080/myapp@2bad",
|
|
||||||
match: false, // TODO(dmcgowan): Support this as valid
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range testcases {
|
|
||||||
checkRegexp(t, ReferenceRegexp, testcases[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIdentifierRegexp(t *testing.T) {
|
|
||||||
fullCases := []regexpMatch{
|
|
||||||
{
|
|
||||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
shortCases := []regexpMatch{
|
|
||||||
{
|
|
||||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "da304",
|
|
||||||
match: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "da304e",
|
|
||||||
match: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range fullCases {
|
|
||||||
checkRegexp(t, anchoredIdentifierRegexp, fullCases[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range shortCases {
|
|
||||||
checkRegexp(t, anchoredShortIdentifierRegexp, shortCases[i])
|
|
||||||
}
|
|
||||||
}
|
|
10
reference/sort_deprecated.go
Normal file
10
reference/sort_deprecated.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import "github.com/distribution/reference"
|
||||||
|
|
||||||
|
// Sort sorts string references preferring higher information references.
|
||||||
|
//
|
||||||
|
// Deprecated: use [reference.Sort].
|
||||||
|
func Sort(references []string) []string {
|
||||||
|
return reference.Sort(references)
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package distribution
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scope defines the set of items that match a namespace.
|
// Scope defines the set of items that match a namespace.
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
)
|
)
|
||||||
|
|
||||||
type urlBuilderTestCase struct {
|
type urlBuilderTestCase struct {
|
||||||
|
|
|
@ -14,8 +14,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
|
|
|
@ -16,11 +16,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
|
|
|
@ -20,13 +20,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/manifest/manifestlist"
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
|
@ -23,7 +24,6 @@ import (
|
||||||
"github.com/docker/distribution/health/checks"
|
"github.com/docker/distribution/health/checks"
|
||||||
prometheus "github.com/docker/distribution/metrics"
|
prometheus "github.com/docker/distribution/metrics"
|
||||||
"github.com/docker/distribution/notifications"
|
"github.com/docker/distribution/notifications"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/auth"
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/storage"
|
"github.com/docker/distribution/registry/storage"
|
||||||
|
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/manifest/manifestlist"
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/docker/distribution/manifest/ocischema"
|
"github.com/docker/distribution/manifest/ocischema"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
v2 "github.com/docker/distribution/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/auth"
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/proxy/scheduler"
|
"github.com/docker/distribution/registry/proxy/scheduler"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/proxy/scheduler"
|
"github.com/docker/distribution/registry/proxy/scheduler"
|
||||||
"github.com/docker/distribution/registry/storage"
|
"github.com/docker/distribution/registry/storage"
|
||||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/proxy/scheduler"
|
"github.com/docker/distribution/registry/proxy/scheduler"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||||
"github.com/docker/distribution/registry/proxy/scheduler"
|
"github.com/docker/distribution/registry/proxy/scheduler"
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/client"
|
"github.com/docker/distribution/registry/client"
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
"github.com/docker/distribution/registry/storage/driver/testdriver"
|
"github.com/docker/distribution/registry/storage/driver/testdriver"
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
|
|
2
registry/storage/cache/memory/memory.go
vendored
2
registry/storage/cache/memory/memory.go
vendored
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
2
registry/storage/cache/redis/redis.go
vendored
2
registry/storage/cache/redis/redis.go
vendored
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/manifest/manifestlist"
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/docker/distribution/manifest/ocischema"
|
"github.com/docker/distribution/manifest/ocischema"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
dcontext "github.com/docker/distribution/context"
|
dcontext "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ github.com/bugsnag/osext 0dd3f918b21bec95ace9dc86c7e70266cfc5c702
|
||||||
github.com/bugsnag/panicwrap e2c28503fcd0675329da73bf48b33404db873782
|
github.com/bugsnag/panicwrap e2c28503fcd0675329da73bf48b33404db873782
|
||||||
github.com/denverdino/aliyungo afedced274aa9a7fcdd47ac97018f0f8db4e5de2
|
github.com/denverdino/aliyungo afedced274aa9a7fcdd47ac97018f0f8db4e5de2
|
||||||
github.com/dgrijalva/jwt-go 4bbdd8ac624fc7a9ef7aec841c43d99b5fe65a29 https://github.com/golang-jwt/jwt.git # v3.2.2
|
github.com/dgrijalva/jwt-go 4bbdd8ac624fc7a9ef7aec841c43d99b5fe65a29 https://github.com/golang-jwt/jwt.git # v3.2.2
|
||||||
|
github.com/distribution/reference 49c28499d219290c3226822e9cfcd4ede6d75379 # v0.5.0
|
||||||
github.com/docker/go-metrics 399ea8c73916000c64c2c76e8da00ca82f8387ab
|
github.com/docker/go-metrics 399ea8c73916000c64c2c76e8da00ca82f8387ab
|
||||||
github.com/docker/libtrust fa567046d9b14f6aa788882a950d69651d230b21
|
github.com/docker/libtrust fa567046d9b14f6aa788882a950d69651d230b21
|
||||||
github.com/garyburd/redigo 535138d7bcd717d6531c701ef5933d98b1866257
|
github.com/garyburd/redigo 535138d7bcd717d6531c701ef5933d98b1866257
|
||||||
|
|
202
vendor/github.com/distribution/reference/LICENSE
generated
vendored
Normal file
202
vendor/github.com/distribution/reference/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
30
vendor/github.com/distribution/reference/README.md
generated
vendored
Normal file
30
vendor/github.com/distribution/reference/README.md
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Distribution reference
|
||||||
|
|
||||||
|
Go library to handle references to container images.
|
||||||
|
|
||||||
|
<img src="/distribution-logo.svg" width="200px" />
|
||||||
|
|
||||||
|
[![Build Status](https://github.com/distribution/reference/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/distribution/reference/actions?query=workflow%3ACI)
|
||||||
|
[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/reference)
|
||||||
|
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
|
||||||
|
[![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.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
||||||
|
issues, fixes, and patches to this project.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
For async communication and long running discussions please use issues and pull requests on the github repo.
|
||||||
|
This will be the best place to discuss design and implementation.
|
||||||
|
|
||||||
|
For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/)
|
||||||
|
that everyone is welcome to join and chat about development.
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
|
||||||
|
The distribution codebase is released under the [Apache 2.0 license](LICENSE).
|
5
vendor/github.com/distribution/reference/go.mod
generated
vendored
Normal file
5
vendor/github.com/distribution/reference/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/distribution/reference
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/opencontainers/go-digest v1.0.0
|
2
reference/helpers.go → vendor/github.com/distribution/reference/helpers.go
generated
vendored
2
reference/helpers.go → vendor/github.com/distribution/reference/helpers.go
generated
vendored
|
@ -32,7 +32,7 @@ func FamiliarString(ref Reference) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FamiliarMatch reports whether ref matches the specified pattern.
|
// FamiliarMatch reports whether ref matches the specified pattern.
|
||||||
// See https://godoc.org/path#Match for supported patterns.
|
// See [path.Match] for supported patterns.
|
||||||
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
|
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
|
||||||
matched, err := path.Match(pattern, FamiliarString(ref))
|
matched, err := path.Match(pattern, FamiliarString(ref))
|
||||||
if namedRef, isNamed := ref.(Named); isNamed && !matched {
|
if namedRef, isNamed := ref.(Named); isNamed && !matched {
|
131
reference/normalize.go → vendor/github.com/distribution/reference/normalize.go
generated
vendored
131
reference/normalize.go → vendor/github.com/distribution/reference/normalize.go
generated
vendored
|
@ -1,19 +1,42 @@
|
||||||
package reference
|
package reference
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/digestset"
|
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
|
// legacyDefaultDomain is the legacy domain for Docker Hub (which was
|
||||||
|
// originally named "the Docker Index"). This domain is still used for
|
||||||
|
// authentication and image search, which were part of the "v1" Docker
|
||||||
|
// registry specification.
|
||||||
|
//
|
||||||
|
// This domain will continue to be supported, but there are plans to consolidate
|
||||||
|
// legacy domains to new "canonical" domains. Once those domains are decided
|
||||||
|
// on, we must update the normalization functions, but preserve compatibility
|
||||||
|
// with existing installs, clients, and user configuration.
|
||||||
legacyDefaultDomain = "index.docker.io"
|
legacyDefaultDomain = "index.docker.io"
|
||||||
defaultDomain = "docker.io"
|
|
||||||
officialRepoName = "library"
|
// defaultDomain is the default domain used for images on Docker Hub.
|
||||||
defaultTag = "latest"
|
// It is used to normalize "familiar" names to canonical names, for example,
|
||||||
|
// to convert "ubuntu" to "docker.io/library/ubuntu:latest".
|
||||||
|
//
|
||||||
|
// Note that actual domain of Docker Hub's registry is registry-1.docker.io.
|
||||||
|
// This domain will continue to be supported, but there are plans to consolidate
|
||||||
|
// legacy domains to new "canonical" domains. Once those domains are decided
|
||||||
|
// on, we must update the normalization functions, but preserve compatibility
|
||||||
|
// with existing installs, clients, and user configuration.
|
||||||
|
defaultDomain = "docker.io"
|
||||||
|
|
||||||
|
// officialRepoPrefix is the namespace used for official images on Docker Hub.
|
||||||
|
// It is used to normalize "familiar" names to canonical names, for example,
|
||||||
|
// to convert "ubuntu" to "docker.io/library/ubuntu:latest".
|
||||||
|
officialRepoPrefix = "library/"
|
||||||
|
|
||||||
|
// defaultTag is the default tag if no tag is provided.
|
||||||
|
defaultTag = "latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// normalizedNamed represents a name which has been
|
// normalizedNamed represents a name which has been
|
||||||
|
@ -35,14 +58,14 @@ func ParseNormalizedNamed(s string) (Named, error) {
|
||||||
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
|
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
|
||||||
}
|
}
|
||||||
domain, remainder := splitDockerDomain(s)
|
domain, remainder := splitDockerDomain(s)
|
||||||
var remoteName string
|
var remote string
|
||||||
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
|
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
|
||||||
remoteName = remainder[:tagSep]
|
remote = remainder[:tagSep]
|
||||||
} else {
|
} else {
|
||||||
remoteName = remainder
|
remote = remainder
|
||||||
}
|
}
|
||||||
if strings.ToLower(remoteName) != remoteName {
|
if strings.ToLower(remote) != remote {
|
||||||
return nil, errors.New("invalid reference format: repository name must be lowercase")
|
return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
ref, err := Parse(domain + "/" + remainder)
|
ref, err := Parse(domain + "/" + remainder)
|
||||||
|
@ -56,41 +79,53 @@ func ParseNormalizedNamed(s string) (Named, error) {
|
||||||
return named, nil
|
return named, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDockerRef normalizes the image reference following the docker convention. This is added
|
// namedTaggedDigested is a reference that has both a tag and a digest.
|
||||||
// mainly for backward compatibility.
|
type namedTaggedDigested interface {
|
||||||
// The reference returned can only be either tagged or digested. For reference contains both tag
|
NamedTagged
|
||||||
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
|
Digested
|
||||||
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
|
}
|
||||||
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
|
|
||||||
|
// ParseDockerRef normalizes the image reference following the docker convention,
|
||||||
|
// which allows for references to contain both a tag and a digest. It returns a
|
||||||
|
// reference that is either tagged or digested. For references containing both
|
||||||
|
// a tag and a digest, it returns a digested reference. For example, the following
|
||||||
|
// reference:
|
||||||
|
//
|
||||||
|
// docker.io/library/busybox:latest@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
|
||||||
|
//
|
||||||
|
// Is returned as a digested reference (with the ":latest" tag removed):
|
||||||
|
//
|
||||||
|
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
|
||||||
|
//
|
||||||
|
// References that are already "tagged" or "digested" are returned unmodified:
|
||||||
|
//
|
||||||
|
// // Already a digested reference
|
||||||
|
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
|
||||||
|
//
|
||||||
|
// // Already a named reference
|
||||||
|
// docker.io/library/busybox:latest
|
||||||
func ParseDockerRef(ref string) (Named, error) {
|
func ParseDockerRef(ref string) (Named, error) {
|
||||||
named, err := ParseNormalizedNamed(ref)
|
named, err := ParseNormalizedNamed(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, ok := named.(NamedTagged); ok {
|
if canonical, ok := named.(namedTaggedDigested); ok {
|
||||||
if canonical, ok := named.(Canonical); ok {
|
// The reference is both tagged and digested; only return digested.
|
||||||
// The reference is both tagged and digested, only
|
newNamed, err := WithName(canonical.Name())
|
||||||
// return digested.
|
if err != nil {
|
||||||
newNamed, err := WithName(canonical.Name())
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newCanonical, err := WithDigest(newNamed, canonical.Digest())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newCanonical, nil
|
|
||||||
}
|
}
|
||||||
|
return WithDigest(newNamed, canonical.Digest())
|
||||||
}
|
}
|
||||||
return TagNameOnly(named), nil
|
return TagNameOnly(named), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitDockerDomain splits a repository name to domain and remotename string.
|
// splitDockerDomain splits a repository name to domain and remote-name.
|
||||||
// If no valid domain is found, the default domain is used. Repository name
|
// If no valid domain is found, the default domain is used. Repository name
|
||||||
// needs to be already validated before.
|
// needs to be already validated before.
|
||||||
func splitDockerDomain(name string) (domain, remainder string) {
|
func splitDockerDomain(name string) (domain, remainder string) {
|
||||||
i := strings.IndexRune(name, '/')
|
i := strings.IndexRune(name, '/')
|
||||||
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
|
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != localhost && strings.ToLower(name[:i]) == name[:i]) {
|
||||||
domain, remainder = defaultDomain, name
|
domain, remainder = defaultDomain, name
|
||||||
} else {
|
} else {
|
||||||
domain, remainder = name[:i], name[i+1:]
|
domain, remainder = name[:i], name[i+1:]
|
||||||
|
@ -99,13 +134,13 @@ func splitDockerDomain(name string) (domain, remainder string) {
|
||||||
domain = defaultDomain
|
domain = defaultDomain
|
||||||
}
|
}
|
||||||
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
||||||
remainder = officialRepoName + "/" + remainder
|
remainder = officialRepoPrefix + remainder
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// familiarizeName returns a shortened version of the name familiar
|
// familiarizeName returns a shortened version of the name familiar
|
||||||
// to to the Docker UI. Familiar names have the default domain
|
// to the Docker UI. Familiar names have the default domain
|
||||||
// "docker.io" and "library/" repository prefix removed.
|
// "docker.io" and "library/" repository prefix removed.
|
||||||
// For example, "docker.io/library/redis" will have the familiar
|
// For example, "docker.io/library/redis" will have the familiar
|
||||||
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
||||||
|
@ -119,8 +154,15 @@ func familiarizeName(named namedRepository) repository {
|
||||||
if repo.domain == defaultDomain {
|
if repo.domain == defaultDomain {
|
||||||
repo.domain = ""
|
repo.domain = ""
|
||||||
// Handle official repositories which have the pattern "library/<official repo name>"
|
// Handle official repositories which have the pattern "library/<official repo name>"
|
||||||
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
|
if strings.HasPrefix(repo.path, officialRepoPrefix) {
|
||||||
repo.path = split[1]
|
// TODO(thaJeztah): this check may be too strict, as it assumes the
|
||||||
|
// "library/" namespace does not have nested namespaces. While this
|
||||||
|
// is true (currently), technically it would be possible for Docker
|
||||||
|
// Hub to use those (e.g. "library/distros/ubuntu:latest").
|
||||||
|
// See https://github.com/distribution/distribution/pull/3769#issuecomment-1302031785.
|
||||||
|
if remainder := strings.TrimPrefix(repo.path, officialRepoPrefix); !strings.ContainsRune(remainder, '/') {
|
||||||
|
repo.path = remainder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return repo
|
return repo
|
||||||
|
@ -180,20 +222,3 @@ func ParseAnyReference(ref string) (Reference, error) {
|
||||||
|
|
||||||
return ParseNormalizedNamed(ref)
|
return ParseNormalizedNamed(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnyReferenceWithSet parses a reference string as a possible short
|
|
||||||
// identifier to be matched in a digest set, a full digest, or familiar name.
|
|
||||||
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
|
|
||||||
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
|
|
||||||
dgst, err := ds.Lookup(ref)
|
|
||||||
if err == nil {
|
|
||||||
return digestReference(dgst), nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if dgst, err := digest.Parse(ref); err == nil {
|
|
||||||
return digestReference(dgst), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseNormalizedNamed(ref)
|
|
||||||
}
|
|
14
reference/reference.go → vendor/github.com/distribution/reference/reference.go
generated
vendored
14
reference/reference.go → vendor/github.com/distribution/reference/reference.go
generated
vendored
|
@ -4,11 +4,14 @@
|
||||||
// Grammar
|
// Grammar
|
||||||
//
|
//
|
||||||
// reference := name [ ":" tag ] [ "@" digest ]
|
// reference := name [ ":" tag ] [ "@" digest ]
|
||||||
// name := [domain '/'] path-component ['/' path-component]*
|
// name := [domain '/'] remote-name
|
||||||
// domain := domain-component ['.' domain-component]* [':' port-number]
|
// domain := host [':' port-number]
|
||||||
|
// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A
|
||||||
|
// domain-name := domain-component ['.' domain-component]*
|
||||||
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||||
// port-number := /[0-9]+/
|
// port-number := /[0-9]+/
|
||||||
// path-component := alpha-numeric [separator alpha-numeric]*
|
// path-component := alpha-numeric [separator alpha-numeric]*
|
||||||
|
// path (or "remote-name") := path-component ['/' path-component]*
|
||||||
// alpha-numeric := /[a-z0-9]+/
|
// alpha-numeric := /[a-z0-9]+/
|
||||||
// separator := /[_.]|__|[-]*/
|
// separator := /[_.]|__|[-]*/
|
||||||
//
|
//
|
||||||
|
@ -21,7 +24,6 @@
|
||||||
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
|
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
|
||||||
//
|
//
|
||||||
// identifier := /[a-f0-9]{64}/
|
// identifier := /[a-f0-9]{64}/
|
||||||
// short-identifier := /[a-f0-9]{6,64}/
|
|
||||||
package reference
|
package reference
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -145,7 +147,7 @@ type namedRepository interface {
|
||||||
Path() string
|
Path() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domain returns the domain part of the Named reference
|
// Domain returns the domain part of the [Named] reference.
|
||||||
func Domain(named Named) string {
|
func Domain(named Named) string {
|
||||||
if r, ok := named.(namedRepository); ok {
|
if r, ok := named.(namedRepository); ok {
|
||||||
return r.Domain()
|
return r.Domain()
|
||||||
|
@ -154,7 +156,7 @@ func Domain(named Named) string {
|
||||||
return domain
|
return domain
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the name without the domain part of the Named reference
|
// Path returns the name without the domain part of the [Named] reference.
|
||||||
func Path(named Named) (name string) {
|
func Path(named Named) (name string) {
|
||||||
if r, ok := named.(namedRepository); ok {
|
if r, ok := named.(namedRepository); ok {
|
||||||
return r.Path()
|
return r.Path()
|
||||||
|
@ -186,7 +188,6 @@ func SplitHostname(named Named) (string, string) {
|
||||||
|
|
||||||
// Parse parses s and returns a syntactically valid Reference.
|
// Parse parses s and returns a syntactically valid Reference.
|
||||||
// If an error was encountered it is returned, along with a nil Reference.
|
// If an error was encountered it is returned, along with a nil Reference.
|
||||||
// NOTE: Parse will not handle short digests.
|
|
||||||
func Parse(s string) (Reference, error) {
|
func Parse(s string) (Reference, error) {
|
||||||
matches := ReferenceRegexp.FindStringSubmatch(s)
|
matches := ReferenceRegexp.FindStringSubmatch(s)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
|
@ -238,7 +239,6 @@ func Parse(s string) (Reference, error) {
|
||||||
// the Named interface. The reference must have a name and be in the canonical
|
// the Named interface. The reference must have a name and be in the canonical
|
||||||
// form, otherwise an error is returned.
|
// form, otherwise an error is returned.
|
||||||
// If an error was encountered it is returned, along with a nil Reference.
|
// If an error was encountered it is returned, along with a nil Reference.
|
||||||
// NOTE: ParseNamed will not handle short digests.
|
|
||||||
func ParseNamed(s string) (Named, error) {
|
func ParseNamed(s string) (Named, error) {
|
||||||
named, err := ParseNormalizedNamed(s)
|
named, err := ParseNormalizedNamed(s)
|
||||||
if err != nil {
|
if err != nil {
|
163
vendor/github.com/distribution/reference/regexp.go
generated
vendored
Normal file
163
vendor/github.com/distribution/reference/regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
|
||||||
|
var DigestRegexp = regexp.MustCompile(digestPat)
|
||||||
|
|
||||||
|
// DomainRegexp matches hostname or IP-addresses, optionally including a port
|
||||||
|
// number. It defines the structure of potential domain components that may be
|
||||||
|
// part of image names. This is purposely a subset of what is allowed by DNS to
|
||||||
|
// ensure backwards compatibility with Docker image names. It may be a subset of
|
||||||
|
// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between
|
||||||
|
// square brackets (excluding zone identifiers as defined by [RFC 6874] or special
|
||||||
|
// addresses such as IPv4-Mapped).
|
||||||
|
//
|
||||||
|
// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874.
|
||||||
|
var DomainRegexp = regexp.MustCompile(domainAndPort)
|
||||||
|
|
||||||
|
// IdentifierRegexp is the format for string identifier used as a
|
||||||
|
// content addressable identifier using sha256. These identifiers
|
||||||
|
// are like digests without the algorithm, since sha256 is used.
|
||||||
|
var IdentifierRegexp = regexp.MustCompile(identifier)
|
||||||
|
|
||||||
|
// NameRegexp is the format for the name component of references, including
|
||||||
|
// an optional domain and port, but without tag or digest suffix.
|
||||||
|
var NameRegexp = regexp.MustCompile(namePat)
|
||||||
|
|
||||||
|
// ReferenceRegexp is the full supported format of a reference. The regexp
|
||||||
|
// is anchored and has capturing groups for name, tag, and digest
|
||||||
|
// components.
|
||||||
|
var ReferenceRegexp = regexp.MustCompile(referencePat)
|
||||||
|
|
||||||
|
// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
|
||||||
|
//
|
||||||
|
// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28
|
||||||
|
var TagRegexp = regexp.MustCompile(tag)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// alphanumeric defines the alphanumeric atom, typically a
|
||||||
|
// component of names. This only allows lower case characters and digits.
|
||||||
|
alphanumeric = `[a-z0-9]+`
|
||||||
|
|
||||||
|
// separator defines the separators allowed to be embedded in name
|
||||||
|
// components. This allows one period, one or two underscore and multiple
|
||||||
|
// dashes. Repeated dashes and underscores are intentionally treated
|
||||||
|
// differently. In order to support valid hostnames as name components,
|
||||||
|
// supporting repeated dash was added. Additionally double underscore is
|
||||||
|
// now allowed as a separator to loosen the restriction for previously
|
||||||
|
// supported names.
|
||||||
|
separator = `(?:[._]|__|[-]+)`
|
||||||
|
|
||||||
|
// localhost is treated as a special value for domain-name. Any other
|
||||||
|
// domain-name without a "." or a ":port" are considered a path component.
|
||||||
|
localhost = `localhost`
|
||||||
|
|
||||||
|
// domainNameComponent restricts the registry domain component of a
|
||||||
|
// repository name to start with a component as defined by DomainRegexp.
|
||||||
|
domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
|
||||||
|
|
||||||
|
// optionalPort matches an optional port-number including the port separator
|
||||||
|
// (e.g. ":80").
|
||||||
|
optionalPort = `(?::[0-9]+)?`
|
||||||
|
|
||||||
|
// tag matches valid tag names. From docker/docker:graph/tags.go.
|
||||||
|
tag = `[\w][\w.-]{0,127}`
|
||||||
|
|
||||||
|
// digestPat matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
|
||||||
|
//
|
||||||
|
// TODO(thaJeztah): this should follow the same rules as https://pkg.go.dev/github.com/opencontainers/go-digest@v1.0.0#DigestRegexp
|
||||||
|
// so that go-digest defines the canonical format. Note that the go-digest is
|
||||||
|
// more relaxed:
|
||||||
|
// - it allows multiple algorithms (e.g. "sha256+b64:<encoded>") to allow
|
||||||
|
// future expansion of supported algorithms.
|
||||||
|
// - it allows the "<encoded>" value to use urlsafe base64 encoding as defined
|
||||||
|
// in [rfc4648, section 5].
|
||||||
|
//
|
||||||
|
// [rfc4648, section 5]: https://www.rfc-editor.org/rfc/rfc4648#section-5.
|
||||||
|
digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`
|
||||||
|
|
||||||
|
// identifier is the format for a content addressable identifier using sha256.
|
||||||
|
// These identifiers are like digests without the algorithm, since sha256 is used.
|
||||||
|
identifier = `([a-f0-9]{64})`
|
||||||
|
|
||||||
|
// ipv6address are enclosed between square brackets and may be represented
|
||||||
|
// in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format
|
||||||
|
// are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as
|
||||||
|
// IPv4-Mapped are deliberately excluded.
|
||||||
|
ipv6address = `\[(?:[a-fA-F0-9:]+)\]`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// domainName defines the structure of potential domain components
|
||||||
|
// that may be part of image names. This is purposely a subset of what is
|
||||||
|
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||||
|
// names. This includes IPv4 addresses on decimal format.
|
||||||
|
domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent)
|
||||||
|
|
||||||
|
// host defines the structure of potential domains based on the URI
|
||||||
|
// Host subcomponent on rfc3986. It may be a subset of DNS domain name,
|
||||||
|
// or an IPv4 address in decimal format, or an IPv6 address between square
|
||||||
|
// brackets (excluding zone identifiers as defined by rfc6874 or special
|
||||||
|
// addresses such as IPv4-Mapped).
|
||||||
|
host = `(?:` + domainName + `|` + ipv6address + `)`
|
||||||
|
|
||||||
|
// allowed by the URI Host subcomponent on rfc3986 to ensure backwards
|
||||||
|
// compatibility with Docker image names.
|
||||||
|
domainAndPort = host + optionalPort
|
||||||
|
|
||||||
|
// anchoredTagRegexp matches valid tag names, anchored at the start and
|
||||||
|
// end of the matched string.
|
||||||
|
anchoredTagRegexp = regexp.MustCompile(anchored(tag))
|
||||||
|
|
||||||
|
// anchoredDigestRegexp matches valid digests, anchored at the start and
|
||||||
|
// end of the matched string.
|
||||||
|
anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat))
|
||||||
|
|
||||||
|
// pathComponent restricts path-components to start with an alphanumeric
|
||||||
|
// character, with following parts able to be separated by a separator
|
||||||
|
// (one period, one or two underscore and multiple dashes).
|
||||||
|
pathComponent = alphanumeric + anyTimes(separator+alphanumeric)
|
||||||
|
|
||||||
|
// remoteName matches the remote-name of a repository. It consists of one
|
||||||
|
// or more forward slash (/) delimited path-components:
|
||||||
|
//
|
||||||
|
// pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu"
|
||||||
|
remoteName = pathComponent + anyTimes(`/`+pathComponent)
|
||||||
|
namePat = optional(domainAndPort+`/`) + remoteName
|
||||||
|
|
||||||
|
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||||
|
// domain and trailing components.
|
||||||
|
anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName)))
|
||||||
|
|
||||||
|
referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat)))
|
||||||
|
|
||||||
|
// anchoredIdentifierRegexp is used to check or match an
|
||||||
|
// identifier value, anchored at start and end of string.
|
||||||
|
anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier))
|
||||||
|
)
|
||||||
|
|
||||||
|
// optional wraps the expression in a non-capturing group and makes the
|
||||||
|
// production optional.
|
||||||
|
func optional(res ...string) string {
|
||||||
|
return `(?:` + strings.Join(res, "") + `)?`
|
||||||
|
}
|
||||||
|
|
||||||
|
// anyTimes wraps the expression in a non-capturing group that can occur
|
||||||
|
// any number of times.
|
||||||
|
func anyTimes(res ...string) string {
|
||||||
|
return `(?:` + strings.Join(res, "") + `)*`
|
||||||
|
}
|
||||||
|
|
||||||
|
// capture wraps the expression in a capturing group.
|
||||||
|
func capture(res ...string) string {
|
||||||
|
return `(` + strings.Join(res, "") + `)`
|
||||||
|
}
|
||||||
|
|
||||||
|
// anchored anchors the regular expression by adding start and end delimiters.
|
||||||
|
func anchored(res ...string) string {
|
||||||
|
return `^` + strings.Join(res, "") + `$`
|
||||||
|
}
|
75
vendor/github.com/distribution/reference/sort.go
generated
vendored
Normal file
75
vendor/github.com/distribution/reference/sort.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package reference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sort sorts string references preferring higher information references.
|
||||||
|
//
|
||||||
|
// The precedence is as follows:
|
||||||
|
//
|
||||||
|
// 1. [Named] + [Tagged] + [Digested] (e.g., "docker.io/library/busybox:latest@sha256:<digest>")
|
||||||
|
// 2. [Named] + [Tagged] (e.g., "docker.io/library/busybox:latest")
|
||||||
|
// 3. [Named] + [Digested] (e.g., "docker.io/library/busybo@sha256:<digest>")
|
||||||
|
// 4. [Named] (e.g., "docker.io/library/busybox")
|
||||||
|
// 5. [Digested] (e.g., "docker.io@sha256:<digest>")
|
||||||
|
// 6. Parse error
|
||||||
|
func Sort(references []string) []string {
|
||||||
|
var prefs []Reference
|
||||||
|
var bad []string
|
||||||
|
|
||||||
|
for _, ref := range references {
|
||||||
|
pref, err := ParseAnyReference(ref)
|
||||||
|
if err != nil {
|
||||||
|
bad = append(bad, ref)
|
||||||
|
} else {
|
||||||
|
prefs = append(prefs, pref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(prefs, func(a, b int) bool {
|
||||||
|
ar := refRank(prefs[a])
|
||||||
|
br := refRank(prefs[b])
|
||||||
|
if ar == br {
|
||||||
|
return prefs[a].String() < prefs[b].String()
|
||||||
|
}
|
||||||
|
return ar < br
|
||||||
|
})
|
||||||
|
sort.Strings(bad)
|
||||||
|
var refs []string
|
||||||
|
for _, pref := range prefs {
|
||||||
|
refs = append(refs, pref.String())
|
||||||
|
}
|
||||||
|
return append(refs, bad...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refRank(ref Reference) uint8 {
|
||||||
|
if _, ok := ref.(Named); ok {
|
||||||
|
if _, ok = ref.(Tagged); ok {
|
||||||
|
if _, ok = ref.(Digested); ok {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if _, ok = ref.(Digested); ok {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return 5
|
||||||
|
}
|
Loading…
Reference in a new issue