diff --git a/pkg/reference/reference.go b/pkg/reference/reference.go index a4bf6da60..1f3a47063 100644 --- a/pkg/reference/reference.go +++ b/pkg/reference/reference.go @@ -18,7 +18,6 @@ package reference import ( "errors" - "fmt" "net/url" "path" "regexp" @@ -136,8 +135,12 @@ func (r Spec) Hostname() string { // Digest returns the digest portion of the reference spec. This may be a // partial or invalid digest, which may be used to lookup a complete digest. func (r Spec) Digest() digest.Digest { - _, dgst := SplitObject(r.Object) - return dgst + i := strings.Index(r.Object, "@") + + if i < 0 { + return "" + } + return digest.Digest(r.Object[i+1:]) } // String returns the normalized string for the ref. @@ -146,21 +149,29 @@ func (r Spec) String() string { return r.Locator } if r.Object[:1] == "@" { - return fmt.Sprintf("%v%v", r.Locator, r.Object) + return r.Locator + r.Object } - return fmt.Sprintf("%v:%v", r.Locator, r.Object) + return r.Locator + ":" + r.Object } -// SplitObject provides two parts of the object spec, delimited by an `@` -// symbol. +// SplitObject provides two parts of the object spec, delimited by an "@" +// symbol. It does not perform any validation on correctness of the values +// returned, and it's the callers' responsibility to validate the result. // -// Either may be empty and it is the callers job to validate them -// appropriately. +// If an "@" delimiter is found, it returns the part *including* the "@" +// delimiter as "tag", and the part after the "@" as digest. +// +// The example below produces "docker.io/library/ubuntu:latest@" and +// "sha256:deadbeef"; +// +// t, d := SplitObject("docker.io/library/ubuntu:latest@sha256:deadbeef") +// fmt.Println(t) // docker.io/library/ubuntu:latest@ +// fmt.Println(d) // sha256:deadbeef func SplitObject(obj string) (tag string, dgst digest.Digest) { - parts := strings.SplitAfterN(obj, "@", 2) - if len(parts) < 2 { - return parts[0], "" + if i := strings.Index(obj, "@"); i >= 0 { + // Offset by one so preserve the "@" in the tag returned. + return obj[:i+1], digest.Digest(obj[i+1:]) } - return parts[0], digest.Digest(parts[1]) + return obj, "" } diff --git a/pkg/reference/reference_test.go b/pkg/reference/reference_test.go index 91f479ba3..b8929a5c8 100644 --- a/pkg/reference/reference_test.go +++ b/pkg/reference/reference_test.go @@ -192,3 +192,33 @@ func TestReferenceParser(t *testing.T) { }) } } + +func BenchmarkSplitObject(b *testing.B) { + inputs := []string{ + "", + "@", + "docker.io@", + "@digest", + "docker.io/library/redis:foo?fooo=asdf", + "docker.io/library/redis:foo@sha256:abcdef?fooo=asdf", + "docker.io/library/redis@sha256:abcdef?fooo=asdf", + "docker.io/library/redis:obj@abcdef?fooo=asdf", + "localhost:5000/library/redis:obj@abcdef?fooo=asdf", + "/docker.io/library/redis:obj@abcdef?fooo=asdf", + "docker.io/library/redis?fooo=asdf", + "sub-dom1.foo.com/bar/baz/quux:latest", + "sub-dom1.foo.com/bar/baz/quux:some-long-tag", + "b.gcr.io/test.example.com/my-app:test.example.com", + "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode + "xn--7o8h.com/myimage:xn--7o8h.com@sha512:fffffff", + "http://xn--7o8h.com/myimage:xn--7o8h.com@sha512:fffffff", + } + + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for _, input := range inputs { + _, _ = SplitObject(input) + } + } +}