package api
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/diff"
)
func TestParseImageStreamImageName(t *testing.T) {
tests := map[string]struct {
input string
expectedRepo string
expectedId string
expectError bool
}{
"empty string": {
input: "",
expectError: true,
},
"one part": {
input: "a",
expectError: true,
},
"more than 2 parts": {
input: "a@b@c",
expectError: true,
},
"empty name part": {
input: "@id",
expectError: true,
},
"empty id part": {
input: "name@",
expectError: true,
},
"valid input": {
input: "repo@id",
expectedRepo: "repo",
expectedId: "id",
expectError: false,
},
}
for name, test := range tests {
repo, id, err := ParseImageStreamImageName(test.input)
didError := err != nil
if e, a := test.expectError, didError; e != a {
t.Errorf("%s: expected error=%t, got=%t: %s", name, e, a, err)
continue
}
if test.expectError {
continue
}
if e, a := test.expectedRepo, repo; e != a {
t.Errorf("%s: repo: expected %q, got %q", name, e, a)
continue
}
if e, a := test.expectedId, id; e != a {
t.Errorf("%s: id: expected %q, got %q", name, e, a)
continue
}
}
}
func TestParseImageStreamTagName(t *testing.T) {
tests := map[string]struct {
id string
expectedName string
expectedTag string
expectError bool
}{
"empty id": {
id: "",
expectError: true,
},
"missing semicolon": {
id: "hello",
expectError: true,
},
"too many semicolons": {
id: "a:b:c",
expectError: true,
},
"empty name": {
id: ":tag",
expectError: true,
},
"empty tag": {
id: "name",
expectError: true,
},
"happy path": {
id: "name:tag",
expectError: false,
expectedName: "name",
expectedTag: "tag",
},
}
for description, testCase := range tests {
name, tag, err := ParseImageStreamTagName(testCase.id)
gotError := err != nil
if e, a := testCase.expectError, gotError; e != a {
t.Fatalf("%s: expected err: %t, got: %t: %s", description, e, a, err)
}
if err != nil {
continue
}
if e, a := testCase.expectedName, name; e != a {
t.Errorf("%s: name: expected %q, got %q", description, e, a)
}
if e, a := testCase.expectedTag, tag; e != a {
t.Errorf("%s: tag: expected %q, got %q", description, e, a)
}
}
}
func TestDockerImageReferenceAsRepository(t *testing.T) {
testCases := []struct {
Registry, Namespace, Name, Tag, ID string
Expected string
}{
{
Namespace: "bar",
Name: "foo",
Tag: "tag",
Expected: "bar/foo",
},
{
Namespace: "bar",
Name: "foo",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "bar/foo",
},
{
Registry: "bar",
Namespace: "foo",
Name: "baz",
Expected: "bar/foo/baz",
},
}
for i, testCase := range testCases {
ref := DockerImageReference{
Registry: testCase.Registry,
Namespace: testCase.Namespace,
Name: testCase.Name,
Tag: testCase.Tag,
ID: testCase.ID,
}
actual := ref.AsRepository().String()
if e, a := testCase.Expected, actual; e != a {
t.Errorf("%d: expected %q, got %q", i, e, a)
}
}
}
func TestDockerImageReferenceDaemonMinimal(t *testing.T) {
testCases := []struct {
Registry, Namespace, Name, Tag, ID string
Expected string
}{
{
Namespace: "library",
Name: "foo",
Tag: "tag",
Expected: "library/foo:tag",
},
{
Namespace: "bar",
Name: "foo",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "bar/foo@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
{
Registry: "bar",
Namespace: "foo",
Name: "baz",
Expected: "bar/foo/baz",
},
{
Registry: "localhost:5000",
Namespace: "library",
Name: "bar",
Tag: "latest",
Expected: "localhost:5000/library/bar",
},
{
Registry: "index.docker.io",
Namespace: "foo",
Name: "bar",
Tag: "latest",
Expected: "docker.io/foo/bar",
},
{
Registry: "registry-1.docker.io",
Namespace: "library",
Name: "foo",
Tag: "bar",
Expected: "docker.io/foo:bar",
},
{
Registry: "docker.io",
Namespace: "foo",
Name: "library",
Expected: "docker.io/foo/library",
},
{
Registry: "registry-1.docker.io",
Name: "library",
Tag: "foo",
Expected: "docker.io/library:foo",
},
}
for i, testCase := range testCases {
ref := DockerImageReference{
Registry: testCase.Registry,
Namespace: testCase.Namespace,
Name: testCase.Name,
Tag: testCase.Tag,
ID: testCase.ID,
}
actual := ref.DaemonMinimal().Exact()
if e, a := testCase.Expected, actual; e != a {
t.Errorf("%d: expected %q, got %q", i, e, a)
}
}
}
func TestDockerImageReferenceString(t *testing.T) {
testCases := []struct {
Registry, Namespace, Name, Tag, ID string
Expected string
}{
{
Name: "foo",
Expected: "foo",
},
{
Name: "foo",
Tag: "tag",
Expected: "foo:tag",
},
{
Name: "foo",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "foo@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
{
Name: "foo",
ID: "3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "foo:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
{
Namespace: "bar",
Name: "foo",
Expected: "bar/foo",
},
{
Namespace: "bar",
Name: "foo",
Tag: "tag",
Expected: "bar/foo:tag",
},
{
Namespace: "bar",
Name: "foo",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "bar/foo@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
{
Registry: "bar",
Namespace: "foo",
Name: "baz",
Expected: "bar/foo/baz",
},
{
Registry: "bar",
Namespace: "foo",
Name: "baz",
Tag: "tag",
Expected: "bar/foo/baz:tag",
},
{
Registry: "bar",
Namespace: "foo",
Name: "baz",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "bar/foo/baz@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
{
Registry: "bar:5000",
Namespace: "foo",
Name: "baz",
Expected: "bar:5000/foo/baz",
},
{
Registry: "bar:5000",
Namespace: "foo",
Name: "baz",
Tag: "tag",
Expected: "bar:5000/foo/baz:tag",
},
{
Registry: "bar:5000",
Namespace: "library",
Name: "baz",
Tag: "tag",
Expected: "bar:5000/library/baz:tag",
},
{
Registry: "bar:5000",
Namespace: "foo",
Name: "baz",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "bar:5000/foo/baz@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
{
Registry: "docker.io",
Namespace: "user",
Name: "app",
Expected: "docker.io/user/app",
},
{
Registry: "index.docker.io",
Name: "foo",
Expected: "index.docker.io/library/foo",
},
{
Registry: "index.docker.io",
Namespace: "library",
Name: "bar",
ID: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Expected: "index.docker.io/library/bar@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
}
for i, testCase := range testCases {
ref := DockerImageReference{
Registry: testCase.Registry,
Namespace: testCase.Namespace,
Name: testCase.Name,
Tag: testCase.Tag,
ID: testCase.ID,
}
actual := ref.String()
if e, a := testCase.Expected, actual; e != a {
t.Errorf("%d: expected %q, got %q", i, e, a)
}
}
}
func validImageWithManifestData() Image {
return Image{
ObjectMeta: kapi.ObjectMeta{
Name: "id",
},
DockerImageManifest: `{
"name": "library/ubuntu",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"blobSum": "tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0"
},
{
"blobSum": "tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171"
},
{
"blobSum": "tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd"
},
{
"blobSum": "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"2d24f826cb16146e2016ff349a8a33ed5830f3b938d45c0f82943f4ab8c097e7\",\"parent\":\"117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c\",\"created\":\"2015-02-21T02:11:06.735146646Z\",\"container\":\"c9a3eda5951d28aa8dbe5933be94c523790721e4f80886d0a8e7a710132a38ec\",\"container_config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/bin/bash]\"],\"Image\":\"117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"architecture\":\"amd64\",\"os\":\"linux\",\"checksum\":\"tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c\",\"parent\":\"1c8294cc516082dfbb731f062806b76b82679ce38864dd87635f08869c993e45\",\"created\":\"2015-02-21T02:11:02.473113442Z\",\"container\":\"b4d4c42c196081cdb8e032446e9db92c0ce8ddeeeb1ef4e582b0275ad62b9af2\",\"container_config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"sed -i 's/^#\\\\s*\\\\(deb.*universe\\\\)$/\\\\1/g' /etc/apt/sources.list\"],\"Image\":\"1c8294cc516082dfbb731f062806b76b82679ce38864dd87635f08869c993e45\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":null,\"Image\":\"1c8294cc516082dfbb731f062806b76b82679ce38864dd87635f08869c993e45\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"architecture\":\"amd64\",\"os\":\"linux\",\"checksum\":\"tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0\",\"Size\":1895}\n"
},
{
"v1Compatibility": "{\"id\":\"1c8294cc516082dfbb731f062806b76b82679ce38864dd87635f08869c993e45\",\"parent\":\"fa4fd76b09ce9b87bfdc96515f9a5dd5121c01cc996cf5379050d8e13d4a864b\",\"created\":\"2015-02-21T02:10:56.648624065Z\",\"container\":\"d6323d4f92bf13dd2a7d2957e8970c8dc8ba3c2df08ffebdf4a04b4b658c83fb\",\"container_config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"echo '#!/bin/sh' \\u003e /usr/sbin/policy-rc.d \\u0009\\u0026\\u0026 echo 'exit 101' \\u003e\\u003e /usr/sbin/policy-rc.d \\u0009\\u0026\\u0026 chmod +x /usr/sbin/policy-rc.d \\u0009\\u0009\\u0026\\u0026 dpkg-divert --local --rename --add /sbin/initctl \\u0009\\u0026\\u0026 cp -a /usr/sbin/policy-rc.d /sbin/initctl \\u0009\\u0026\\u0026 sed -i 's/^exit.*/exit 0/' /sbin/initctl \\u0009\\u0009\\u0026\\u0026 echo 'force-unsafe-io' \\u003e /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \\u0009\\u0009\\u0026\\u0026 echo 'DPkg::Post-Invoke { \\\"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\\\"; };' \\u003e /etc/apt/apt.conf.d/docker-clean \\u0009\\u0026\\u0026 echo 'APT::Update::Post-Invoke { \\\"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\\\"; };' \\u003e\\u003e /etc/apt/apt.conf.d/docker-clean \\u0009\\u0026\\u0026 echo 'Dir::Cache::pkgcache \\\"\\\"; Dir::Cache::srcpkgcache \\\"\\\";' \\u003e\\u003e /etc/apt/apt.conf.d/docker-clean \\u0009\\u0009\\u0026\\u0026 echo 'Acquire::Languages \\\"none\\\";' \\u003e /etc/apt/apt.conf.d/docker-no-languages \\u0009\\u0009\\u0026\\u0026 echo 'Acquire::GzipIndexes \\\"true\\\"; Acquire::CompressionTypes::Order:: \\\"gz\\\";' \\u003e /etc/apt/apt.conf.d/docker-gzip-indexes\"],\"Image\":\"fa4fd76b09ce9b87bfdc96515f9a5dd5121c01cc996cf5379050d8e13d4a864b\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":null,\"Image\":\"fa4fd76b09ce9b87bfdc96515f9a5dd5121c01cc996cf5379050d8e13d4a864b\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"architecture\":\"amd64\",\"os\":\"linux\",\"checksum\":\"tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171\",\"Size\":194533}\n"
},
{
"v1Compatibility": "{\"id\":\"fa4fd76b09ce9b87bfdc96515f9a5dd5121c01cc996cf5379050d8e13d4a864b\",\"parent\":\"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158\",\"created\":\"2015-02-21T02:10:48.627646094Z\",\"container\":\"43bd710ec89aa1d61685c5ddb8737b315f2152d04c9e127ea2a9cf79f950fa5d\",\"container_config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ADD file:0018ff77d038472f52512fd66ae2466e6325e5d91289e10a6b324f40582298df in /\"],\"Image\":\"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"43bd710ec89a\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":null,\"Image\":\"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[]},\"architecture\":\"amd64\",\"os\":\"linux\",\"checksum\":\"tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd\",\"Size\":188097705}\n"
},
{
"v1Compatibility": "{\"id\":\"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158\",\"comment\":\"Imported from -\",\"created\":\"2013-06-13T14:03:50.821769-07:00\",\"container_config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null},\"docker_version\":\"0.4.0\",\"architecture\":\"x86_64\",\"checksum\":\"tarsum.dev+sha256:324d4cf44ee7daa46266c1df830c61a7df615c0632176a339e7310e34723d67a\",\"Size\":0}\n"
}
],
"schemaVersion": 1,
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
"kty": "EC",
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
},
"alg": "ES256"
},
"signature": "JafVC022gLJbK8fEp9UrW-GWAi53nD4Xf0-fmb766nV6zGTq7g9RutSWHcpv3OqhV8t5b-4j-bXhvnGBjXA7Yg",
"protected": "eyJmb3JtYXRMZW5ndGgiOjEwMDc2LCJmb3JtYXRUYWlsIjoiQ24wIiwidGltZSI6IjIwMTUtMDMtMDdUMTI6Mzk6MjJaIn0"
}
]
}`,
}
}
func validImageWithManifestV2Data() Image {
return Image{
ObjectMeta: kapi.ObjectMeta{
Name: "id",
},
DockerImageConfig: `{
"architecture": "amd64",
"config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/bin/sh",
"-c",
"echo hi"
],
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"derived=true",
"asdf=true"
],
"Hostname": "23304fc829f9",
"Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
"Labels": {},
"OnBuild": [],
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": null,
"WorkingDir": ""
},
"container": "e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001",
"container_config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/bin/sh",
"-c",
"#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"
],
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"derived=true",
"asdf=true"
],
"Hostname": "23304fc829f9",
"Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
"Labels": {},
"OnBuild": [],
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": null,
"WorkingDir": ""
},
"created": "2015-02-21T02:11:06.735146646Z",
"docker_version": "1.9.0-dev",
"history": [
{
"created": "2015-10-31T22:22:54.690851953Z",
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
},
{
"created": "2015-10-31T22:22:55.613815829Z",
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
},
{
"created": "2015-11-04T23:06:30.934316144Z",
"created_by": "/bin/sh -c #(nop) ENV derived=true",
"empty_layer": true
},
{
"created": "2015-11-04T23:06:31.192097572Z",
"created_by": "/bin/sh -c #(nop) ENV asdf=true",
"empty_layer": true
},
{
"created": "2015-11-04T23:06:32.083868454Z",
"created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"
},
{
"created": "2015-11-04T23:06:32.365666163Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"diff_ids": [
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
"sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49"
],
"type": "layers"
}
}`,
DockerImageManifest: `{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:815d06b56f4138afacd0009b8e3799fcdce79f0507bf8d0588e219b93ab6fd4d"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 5312,
"digest": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 235231,
"digest": "sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 235231,
"digest": "sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 639152,
"digest": "sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
}
]
}`,
}
}
func TestImageWithMetadata(t *testing.T) {
tests := map[string]struct {
image Image
expectedImage Image
expectError bool
}{
"no manifest data": {
image: Image{},
expectedImage: Image{},
},
"error unmarshalling manifest data": {
image: Image{
DockerImageManifest: "{ no {{{ json here!!!",
},
expectedImage: Image{},
expectError: true,
},
"no history": {
image: Image{
DockerImageManifest: `{"name": "library/ubuntu", "tag": "latest"}`,
},
expectedImage: Image{
DockerImageManifest: `{"name": "library/ubuntu", "tag": "latest"}`,
},
},
"error unmarshalling v1 compat": {
image: Image{
DockerImageManifest: "{\"name\": \"library/ubuntu\", \"tag\": \"latest\", \"history\": [\"v1Compatibility\": \"{ not valid {{ json\" }",
},
expectError: true,
},
"happy path": {
image: validImageWithManifestData(),
expectedImage: Image{
ObjectMeta: kapi.ObjectMeta{
Name: "id",
},
DockerImageManifest: validImageWithManifestData().DockerImageManifest,
DockerImageLayers: []ImageLayer{
{Name: "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", LayerSize: 0},
{Name: "tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd", MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", LayerSize: 188097705},
{Name: "tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171", MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", LayerSize: 194533},
{Name: "tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0", MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", LayerSize: 1895},
{Name: "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", LayerSize: 0},
},
DockerImageManifestMediaType: "application/vnd.docker.distribution.manifest.v1+json",
DockerImageMetadata: DockerImage{
ID: "2d24f826cb16146e2016ff349a8a33ed5830f3b938d45c0f82943f4ab8c097e7",
Parent: "117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c",
Comment: "",
Created: unversioned.Date(2015, 2, 21, 2, 11, 6, 735146646, time.UTC),
Container: "c9a3eda5951d28aa8dbe5933be94c523790721e4f80886d0a8e7a710132a38ec",
ContainerConfig: DockerConfig{
Hostname: "43bd710ec89a",
Domainname: "",
User: "",
Memory: 0,
MemorySwap: 0,
CPUShares: 0,
CPUSet: "",
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
PortSpecs: nil,
ExposedPorts: nil,
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
Cmd: []string{"/bin/sh", "-c", "#(nop) CMD [/bin/bash]"},
Image: "117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c",
Volumes: nil,
WorkingDir: "",
Entrypoint: nil,
NetworkDisabled: false,
SecurityOpts: nil,
OnBuild: []string{},
},
DockerVersion: "1.4.1",
Author: "",
Config: &DockerConfig{
Hostname: "43bd710ec89a",
Domainname: "",
User: "",
Memory: 0,
MemorySwap: 0,
CPUShares: 0,
CPUSet: "",
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
PortSpecs: nil,
ExposedPorts: nil,
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
Cmd: []string{"/bin/bash"},
Image: "117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c",
Volumes: nil,
WorkingDir: "",
Entrypoint: nil,
NetworkDisabled: false,
OnBuild: []string{},
},
Architecture: "amd64",
Size: 188294133,
},
},
},
"valid metadata size": {
image: validImageWithManifestV2Data(),
expectedImage: Image{
ObjectMeta: kapi.ObjectMeta{
Name: "id",
},
DockerImageConfig: validImageWithManifestV2Data().DockerImageConfig,
DockerImageManifest: validImageWithManifestV2Data().DockerImageManifest,
DockerImageManifestMediaType: "application/vnd.docker.distribution.manifest.v2+json",
DockerImageLayers: []ImageLayer{
{Name: "sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", LayerSize: 639152},
{Name: "sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", LayerSize: 235231},
{Name: "sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", LayerSize: 235231},
{Name: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", LayerSize: 5312},
},
DockerImageMetadata: DockerImage{
ID: "sha256:815d06b56f4138afacd0009b8e3799fcdce79f0507bf8d0588e219b93ab6fd4d",
Parent: "",
Comment: "",
Created: unversioned.Date(2015, 2, 21, 2, 11, 6, 735146646, time.UTC),
Container: "e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001",
DockerVersion: "1.9.0-dev",
Author: "",
Architecture: "amd64",
Size: 882848,
ContainerConfig: DockerConfig{
Hostname: "23304fc829f9",
Domainname: "",
User: "",
Memory: 0,
MemorySwap: 0,
CPUShares: 0,
CPUSet: "",
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
PortSpecs: nil,
ExposedPorts: nil,
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "derived=true", "asdf=true"},
Cmd: []string{"/bin/sh", "-c", "#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"},
Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
Volumes: nil,
WorkingDir: "",
Entrypoint: nil,
NetworkDisabled: false,
SecurityOpts: nil,
OnBuild: []string{},
},
Config: &DockerConfig{
Hostname: "23304fc829f9",
Domainname: "",
User: "",
Memory: 0,
MemorySwap: 0,
CPUShares: 0,
CPUSet: "",
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
PortSpecs: nil,
ExposedPorts: nil,
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "derived=true", "asdf=true"},
Cmd: []string{"/bin/sh", "-c", "echo hi"},
Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
Volumes: nil,
WorkingDir: "",
Entrypoint: nil,
NetworkDisabled: false,
OnBuild: []string{},
},
},
},
},
}
for name, test := range tests {
imageWithMetadata := test.image
err := ImageWithMetadata(&imageWithMetadata)
gotError := err != nil
if e, a := test.expectError, gotError; e != a {
t.Fatalf("%s: expectError=%t, gotError=%t: %s", name, e, a, err)
}
if test.expectError {
continue
}
if e, a := test.expectedImage, imageWithMetadata; !kapi.Semantic.DeepEqual(e, a) {
t.Errorf("%s: image: %s", name, diff.ObjectDiff(e, a))
}
}
}
func TestLatestTaggedImage(t *testing.T) {
tests := []struct {
tag string
tags map[string]TagEventList
expected string
expectNotFound bool
}{
{
tag: "foo",
tags: map[string]TagEventList{},
expectNotFound: true,
},
{
tag: "foo",
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
},
expectNotFound: true,
},
{
tag: "",
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
},
expected: "latest-ref",
},
{
tag: "foo",
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
"foo": {
Items: []TagEvent{
{DockerImageReference: "foo-ref"},
{DockerImageReference: "older"},
},
},
},
expected: "foo-ref",
},
}
for i, test := range tests {
stream := &ImageStream{}
stream.Status.Tags = test.tags
actual := LatestTaggedImage(stream, test.tag)
if actual == nil {
if !test.expectNotFound {
t.Errorf("%d: unexpected nil result", i)
}
continue
}
if e, a := test.expected, actual.DockerImageReference; e != a {
t.Errorf("%d: expected %q, got %q", i, e, a)
}
}
}
func TestResolveLatestTaggedImage(t *testing.T) {
tests := []struct {
tag string
statusRef string
refs map[string]TagReference
tags map[string]TagEventList
expected string
expectNotFound bool
}{
{
tag: "foo",
tags: map[string]TagEventList{},
expectNotFound: true,
},
{
tag: "foo",
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
},
expectNotFound: true,
},
{
tag: "",
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
},
expected: "latest-ref",
},
{
tag: "foo",
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
"foo": {
Items: []TagEvent{
{DockerImageReference: "foo-ref"},
{DockerImageReference: "older"},
},
},
},
expected: "foo-ref",
},
// the default reference policy does nothing
{
refs: map[string]TagReference{
"latest": {
ReferencePolicy: TagReferencePolicy{Type: SourceTagReferencePolicy},
},
},
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref", Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246"},
{DockerImageReference: "older"},
},
},
},
expected: "latest-ref",
},
// the local reference policy does nothing unless reference is set
{
refs: map[string]TagReference{
"latest": {
ReferencePolicy: TagReferencePolicy{Type: LocalTagReferencePolicy},
},
},
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref", Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246"},
{DockerImageReference: "older"},
},
},
},
expected: "latest-ref",
},
// the local reference policy does nothing unless the image id is set
{
statusRef: "test.server/a/b",
refs: map[string]TagReference{
"latest": {
ReferencePolicy: TagReferencePolicy{Type: LocalTagReferencePolicy},
},
},
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref"},
{DockerImageReference: "older"},
},
},
},
expected: "latest-ref",
},
// the local reference policy uses the output status reference and the image id
// and returns a pullthrough spec
{
statusRef: "test.server/a/b",
refs: map[string]TagReference{
"latest": {
ReferencePolicy: TagReferencePolicy{Type: LocalTagReferencePolicy},
},
},
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{DockerImageReference: "latest-ref", Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246"},
{DockerImageReference: "older"},
},
},
},
expected: "test.server/a/b@sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
},
}
for i, test := range tests {
stream := &ImageStream{}
stream.Status.DockerImageRepository = test.statusRef
stream.Status.Tags = test.tags
stream.Spec.Tags = test.refs
actual, ok := ResolveLatestTaggedImage(stream, test.tag)
if !ok {
if !test.expectNotFound {
t.Errorf("%d: unexpected nil result", i)
}
continue
}
if e, a := test.expected, actual; e != a {
t.Errorf("%d: expected %q, got %q", i, e, a)
}
}
}
func TestAddTagEventToImageStream(t *testing.T) {
tests := map[string]struct {
tags map[string]TagEventList
nextRef string
nextImage string
expectedTags map[string]TagEventList
expectedUpdate bool
}{
"nil entry for tag": {
tags: map[string]TagEventList{},
nextRef: "ref",
nextImage: "image",
expectedTags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
expectedUpdate: true,
},
"empty items for tag": {
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{},
},
},
nextRef: "ref",
nextImage: "image",
expectedTags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
expectedUpdate: true,
},
"same ref and image": {
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
nextRef: "ref",
nextImage: "image",
expectedTags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
expectedUpdate: false,
},
"same ref, different image": {
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
nextRef: "ref",
nextImage: "newimage",
expectedTags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "newimage",
},
},
},
},
expectedUpdate: true,
},
"different ref, same image": {
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
nextRef: "newref",
nextImage: "image",
expectedTags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "newref",
Image: "image",
},
},
},
},
expectedUpdate: true,
},
"different ref, different image": {
tags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
nextRef: "newref",
nextImage: "newimage",
expectedTags: map[string]TagEventList{
"latest": {
Items: []TagEvent{
{
DockerImageReference: "newref",
Image: "newimage",
},
{
DockerImageReference: "ref",
Image: "image",
},
},
},
},
expectedUpdate: true,
},
}
for name, test := range tests {
stream := &ImageStream{}
stream.Status.Tags = test.tags
updated := AddTagEventToImageStream(stream, "latest", TagEvent{DockerImageReference: test.nextRef, Image: test.nextImage})
if e, a := test.expectedUpdate, updated; e != a {
t.Errorf("%s: expected updated=%t, got %t", name, e, a)
}
if e, a := test.expectedTags, stream.Status.Tags; !reflect.DeepEqual(e, a) {
t.Errorf("%s: expected\ntags=%#v\ngot=%#v", name, e, a)
}
}
}
func TestUpdateTrackingTags(t *testing.T) {
tests := map[string]struct {
fromNil bool
fromKind string
fromNamespace string
fromName string
trackingTags []string
nonTrackingTags []string
statusTags []string
updatedImageReference string
updatedImage string
expectedUpdates []string
}{
"nil from": {
fromNil: true,
},
"from kind not ImageStreamTag": {
fromKind: "ImageStreamImage",
},
"from namespace different": {
fromNamespace: "other",
},
"from name different": {
trackingTags: []string{"otherstream:2.0"},
},
"no tracking": {
trackingTags: []string{},
statusTags: []string{"2.0", "3.0"},
},
"stream name in from name": {
trackingTags: []string{"latest"},
fromName: "ruby:2.0",
statusTags: []string{"2.0", "3.0"},
expectedUpdates: []string{"latest"},
},
"1 tracking, 1 not": {
trackingTags: []string{"latest"},
nonTrackingTags: []string{"other"},
statusTags: []string{"2.0", "3.0"},
expectedUpdates: []string{"latest"},
},
"multiple tracking, multiple not": {
trackingTags: []string{"latest1", "latest2"},
nonTrackingTags: []string{"other1", "other2"},
statusTags: []string{"2.0", "3.0"},
expectedUpdates: []string{"latest1", "latest2"},
},
"no change to tracked tag": {
trackingTags: []string{"latest"},
statusTags: []string{"2.0", "3.0"},
updatedImageReference: "ns/ruby@id",
updatedImage: "id",
},
}
for name, test := range tests {
stream := &ImageStream{
ObjectMeta: kapi.ObjectMeta{
Namespace: "ns",
Name: "ruby",
},
Spec: ImageStreamSpec{
Tags: map[string]TagReference{},
},
Status: ImageStreamStatus{
Tags: map[string]TagEventList{},
},
}
if len(test.fromNamespace) > 0 {
stream.Namespace = test.fromNamespace
}
fromName := test.fromName
if len(fromName) == 0 {
fromName = "2.0"
}
for _, tag := range test.trackingTags {
stream.Spec.Tags[tag] = TagReference{
From: &kapi.ObjectReference{
Kind: "ImageStreamTag",
Name: fromName,
},
}
}
for _, tag := range test.nonTrackingTags {
stream.Spec.Tags[tag] = TagReference{}
}
for _, tag := range test.statusTags {
stream.Status.Tags[tag] = TagEventList{
Items: []TagEvent{
{
DockerImageReference: "ns/ruby@id",
Image: "id",
},
},
}
}
if test.fromNil {
stream.Spec.Tags = map[string]TagReference{
"latest": {},
}
}
if len(test.fromKind) > 0 {
stream.Spec.Tags = map[string]TagReference{
"latest": {
From: &kapi.ObjectReference{
Kind: test.fromKind,
Name: "asdf",
},
},
}
}
updatedImageReference := test.updatedImageReference
if len(updatedImageReference) == 0 {
updatedImageReference = "ns/ruby@newid"
}
updatedImage := test.updatedImage
if len(updatedImage) == 0 {
updatedImage = "newid"
}
newTagEvent := TagEvent{
DockerImageReference: updatedImageReference,
Image: updatedImage,
}
UpdateTrackingTags(stream, "2.0", newTagEvent)
for _, tag := range test.expectedUpdates {
tagEventList, ok := stream.Status.Tags[tag]
if !ok {
t.Errorf("%s: expected update for tag %q", name, tag)
continue
}
if e, a := updatedImageReference, tagEventList.Items[0].DockerImageReference; e != a {
t.Errorf("%s: dockerImageReference: expected %q, got %q", name, e, a)
}
if e, a := updatedImage, tagEventList.Items[0].Image; e != a {
t.Errorf("%s: image: expected %q, got %q", name, e, a)
}
}
}
}
func TestJoinImageStreamTag(t *testing.T) {
if e, a := "foo:bar", JoinImageStreamTag("foo", "bar"); e != a {
t.Errorf("Unexpected value: %s", a)
}
if e, a := "foo:"+DefaultImageTag, JoinImageStreamTag("foo", ""); e != a {
t.Errorf("Unexpected value: %s", a)
}
}
func TestResolveImageID(t *testing.T) {
tests := map[string]struct {
tags map[string]TagEventList
imageID string
expErr string
expEvent TagEvent
}{
"single tag, match ID prefix": {
tags: map[string]TagEventList{
"tag1": {
Items: []TagEvent{
{
DockerImageReference: "repo@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Image: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
},
},
},
imageID: "3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
expErr: "",
expEvent: TagEvent{
DockerImageReference: "repo@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
Image: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
},
},
"single tag, match string prefix": {
tags: map[string]TagEventList{
"tag1": {
Items: []TagEvent{
{
DockerImageReference: "repo:mytag",
Image: "mytag",
},
},
},
},
imageID: "mytag",
expErr: "",
expEvent: TagEvent{
DockerImageReference: "repo:mytag",
Image: "mytag",
},
},
"single tag, ID error": {
tags: map[string]TagEventList{
"tag1": {
Items: []TagEvent{
{
DockerImageReference: "repo@sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b2",
Image: "sha256:3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b2",
},
},
},
},
imageID: "3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
expErr: "not found",
expEvent: TagEvent{},
},
"no tag": {
tags: map[string]TagEventList{},
imageID: "3c87c572822935df60f0f5d3665bd376841a7fcfeb806b5f212de6a00e9a7b25",
expErr: "not found",
expEvent: TagEvent{},
},
"multiple match": {
tags: map[string]TagEventList{
"tag1": {
Items: []TagEvent{
{
DockerImageReference: "repo@mytag",
Image: "mytag",
},
{
DockerImageReference: "repo@mytag",
Image: "mytag2",
},
},
},
},
imageID: "mytag",
expErr: "multiple images match the prefix",
expEvent: TagEvent{},
},
"find match out of multiple tags in first position": {
tags: map[string]TagEventList{
"tag1": {
Items: []TagEvent{
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000001",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000001",
},
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000002",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000002",
},
},
},
"tag2": {
Items: []TagEvent{
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000003",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000003",
},
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000004",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000004",
},
},
},
},
imageID: "sha256:0000000000000000000000000000000000000000000000000000000000000001",
expEvent: TagEvent{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000001",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000001",
},
},
"find match out of multiple tags in last position": {
tags: map[string]TagEventList{
"tag1": {
Items: []TagEvent{
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000001",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000001",
},
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000002",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000002",
},
},
},
"tag2": {
Items: []TagEvent{
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000003",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000003",
},
{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000004",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000004",
},
},
},
},
imageID: "sha256:0000000000000000000000000000000000000000000000000000000000000004",
expEvent: TagEvent{
DockerImageReference: "repo@sha256:0000000000000000000000000000000000000000000000000000000000000004",
Image: "sha256:0000000000000000000000000000000000000000000000000000000000000004",
},
},
}
for name, test := range tests {
stream := &ImageStream{}
stream.Status.Tags = test.tags
event, err := ResolveImageID(stream, test.imageID)
if len(test.expErr) > 0 {
if err == nil || !strings.Contains(err.Error(), test.expErr) {
t.Errorf("%s: unexpected error, expected %v, got %v", name, test.expErr, err)
}
continue
} else if err != nil {
t.Errorf("%s: unexpected error, got %v", name, err)
continue
}
if test.expEvent.Image != event.Image || test.expEvent.DockerImageReference != event.DockerImageReference {
t.Errorf("%s: unexpected tag, expected %#v, got %#v", name, test.expEvent, event)
}
}
}
func TestDockerImageReferenceEquality(t *testing.T) {
equalityTests := []struct {
a, b DockerImageReference
isEqual bool
}{
{
a: DockerImageReference{},
b: DockerImageReference{},
isEqual: true,
},
{
a: DockerImageReference{
Name: "openshift",
},
b: DockerImageReference{
Name: "openshift",
},
isEqual: true,
},
{
a: DockerImageReference{
Name: "openshift",
},
b: DockerImageReference{
Name: "openshift3",
},
isEqual: false,
},
{
a: DockerImageReference{
Name: "openshift",
},
b: DockerImageReference{
Registry: DockerDefaultRegistry,
Namespace: DockerDefaultNamespace,
Name: "openshift",
Tag: DefaultImageTag,
},
isEqual: true,
},
{
a: DockerImageReference{
Name: "openshift",
},
b: DockerImageReference{
Registry: DockerDefaultRegistry,
Namespace: DockerDefaultNamespace,
Name: "openshift",
Tag: "v1.0",
},
isEqual: false,
},
{
a: DockerImageReference{
Name: "openshift",
},
b: DockerImageReference{
Registry: DockerDefaultRegistry,
Namespace: DockerDefaultNamespace,
Name: "openshift",
Tag: DefaultImageTag,
ID: "d0a28ab59a",
},
isEqual: false,
},
}
for i, test := range equalityTests {
if isEqual := test.a.Equal(test.b); isEqual != test.isEqual {
t.Errorf("test %d: %#v.Equal(%#v) = %t; want %t",
i, test.a, test.b, isEqual, test.isEqual)
}
// commutativeness sanity check
if x, y := test.a.Equal(test.b), test.b.Equal(test.a); x != y {
t.Errorf("test %[1]d: %[2]q.Equal(%[3]q) = %[4]t != %[3]q.Equal(%[2]q) = %[5]t",
i, test.a, test.b, x, y)
}
}
}
func TestPrioritizeTags(t *testing.T) {
tests := []struct {
tags []string
expected []string
}{
{
tags: []string{"other", "latest", "v5.5", "5.2.3", "v5.3.6-bother", "5.3.6-abba", "5.6"},
expected: []string{"latest", "5.6", "v5.5", "v5.3.6-bother", "5.3.6-abba", "5.2.3", "other"},
},
{
tags: []string{"1.1-beta1", "1.2-rc1", "1.1-rc1", "1.1-beta2", "1.2-beta1", "1.2-alpha1", "1.2-beta4", "latest"},
expected: []string{"latest", "1.2-rc1", "1.2-beta4", "1.2-beta1", "1.2-alpha1", "1.1-rc1", "1.1-beta2", "1.1-beta1"},
},
}
for i, tc := range tests {
PrioritizeTags(tc.tags)
if !reflect.DeepEqual(tc.tags, tc.expected) {
t.Errorf("%d: unexpected order: %v", i, tc.tags)
}
}
}
func TestTagsChanged(t *testing.T) {
tests := map[string]struct {
new []TagEvent
old []TagEvent
changed bool
deleted bool
}{
"both empty": {
new: []TagEvent{},
old: []TagEvent{},
changed: false,
deleted: false,
},
"new image": {
new: []TagEvent{{Image: "newimage"}},
old: []TagEvent{},
changed: true,
deleted: false,
},
"image deleted": {
new: []TagEvent{},
old: []TagEvent{{Image: "oldimage"}},
changed: true,
deleted: true,
},
"image changed": {
new: []TagEvent{{Image: "newimage"}},
old: []TagEvent{{Image: "oldImage"}},
changed: true,
deleted: false,
},
}
for name, test := range tests {
changed, deleted := tagsChanged(test.new, test.old)
if changed != test.changed || deleted != test.deleted {
t.Errorf("%s: unexpected tagsChanged, expected (%v, %v) got (%v, %v)",
name, test.changed, test.deleted, changed, deleted)
}
}
}
func TestIndexOfImageSignature(t *testing.T) {
for _, tc := range []struct {
name string
signatures []ImageSignature
matchType string
matchContent []byte
expectedIndex int
}{
{
name: "empty",
matchType: ImageSignatureTypeAtomicImageV1,
matchContent: []byte("blob"),
expectedIndex: -1,
},
{
name: "not present",
signatures: []ImageSignature{
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("binary"),
},
{
Type: "custom",
Content: []byte("blob"),
},
},
matchType: ImageSignatureTypeAtomicImageV1,
matchContent: []byte("blob"),
expectedIndex: -1,
},
{
name: "first and only",
signatures: []ImageSignature{
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("binary"),
},
},
matchType: ImageSignatureTypeAtomicImageV1,
matchContent: []byte("binary"),
expectedIndex: 0,
},
{
name: "last",
signatures: []ImageSignature{
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("binary"),
},
{
Type: "custom",
Content: []byte("blob"),
},
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("blob"),
},
},
matchType: ImageSignatureTypeAtomicImageV1,
matchContent: []byte("blob"),
expectedIndex: 2,
},
{
name: "many matches",
signatures: []ImageSignature{
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("blob2"),
},
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("blob"),
},
{
Type: "custom",
Content: []byte("blob"),
},
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("blob"),
},
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("blob"),
},
{
Type: ImageSignatureTypeAtomicImageV1,
Content: []byte("binary"),
},
},
matchType: ImageSignatureTypeAtomicImageV1,
matchContent: []byte("blob"),
expectedIndex: 1,
},
} {
im := Image{
Signatures: make([]ImageSignature, len(tc.signatures)),
}
for i, signature := range tc.signatures {
signature.Name = fmt.Sprintf("%s:%s", signature.Type, signature.Content)
im.Signatures[i] = signature
}
matchName := fmt.Sprintf("%s:%s", tc.matchType, tc.matchContent)
index := IndexOfImageSignatureByName(im.Signatures, matchName)
if index != tc.expectedIndex {
t.Errorf("[%s] got unexpected index: %d != %d", tc.name, index, tc.expectedIndex)
}
index = IndexOfImageSignature(im.Signatures, tc.matchType, tc.matchContent)
if index != tc.expectedIndex {
t.Errorf("[%s] got unexpected index: %d != %d", tc.name, index, tc.expectedIndex)
}
}
}