2015-07-10 20:36:04 +02:00
package reference
2015-09-09 01:00:48 +02:00
import (
2016-12-16 07:38:38 +01:00
_ "crypto/sha256"
_ "crypto/sha512"
2015-10-01 00:36:19 +02:00
"encoding/json"
2015-09-09 01:00:48 +02:00
"strconv"
"strings"
"testing"
2015-07-10 20:36:04 +02:00
2016-12-17 01:28:34 +01:00
"github.com/opencontainers/go-digest"
2015-09-09 01:00:48 +02:00
)
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
2016-06-09 20:32:23 +02:00
// domain is the domain expected in the reference
domain string
2015-09-09 01:00:48 +02:00
// 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" ,
2016-06-09 20:32:23 +02:00
domain : "test.com" ,
2015-09-09 01:00:48 +02:00
repository : "test.com/repo" ,
tag : "tag" ,
} ,
{
input : "test:5000/repo" ,
2016-06-09 20:32:23 +02:00
domain : "test:5000" ,
2015-09-09 01:00:48 +02:00
repository : "test:5000/repo" ,
} ,
{
input : "test:5000/repo:tag" ,
2016-06-09 20:32:23 +02:00
domain : "test:5000" ,
2015-09-09 01:00:48 +02:00
repository : "test:5000/repo" ,
tag : "tag" ,
} ,
{
input : "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
2016-06-09 20:32:23 +02:00
domain : "test:5000" ,
2015-09-09 01:00:48 +02:00
repository : "test:5000/repo" ,
digest : "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
} ,
{
input : "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
2016-06-09 20:32:23 +02:00
domain : "test:5000" ,
2015-09-09 01:00:48 +02:00
repository : "test:5000/repo" ,
tag : "tag" ,
digest : "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
} ,
{
input : "test:5000/repo" ,
2016-06-09 20:32:23 +02:00
domain : "test:5000" ,
2015-09-09 01:00:48 +02:00
repository : "test:5000/repo" ,
} ,
{
input : "" ,
err : ErrNameEmpty ,
} ,
{
input : ":justtag" ,
err : ErrReferenceInvalidFormat ,
} ,
{
input : "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
err : ErrReferenceInvalidFormat ,
} ,
2015-12-03 00:57:47 +01:00
{
input : "repo@sha256:ffffffffffffffffffffffffffffffffff" ,
err : digest . ErrDigestInvalidLength ,
} ,
2015-09-09 01:00:48 +02:00
{
input : "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
err : digest . ErrDigestUnsupported ,
} ,
2016-08-11 15:30:17 +02:00
{
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" ,
} ,
2015-09-09 01:00:48 +02:00
{
input : strings . Repeat ( "a/" , 128 ) + "a:tag" ,
err : ErrNameTooLong ,
} ,
{
input : strings . Repeat ( "a/" , 127 ) + "a:tag-puts-this-over-max" ,
2016-06-09 20:32:23 +02:00
domain : "a" ,
2015-09-09 01:00:48 +02:00
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" ,
2016-06-09 20:32:23 +02:00
domain : "sub-dom1.foo.com" ,
2015-09-09 01:00:48 +02:00
repository : "sub-dom1.foo.com/bar/baz/quux" ,
} ,
{
input : "sub-dom1.foo.com/bar/baz/quux:some-long-tag" ,
2016-06-09 20:32:23 +02:00
domain : "sub-dom1.foo.com" ,
2015-09-09 01:00:48 +02:00
repository : "sub-dom1.foo.com/bar/baz/quux" ,
tag : "some-long-tag" ,
} ,
{
input : "b.gcr.io/test.example.com/my-app:test.example.com" ,
2016-06-09 20:32:23 +02:00
domain : "b.gcr.io" ,
2015-09-09 01:00:48 +02:00
repository : "b.gcr.io/test.example.com/my-app" ,
tag : "test.example.com" ,
} ,
{
input : "xn--n3h.com/myimage:xn--n3h.com" , // ☃.com in punycode
2016-06-09 20:32:23 +02:00
domain : "xn--n3h.com" ,
2015-09-09 01:00:48 +02:00
repository : "xn--n3h.com/myimage" ,
tag : "xn--n3h.com" ,
} ,
{
2015-12-03 00:57:47 +01:00
input : "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" , // 🐳.com in punycode
2016-06-09 20:32:23 +02:00
domain : "xn--7o8h.com" ,
2015-09-09 01:00:48 +02:00
repository : "xn--7o8h.com/myimage" ,
tag : "xn--7o8h.com" ,
2015-12-03 00:57:47 +01:00
digest : "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ,
2015-09-09 01:00:48 +02:00
} ,
{
input : "foo_bar.com:8080" ,
repository : "foo_bar.com" ,
tag : "8080" ,
} ,
{
input : "foo/foo_bar.com:8080" ,
2016-06-09 20:32:23 +02:00
domain : "foo" ,
2015-09-09 01:00:48 +02:00
repository : "foo/foo_bar.com" ,
tag : "8080" ,
} ,
2015-07-10 20:36:04 +02:00
}
2015-09-09 01:00:48 +02:00
for _ , testcase := range referenceTestcases {
failf := func ( format string , v ... interface { } ) {
t . Logf ( strconv . Quote ( testcase . input ) + ": " + format , v ... )
t . Fail ( )
}
2015-07-10 20:36:04 +02:00
2015-09-09 01:00:48 +02:00
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 )
2015-07-10 20:36:04 +02:00
}
2015-09-09 01:00:48 +02:00
continue
} else if err != nil {
failf ( "unexpected parse error: %v" , err )
continue
2015-07-10 20:36:04 +02:00
}
2015-09-09 01:00:48 +02:00
if repo . String ( ) != testcase . input {
failf ( "mismatched repo: got %q, expected %q" , repo . String ( ) , testcase . input )
2015-07-10 20:36:04 +02:00
}
2015-09-09 01:00:48 +02:00
if named , ok := repo . ( Named ) ; ok {
if named . Name ( ) != testcase . repository {
failf ( "unexpected repository: got %q, expected %q" , named . Name ( ) , testcase . repository )
}
2016-06-09 20:32:23 +02:00
domain , _ := SplitHostname ( named )
if domain != testcase . domain {
failf ( "unexpected domain: got %q, expected %q" , domain , testcase . domain )
2015-09-09 01:00:48 +02:00
}
2016-06-09 20:32:23 +02:00
} else if testcase . repository != "" || testcase . domain != "" {
2015-09-09 01:00:48 +02:00
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" )
2015-07-10 20:36:04 +02:00
}
}
}
2015-11-02 18:12:21 +01:00
// 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 )
}
}
}
2015-09-09 01:00:48 +02:00
func TestSplitHostname ( t * testing . T ) {
testcases := [ ] struct {
2016-06-09 20:32:23 +02:00
input string
domain string
name string
2015-09-09 01:00:48 +02:00
} {
{
2016-06-09 20:32:23 +02:00
input : "test.com/foo" ,
domain : "test.com" ,
name : "foo" ,
2015-09-09 01:00:48 +02:00
} ,
{
2016-06-09 20:32:23 +02:00
input : "test_com/foo" ,
domain : "" ,
name : "test_com/foo" ,
2015-09-09 01:00:48 +02:00
} ,
{
2016-06-09 20:32:23 +02:00
input : "test:8080/foo" ,
domain : "test:8080" ,
name : "foo" ,
2015-09-09 01:00:48 +02:00
} ,
{
2016-06-09 20:32:23 +02:00
input : "test.com:8080/foo" ,
domain : "test.com:8080" ,
name : "foo" ,
2015-09-09 01:00:48 +02:00
} ,
{
2016-06-09 20:32:23 +02:00
input : "test-com:8080/foo" ,
domain : "test-com:8080" ,
name : "foo" ,
2015-09-09 01:00:48 +02:00
} ,
{
2016-06-09 20:32:23 +02:00
input : "xn--n3h.com:18080/foo" ,
domain : "xn--n3h.com:18080" ,
name : "foo" ,
2015-09-09 01:00:48 +02:00
} ,
2015-07-10 20:36:04 +02:00
}
2015-09-09 01:00:48 +02:00
for _ , testcase := range testcases {
failf := func ( format string , v ... interface { } ) {
t . Logf ( strconv . Quote ( testcase . input ) + ": " + format , v ... )
t . Fail ( )
}
2015-07-10 20:36:04 +02:00
2015-10-14 00:42:04 +02:00
named , err := WithName ( testcase . input )
2015-09-09 01:00:48 +02:00
if err != nil {
failf ( "error parsing name: %s" , err )
}
2016-06-09 20:32:23 +02:00
domain , name := SplitHostname ( named )
if domain != testcase . domain {
failf ( "unexpected domain: got %q, expected %q" , domain , testcase . domain )
2015-09-09 01:00:48 +02:00
}
if name != testcase . name {
failf ( "unexpected name: got %q, expected %q" , name , testcase . name )
}
2015-07-10 20:36:04 +02:00
}
}
2015-10-01 00:36:19 +02:00
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" ,
2015-12-03 00:57:47 +01:00
input : "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112" ,
2015-10-01 00:36:19 +02:00
name : "other.com/named" ,
2015-12-03 00:57:47 +01:00
digest : "sha256:1234567890098765432112345667890098765432112345667890098765432112" ,
2015-10-01 00:36:19 +02:00
} ,
}
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" )
}
}
}
2015-10-10 02:09:54 +02:00
func TestWithTag ( t * testing . T ) {
testcases := [ ] struct {
name string
2016-11-08 23:47:33 +01:00
digest digest . Digest
2015-10-10 02:09:54 +02:00
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" ,
} ,
2015-10-29 02:22:00 +01:00
{
name : "test.com:8000/foo" ,
tag : "TAG5" ,
combined : "test.com:8000/foo:TAG5" ,
} ,
2016-11-08 23:47:33 +01:00
{
name : "test.com:8000/foo" ,
digest : "sha256:1234567890098765432112345667890098765" ,
tag : "TAG5" ,
combined : "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765" ,
} ,
2015-10-10 02:09:54 +02:00
}
for _ , testcase := range testcases {
failf := func ( format string , v ... interface { } ) {
t . Logf ( strconv . Quote ( testcase . name ) + ": " + format , v ... )
t . Fail ( )
}
2015-10-14 00:42:04 +02:00
named , err := WithName ( testcase . name )
2015-10-10 02:09:54 +02:00
if err != nil {
failf ( "error parsing name: %s" , err )
}
2016-11-08 23:47:33 +01:00
if testcase . digest != "" {
canonical , err := WithDigest ( named , testcase . digest )
if err != nil {
failf ( "error adding digest" )
}
named = canonical
}
2015-10-10 02:09:54 +02:00
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
2016-11-08 23:47:33 +01:00
tag string
2015-10-10 02:09:54 +02:00
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" ,
} ,
2016-11-08 23:47:33 +01:00
{
name : "test.com:8000/foo" ,
digest : "sha256:1234567890098765432112345667890098765" ,
tag : "latest" ,
combined : "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765" ,
} ,
2015-10-10 02:09:54 +02:00
}
for _ , testcase := range testcases {
failf := func ( format string , v ... interface { } ) {
t . Logf ( strconv . Quote ( testcase . name ) + ": " + format , v ... )
t . Fail ( )
}
2015-10-14 00:42:04 +02:00
named , err := WithName ( testcase . name )
2015-10-10 02:09:54 +02:00
if err != nil {
failf ( "error parsing name: %s" , err )
}
2016-11-08 23:47:33 +01:00
if testcase . tag != "" {
tagged , err := WithTag ( named , testcase . tag )
if err != nil {
failf ( "error adding tag" )
}
named = tagged
}
2015-10-10 02:09:54 +02:00
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 )
}
}
}
2017-01-14 01:19:24 +01:00
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 succeded: 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 )
}
}
}