diff --git a/pkg/cmd/admin/migrate/images/images.go b/pkg/cmd/admin/migrate/images/images.go index 1e09a4754b6b..e0681064a5f9 100644 --- a/pkg/cmd/admin/migrate/images/images.go +++ b/pkg/cmd/admin/migrate/images/images.go @@ -193,6 +193,9 @@ func (o *MigrateImageReferenceOptions) transform(obj runtime.Object) (migrate.Re return reporter(changed), nil case *imageapi.ImageStream: var info imageChangeInfo + if len(t.Spec.DockerImageRepository) > 0 { + info.spec = updateString(&t.Spec.DockerImageRepository, fn) + } for _, ref := range t.Spec.Tags { if ref.From == nil || ref.From.Kind != "DockerImage" { continue @@ -387,7 +390,7 @@ func ParseMapping(s string) (ImageReferenceMapping, error) { if to[0] == "" && to[1] == "" { return ImageReferenceMapping{}, fmt.Errorf("%q is not a valid target: at least one change must be specified", parts[1]) } - if from[0] == from[1] && to[0] == to[1] { + if from[0] == to[0] && from[1] == to[1] { return ImageReferenceMapping{}, fmt.Errorf("%q is not valid: must target at least one field to change", s) } return ImageReferenceMapping{ diff --git a/pkg/cmd/admin/migrate/images/images_test.go b/pkg/cmd/admin/migrate/images/images_test.go index 9381e2126439..9984a9638888 100644 --- a/pkg/cmd/admin/migrate/images/images_test.go +++ b/pkg/cmd/admin/migrate/images/images_test.go @@ -2,6 +2,15 @@ package images import ( "testing" + + kapi "k8s.io/kubernetes/pkg/api" + kextensions "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/diff" + + buildapi "github.com/openshift/origin/pkg/build/api" + deployapi "github.com/openshift/origin/pkg/deploy/api" + imageapi "github.com/openshift/origin/pkg/image/api" ) func TestImageReferenceMappingsMapReference(t *testing.T) { @@ -127,3 +136,461 @@ func TestImageReferenceMappingsMapDockerAuthKey(t *testing.T) { } } } + +func TestTransform(t *testing.T) { + type variant struct { + changed bool + nilReporter bool + err bool + obj, expected runtime.Object + } + testCases := []struct { + mappings ImageReferenceMappings + variants []variant + }{ + { + mappings: ImageReferenceMappings{{FromRegistry: "docker.io", ToRegistry: "index.docker.io"}}, + variants: []variant{ + { + obj: &kapi.Pod{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + changed: true, + expected: &kapi.Pod{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + { + obj: &kapi.ReplicationController{ + Spec: kapi.ReplicationControllerSpec{ + Template: &kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + }, + }, + changed: true, + expected: &kapi.ReplicationController{ + Spec: kapi.ReplicationControllerSpec{ + Template: &kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + }, + }, + { + obj: &kextensions.Deployment{ + Spec: kextensions.DeploymentSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + }, + }, + changed: true, + expected: &kextensions.Deployment{ + Spec: kextensions.DeploymentSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + }, + }, + { + obj: &deployapi.DeploymentConfig{ + Spec: deployapi.DeploymentConfigSpec{ + Template: &kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + }, + }, + changed: true, + expected: &deployapi.DeploymentConfig{ + Spec: deployapi.DeploymentConfigSpec{ + Template: &kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + }, + }, + { + obj: &kextensions.DaemonSet{ + Spec: kextensions.DaemonSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + }, + }, + changed: true, + expected: &kextensions.DaemonSet{ + Spec: kextensions.DaemonSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + }, + }, + { + obj: &kextensions.ReplicaSet{ + Spec: kextensions.ReplicaSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + }, + }, + changed: true, + expected: &kextensions.ReplicaSet{ + Spec: kextensions.ReplicaSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + }, + }, + { + obj: &kextensions.Job{ + Spec: kextensions.JobSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "docker.io/foo/bar"}, + {Image: "foo/bar"}, + }, + }, + }, + }, + }, + changed: true, + expected: &kextensions.Job{ + Spec: kextensions.JobSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "index.docker.io/foo/bar"}, + {Image: "index.docker.io/foo/bar"}, + }, + }, + }, + }, + }, + }, + { + obj: &kapi.Node{}, + nilReporter: true, + }, + { + obj: &buildapi.BuildConfig{ + Spec: buildapi.BuildConfigSpec{ + CommonSpec: buildapi.CommonSpec{ + Output: buildapi.BuildOutput{To: &kapi.ObjectReference{Kind: "DockerImage", Name: "docker.io/foo/bar"}}, + Source: buildapi.BuildSource{ + Images: []buildapi.ImageSource{ + {From: kapi.ObjectReference{Kind: "DockerImage", Name: "docker.io/foo/bar"}}, + {From: kapi.ObjectReference{Kind: "DockerImage", Name: "foo/bar"}}, + }, + }, + Strategy: buildapi.BuildStrategy{ + DockerStrategy: &buildapi.DockerBuildStrategy{From: &kapi.ObjectReference{Kind: "DockerImage", Name: "docker.io/foo/bar"}}, + SourceStrategy: &buildapi.SourceBuildStrategy{From: kapi.ObjectReference{Kind: "DockerImage", Name: "docker.io/foo/bar"}}, + CustomStrategy: &buildapi.CustomBuildStrategy{From: kapi.ObjectReference{Kind: "DockerImage", Name: "docker.io/foo/bar"}}, + }, + }, + }, + }, + changed: true, + expected: &buildapi.BuildConfig{ + Spec: buildapi.BuildConfigSpec{ + CommonSpec: buildapi.CommonSpec{ + Output: buildapi.BuildOutput{To: &kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + Source: buildapi.BuildSource{ + Images: []buildapi.ImageSource{ + {From: kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + {From: kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + }, + }, + Strategy: buildapi.BuildStrategy{ + DockerStrategy: &buildapi.DockerBuildStrategy{From: &kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + SourceStrategy: &buildapi.SourceBuildStrategy{From: kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + CustomStrategy: &buildapi.CustomBuildStrategy{From: kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + }, + }, + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeDockercfg, + Data: map[string][]byte{ + kapi.DockerConfigKey: []byte(`{"docker.io":{"auth":"Og=="},"other.server":{"auth":"Og=="}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + changed: true, + expected: &kapi.Secret{ + Type: kapi.SecretTypeDockercfg, + Data: map[string][]byte{ + kapi.DockerConfigKey: []byte(`{"index.docker.io":{"auth":"Og=="},"other.server":{"auth":"Og=="}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeDockercfg, + Data: map[string][]byte{ + kapi.DockerConfigKey: []byte(`{"myserver.com":{"auth":"Og=="},"other.server":{"auth":"Og=="}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + expected: &kapi.Secret{ + Type: kapi.SecretTypeDockercfg, + Data: map[string][]byte{ + kapi.DockerConfigKey: []byte(`{"myserver.com":{"auth":"Og=="},"other.server":{"auth":"Og=="}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{"docker.io":{"auth":"Og=="},"other.server":{"auth":"Og=="}}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + changed: true, + expected: &kapi.Secret{ + Type: kapi.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{"index.docker.io":{"auth":"Og=="},"other.server":{"auth":"Og=="}}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{"myserver.com":{},"other.server":{}}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + expected: &kapi.Secret{ + Type: kapi.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{"myserver.com":{},"other.server":{}}}`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeDockercfg, + Data: map[string][]byte{ + kapi.DockerConfigKey: []byte(`{"auths":{`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + err: true, + expected: &kapi.Secret{ + Type: kapi.SecretTypeDockercfg, + Data: map[string][]byte{ + kapi.DockerConfigKey: []byte(`{"auths":{`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + err: true, + expected: &kapi.Secret{ + Type: kapi.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{`), + "another": []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &kapi.Secret{ + Type: kapi.SecretTypeOpaque, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + expected: &kapi.Secret{ + Type: kapi.SecretTypeOpaque, + Data: map[string][]byte{ + kapi.DockerConfigJsonKey: []byte(`{"auths":{"docker.io":{},"other.server":{}}}`), + }, + }, + }, + { + obj: &imageapi.Image{ + DockerImageReference: "docker.io/foo/bar", + }, + changed: true, + expected: &imageapi.Image{ + DockerImageReference: "index.docker.io/foo/bar", + }, + }, + { + obj: &imageapi.Image{ + DockerImageReference: "other.docker.io/foo/bar", + }, + expected: &imageapi.Image{ + DockerImageReference: "other.docker.io/foo/bar", + }, + }, + { + obj: &imageapi.ImageStream{ + Spec: imageapi.ImageStreamSpec{ + Tags: map[string]imageapi.TagReference{ + "foo": {From: &kapi.ObjectReference{Kind: "DockerImage", Name: "docker.io/foo/bar"}}, + "bar": {From: &kapi.ObjectReference{Kind: "ImageStream", Name: "docker.io/foo/bar"}}, + "baz": {}, + }, + DockerImageRepository: "docker.io/foo/bar", + }, + Status: imageapi.ImageStreamStatus{ + DockerImageRepository: "docker.io/foo/bar", + Tags: map[string]imageapi.TagEventList{ + "bar": {Items: []imageapi.TagEvent{ + {DockerImageReference: "docker.io/foo/bar"}, + {DockerImageReference: "docker.io/foo/bar"}, + }}, + "baz": {Items: []imageapi.TagEvent{ + {DockerImageReference: "some.other/reference"}, + {DockerImageReference: "docker.io/foo/bar"}, + }}, + }, + }, + }, + changed: true, + expected: &imageapi.ImageStream{ + Spec: imageapi.ImageStreamSpec{ + Tags: map[string]imageapi.TagReference{ + "foo": {From: &kapi.ObjectReference{Kind: "DockerImage", Name: "index.docker.io/foo/bar"}}, + "bar": {From: &kapi.ObjectReference{Kind: "ImageStream", Name: "docker.io/foo/bar"}}, + "baz": {}, + }, + DockerImageRepository: "index.docker.io/foo/bar", + }, + Status: imageapi.ImageStreamStatus{ + DockerImageRepository: "docker.io/foo/bar", + Tags: map[string]imageapi.TagEventList{ + "bar": {Items: []imageapi.TagEvent{ + {DockerImageReference: "index.docker.io/foo/bar"}, + {DockerImageReference: "index.docker.io/foo/bar"}, + }}, + "baz": {Items: []imageapi.TagEvent{ + {DockerImageReference: "some.other/reference"}, + {DockerImageReference: "index.docker.io/foo/bar"}, + }}, + }, + }, + }, + }, + }, + }, + { + mappings: ImageReferenceMappings{{FromRegistry: "index.docker.io", ToRegistry: "another.registry"}}, + }, + { + mappings: ImageReferenceMappings{{FromRegistry: "index.docker.io", ToRegistry: "another.registry", ToName: "extra"}}, + }, + } + + for _, test := range testCases { + for i, v := range test.variants { + o := MigrateImageReferenceOptions{Mappings: test.mappings} + reporter, err := o.transform(v.obj) + if (err != nil) != v.err { + t.Errorf("%d: %v %t", i, err, v.err) + continue + } + if err != nil { + continue + } + if (reporter == nil) != v.nilReporter { + t.Errorf("%d: reporter %#v %t", i, reporter, v.nilReporter) + continue + } + if reporter == nil { + continue + } + if reporter.Changed() != v.changed { + t.Errorf("%d: changed %#v %t", i, reporter, v.changed) + continue + } + if !kapi.Semantic.DeepEqual(v.expected, v.obj) { + t.Errorf("%d: object: %s", i, diff.ObjectDiff(v.expected, v.obj)) + continue + } + } + } +} diff --git a/test/cmd/migrate.sh b/test/cmd/migrate.sh index 814d402333cf..4f6a03f6d55d 100755 --- a/test/cmd/migrate.sh +++ b/test/cmd/migrate.sh @@ -40,8 +40,18 @@ os::cmd::expect_success 'oc import-image --from=php:latest test:2 --confirm' os::cmd::expect_success 'oc tag --source=docker mysql:latest test:2' os::cmd::expect_success 'oc tag --source=docker php:latest test:2' os::cmd::expect_success 'oc tag --source=docker myregistry.com/php:latest test:3' -# verify dry run +# verify error cases os::cmd::expect_failure_and_text 'oadm migrate image-references' 'at least one mapping argument must be specified: REGISTRY/NAME=REGISTRY/NAME' +os::cmd::expect_failure_and_text 'oadm migrate image-references my.docker.io=docker.io/* --loglevel=1' 'all arguments' +os::cmd::expect_failure_and_text 'oadm migrate image-references my.docker.io/=docker.io/* --loglevel=1' 'not a valid source' +os::cmd::expect_failure_and_text 'oadm migrate image-references /*=docker.io/* --loglevel=1' 'not a valid source' +os::cmd::expect_failure_and_text 'oadm migrate image-references my.docker.io/*=docker.io --loglevel=1' 'all arguments' +os::cmd::expect_failure_and_text 'oadm migrate image-references my.docker.io/*=docker.io/ --loglevel=1' 'not a valid target' +os::cmd::expect_failure_and_text 'oadm migrate image-references my.docker.io/*=/x --loglevel=1' 'not a valid target' +os::cmd::expect_failure_and_text 'oadm migrate image-references my.docker.io/*=*/* --loglevel=1' 'at least one change' +os::cmd::expect_failure_and_text 'oadm migrate image-references a/b=a/b --loglevel=1' 'at least one field' +os::cmd::expect_failure_and_text 'oadm migrate image-references */*=*/* --loglevel=1' 'at least one change' +# verify dry run os::cmd::expect_success_and_text 'oadm migrate image-references my.docker.io/*=docker.io/* --loglevel=1' 'migrated=0' os::cmd::expect_success_and_text 'oadm migrate image-references --include=imagestreams docker.io/*=my.docker.io/* --loglevel=1' 'migrated \(DRY RUN\): imagestreams/test -n ' os::cmd::expect_success_and_text 'oadm migrate image-references --include=imagestreams docker.io/mysql=my.docker.io/* --all-namespaces=false --loglevel=1' 'migrated=1'