Add WithTag and WithDigest combinator functions
These functions allow a Named type to be combined with a tag or a digest. WithTag will replace the ImageReference function in github.com/docker/docker/utils as the Docker Engine transitions to the reference package. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
3943c4165c
commit
b07d759241
3 changed files with 122 additions and 1 deletions
|
@ -43,6 +43,12 @@ var (
|
||||||
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||||
ErrReferenceInvalidFormat = errors.New("invalid reference format")
|
ErrReferenceInvalidFormat = errors.New("invalid reference format")
|
||||||
|
|
||||||
|
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
|
ErrTagInvalidFormat = errors.New("invalid tag format")
|
||||||
|
|
||||||
|
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
|
ErrDigestInvalidFormat = errors.New("invalid digest format")
|
||||||
|
|
||||||
// ErrNameEmpty is returned for empty, invalid repository names.
|
// ErrNameEmpty is returned for empty, invalid repository names.
|
||||||
ErrNameEmpty = errors.New("repository name must have at least one component")
|
ErrNameEmpty = errors.New("repository name must have at least one component")
|
||||||
|
|
||||||
|
@ -182,6 +188,30 @@ func ParseNamed(name string) (Named, error) {
|
||||||
return repository(name), nil
|
return repository(name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTag combines the name from "name" and the tag from "tag" to form a
|
||||||
|
// reference incorporating both the name and the tag.
|
||||||
|
func WithTag(name Named, tag string) (Tagged, error) {
|
||||||
|
if !anchoredNameRegexp.MatchString(tag) {
|
||||||
|
return nil, ErrTagInvalidFormat
|
||||||
|
}
|
||||||
|
return taggedReference{
|
||||||
|
name: name.Name(),
|
||||||
|
tag: tag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDigest combines the name from "name" and the digest from "digest" to form
|
||||||
|
// a reference incorporating both the name and the digest.
|
||||||
|
func WithDigest(name Named, digest digest.Digest) (Digested, error) {
|
||||||
|
if !anchoredDigestRegexp.MatchString(digest.String()) {
|
||||||
|
return nil, ErrDigestInvalidFormat
|
||||||
|
}
|
||||||
|
return canonicalReference{
|
||||||
|
name: name.Name(),
|
||||||
|
digest: digest,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getBestReferenceType(ref reference) Reference {
|
func getBestReferenceType(ref reference) Reference {
|
||||||
if ref.name == "" {
|
if ref.name == "" {
|
||||||
// Allow digest only references
|
// Allow digest only references
|
||||||
|
|
|
@ -395,3 +395,87 @@ func TestSerialization(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithTag(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
failf := func(format string, v ...interface{}) {
|
||||||
|
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
named, err := ParseNamed(testcase.name)
|
||||||
|
if err != nil {
|
||||||
|
failf("error parsing name: %s", err)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
failf := func(format string, v ...interface{}) {
|
||||||
|
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
named, err := ParseNamed(testcase.name)
|
||||||
|
if err != nil {
|
||||||
|
failf("error parsing name: %s", err)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,13 @@ var (
|
||||||
// end of the matched string.
|
// end of the matched string.
|
||||||
anchoredTagRegexp = regexp.MustCompile(`^` + TagRegexp.String() + `$`)
|
anchoredTagRegexp = regexp.MustCompile(`^` + TagRegexp.String() + `$`)
|
||||||
|
|
||||||
|
// DigestRegexp matches valid digests.
|
||||||
|
DigestRegexp = regexp.MustCompile(`[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 = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
|
||||||
|
|
||||||
// NameRegexp is the format for the name component of references. The
|
// NameRegexp is the format for the name component of references. The
|
||||||
// regexp has capturing groups for the hostname and name part omitting
|
// regexp has capturing groups for the hostname and name part omitting
|
||||||
// the seperating forward slash from either.
|
// the seperating forward slash from either.
|
||||||
|
@ -35,7 +42,7 @@ var (
|
||||||
|
|
||||||
// ReferenceRegexp is the full supported format of a reference. The
|
// ReferenceRegexp is the full supported format of a reference. The
|
||||||
// regexp has capturing groups for name, tag, and digest components.
|
// regexp has capturing groups for name, tag, and digest components.
|
||||||
ReferenceRegexp = regexp.MustCompile(`^((?:` + hostnameRegexp.String() + `/)?` + nameRegexp.String() + `)(?:[:](` + TagRegexp.String() + `))?(?:[@]([A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}))?$`)
|
ReferenceRegexp = regexp.MustCompile(`^((?:` + hostnameRegexp.String() + `/)?` + nameRegexp.String() + `)(?:[:](` + TagRegexp.String() + `))?(?:[@](` + DigestRegexp.String() + `))?$`)
|
||||||
|
|
||||||
// anchoredNameRegexp is used to parse a name value, capturing hostname
|
// anchoredNameRegexp is used to parse a name value, capturing hostname
|
||||||
anchoredNameRegexp = regexp.MustCompile(`^(?:(` + hostnameRegexp.String() + `)/)?(` + nameRegexp.String() + `)$`)
|
anchoredNameRegexp = regexp.MustCompile(`^(?:(` + hostnameRegexp.String() + `)/)?(` + nameRegexp.String() + `)$`)
|
||||||
|
|
Loading…
Reference in a new issue