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:
Aaron Lehmann 2015-10-09 17:09:54 -07:00
parent ddfd2335c7
commit 2c0d852000
3 changed files with 122 additions and 1 deletions

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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() + `)$`)