package docker import ( "context" "strings" "testing" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" sha256digest = "@sha256:" + sha256digestHex unknownDigestSuffixTest = "@@unknown-digest@@" ) func TestTransportName(t *testing.T) { assert.Equal(t, "docker", Transport.Name()) } func TestTransportParseReference(t *testing.T) { testParseReference(t, Transport.ParseReference) } func TestTransportValidatePolicyConfigurationScope(t *testing.T) { for _, scope := range []string{ "docker.io/library/busybox" + sha256digest, "docker.io/library/busybox:notlatest", "docker.io/library/busybox", "docker.io/library", "docker.io", "*.io", } { err := Transport.ValidatePolicyConfigurationScope(scope) assert.NoError(t, err, scope) } } func TestParseReference(t *testing.T) { testParseReference(t, ParseReference) } // testParseReference is a test shared for Transport.ParseReference and ParseReference. func testParseReference(t *testing.T, fn func(string) (types.ImageReference, error)) { for _, c := range []struct { input, expected string expectedUnknownDigest bool }{ {"busybox", "", false}, // Missing // prefix {"//busybox:notlatest", "docker.io/library/busybox:notlatest", false}, // Explicit tag {"//busybox" + sha256digest, "docker.io/library/busybox" + sha256digest, false}, // Explicit digest {"//busybox", "docker.io/library/busybox:latest", false}, // Default tag // A github.com/distribution/reference value can have a tag and a digest at the same time! // The docker/distribution API does not really support that (we can’t ask for an image with a specific // tag and digest), so fail. This MAY be accepted in the future. {"//busybox:latest" + sha256digest, "", false}, // Both tag and digest {"//docker.io/library/busybox:latest", "docker.io/library/busybox:latest", false}, // All implied values explicitly specified {"//UPPERCASEISINVALID", "", false}, // Invalid input {"//busybox" + unknownDigestSuffixTest, "docker.io/library/busybox", true}, // UnknownDigest suffix {"//example.com/ns/busybox" + unknownDigestSuffixTest, "example.com/ns/busybox", true}, // UnknownDigest with registry/repo {"//example.com/ns/busybox:tag1" + unknownDigestSuffixTest, "", false}, // UnknownDigest with tag should fail {"//example.com/ns/busybox" + sha256digest + unknownDigestSuffixTest, "", false}, // UnknownDigest with digest should fail } { ref, err := fn(c.input) if c.expected == "" { assert.Error(t, err, c.input) } else { require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) assert.Equal(t, c.expected, dockerRef.ref.String(), c.input) assert.Equal(t, c.expectedUnknownDigest, dockerRef.isUnknownDigest) } } } // A common list of reference formats to test for the various ImageReference methods. var validReferenceTestCases = []struct { input, dockerRef, stringWithinTransport string expectedUnknownDigest bool }{ {"busybox:notlatest", "docker.io/library/busybox:notlatest", "//busybox:notlatest", false}, // Explicit tag {"busybox" + sha256digest, "docker.io/library/busybox" + sha256digest, "//busybox" + sha256digest, false}, // Explicit digest {"docker.io/library/busybox:latest", "docker.io/library/busybox:latest", "//busybox:latest", false}, // All implied values explicitly specified {"example.com/ns/foo:bar", "example.com/ns/foo:bar", "//example.com/ns/foo:bar", false}, // All values explicitly specified {"example.com/ns/busybox" + unknownDigestSuffixTest, "example.com/ns/busybox", "//example.com/ns/busybox" + unknownDigestSuffixTest, true}, // UnknownDigest Suffix full name {"busybox" + unknownDigestSuffixTest, "docker.io/library/busybox", "//busybox" + unknownDigestSuffixTest, true}, // UnknownDigest short name } func TestNewReference(t *testing.T) { for _, c := range validReferenceTestCases { if strings.HasSuffix(c.input, unknownDigestSuffixTest) { continue } parsed, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) ref, err := NewReference(parsed) require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) assert.Equal(t, c.dockerRef, dockerRef.ref.String(), c.input) assert.Equal(t, false, dockerRef.isUnknownDigest) } // Neither a tag nor digest parsed, err := reference.ParseNormalizedNamed("busybox") require.NoError(t, err) _, err = NewReference(parsed) assert.Error(t, err) // A github.com/distribution/reference value can have a tag and a digest at the same time! parsed, err = reference.ParseNormalizedNamed("busybox:notlatest" + sha256digest) require.NoError(t, err) _, ok := parsed.(reference.Canonical) require.True(t, ok) _, ok = parsed.(reference.NamedTagged) require.True(t, ok) _, err = NewReference(parsed) assert.Error(t, err) } func TestNewReferenceUnknownDigest(t *testing.T) { // References with tags and digests should be rejected for _, c := range validReferenceTestCases { in, ok := strings.CutSuffix(c.input, unknownDigestSuffixTest) if !ok { parsed, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) _, err = NewReferenceUnknownDigest(parsed) assert.Error(t, err) continue } parsed, err := reference.ParseNormalizedNamed(in) require.NoError(t, err) ref, err := NewReferenceUnknownDigest(parsed) require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) assert.Equal(t, c.dockerRef, dockerRef.ref.String(), c.input) assert.Equal(t, true, dockerRef.isUnknownDigest) } } func TestReferenceTransport(t *testing.T) { ref, err := ParseReference("//busybox") require.NoError(t, err) assert.Equal(t, Transport, ref.Transport()) } func TestReferenceStringWithinTransport(t *testing.T) { for _, c := range validReferenceTestCases { ref, err := ParseReference("//" + c.input) require.NoError(t, err, c.input) stringRef := ref.StringWithinTransport() assert.Equal(t, c.stringWithinTransport, stringRef, c.input) // Do one more round to verify that the output can be parsed, to an equal value. ref2, err := Transport.ParseReference(stringRef) require.NoError(t, err, c.input) stringRef2 := ref2.StringWithinTransport() assert.Equal(t, stringRef, stringRef2, c.input) } } func TestReferenceDockerReference(t *testing.T) { for _, c := range validReferenceTestCases { ref, err := ParseReference("//" + c.input) require.NoError(t, err, c.input) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef, c.input) assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) } } func TestReferencePolicyConfigurationIdentity(t *testing.T) { // Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference. ref, err := ParseReference("//busybox") require.NoError(t, err) assert.Equal(t, "docker.io/library/busybox:latest", ref.PolicyConfigurationIdentity()) ref, err = ParseReference("//busybox" + unknownDigestSuffixTest) require.NoError(t, err) assert.Equal(t, "docker.io/library/busybox", ref.PolicyConfigurationIdentity()) } func TestReferencePolicyConfigurationNamespaces(t *testing.T) { // Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference. ref, err := ParseReference("//busybox") require.NoError(t, err) assert.Equal(t, []string{ "docker.io/library/busybox", "docker.io/library", "docker.io", "*.io", }, ref.PolicyConfigurationNamespaces()) ref, err = ParseReference("//busybox" + unknownDigestSuffixTest) require.NoError(t, err) assert.Equal(t, []string{ "docker.io/library", "docker.io", "*.io", }, ref.PolicyConfigurationNamespaces()) } func TestReferenceNewImage(t *testing.T) { sysCtx := &types.SystemContext{ RegistriesDirPath: "/this/does/not/exist", DockerPerHostCertDirPath: "/this/does/not/exist", ArchitectureChoice: "amd64", OSChoice: "linux", } ref, err := ParseReference("//quay.io/libpod/busybox") require.NoError(t, err) img, err := ref.NewImage(context.Background(), sysCtx) require.NoError(t, err) defer img.Close() // unknownDigest case should return error ref, err = ParseReference("//quay.io/libpod/busybox" + unknownDigestSuffixTest) require.NoError(t, err) _, err = ref.NewImage(context.Background(), sysCtx) assert.Error(t, err) } func TestReferenceNewImageSource(t *testing.T) { sysCtx := &types.SystemContext{ RegistriesDirPath: "/this/does/not/exist", DockerPerHostCertDirPath: "/this/does/not/exist", } ref, err := ParseReference("//quay.io/libpod/busybox") require.NoError(t, err) src, err := ref.NewImageSource(context.Background(), sysCtx) require.NoError(t, err) defer src.Close() // unknownDigest case should return error ref, err = ParseReference("//quay.io/libpod/busybox" + unknownDigestSuffixTest) require.NoError(t, err) _, err = ref.NewImageSource(context.Background(), sysCtx) assert.Error(t, err) } func TestReferenceNewImageDestination(t *testing.T) { ref, err := ParseReference("//quay.io/libpod/busybox") require.NoError(t, err) dest, err := ref.NewImageDestination(context.Background(), &types.SystemContext{RegistriesDirPath: "/this/does/not/exist", DockerPerHostCertDirPath: "/this/does/not/exist"}) require.NoError(t, err) defer dest.Close() ref, err = ParseReference("//quay.io/libpod/busybox" + unknownDigestSuffixTest) require.NoError(t, err) dest2, err := ref.NewImageDestination(context.Background(), &types.SystemContext{RegistriesDirPath: "/this/does/not/exist", DockerPerHostCertDirPath: "/this/does/not/exist"}) require.NoError(t, err) defer dest2.Close() } func TestReferenceTagOrDigest(t *testing.T) { for input, expected := range map[string]string{ "//busybox:notlatest": "notlatest", "//busybox" + sha256digest: "sha256:" + sha256digestHex, } { ref, err := ParseReference(input) require.NoError(t, err, input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, input) tod, err := dockerRef.tagOrDigest() require.NoError(t, err, input) assert.Equal(t, expected, tod, input) } // Invalid input ref, err := reference.ParseNormalizedNamed("busybox") require.NoError(t, err) dockerRef := dockerReference{ref: ref} _, err = dockerRef.tagOrDigest() assert.Error(t, err) // Invalid input, unknownDigest case ref, err = reference.ParseNormalizedNamed("busybox") require.NoError(t, err) dockerRef = dockerReference{ref: ref, isUnknownDigest: true} _, err = dockerRef.tagOrDigest() assert.Error(t, err) }