package controller import ( "errors" "fmt" "strings" "testing" kapi "k8s.io/kubernetes/pkg/api" kerrors "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" buildapi "github.com/openshift/origin/pkg/build/api" buildtest "github.com/openshift/origin/pkg/build/controller/test" buildgenerator "github.com/openshift/origin/pkg/build/generator" "github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" imageapi "github.com/openshift/origin/pkg/image/api" ) func TestNewImageID(t *testing.T) { // valid configuration, new build should be triggered. buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename:newImageID123") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Fatalf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) == 0 { t.Error("Expected build generation when new image was created!") } if actual, expected := bcInstantiator.newBuild.Spec.Strategy.DockerStrategy.From.Name, "registry.com/namespace/imagename:newImageID123"; actual != expected { t.Errorf("Image substitutions not properly setup for new build. Expected %s, got %s |", expected, actual) } if bcUpdater.buildcfg == nil { t.Fatalf("Expected buildConfig update when new image was created!") } if actual, expected := bcUpdater.buildcfg.Spec.Triggers[0].ImageChange.LastTriggeredImageID, "registry.com/namespace/imagename:newImageID123"; actual != expected { t.Errorf("Expected last triggered image %q, got %q", expected, actual) } } func TestNewImageIDDefaultTag(t *testing.T) { // valid configuration using default tag, new build should be triggered. buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "") imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{imageapi.DefaultImageTag: "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename:newImageID123") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Fatalf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) == 0 { t.Error("Expected build generation when new image was created!") } if actual, expected := bcInstantiator.newBuild.Spec.Strategy.DockerStrategy.From.Name, "registry.com/namespace/imagename:newImageID123"; actual != expected { t.Errorf("Image substitutions not properly setup for new build. Expected %s, got %s |", expected, actual) } if bcUpdater.buildcfg == nil { t.Fatal("Expected buildConfig update when new image was created!") } if actual, expected := bcUpdater.buildcfg.Spec.Triggers[0].ImageChange.LastTriggeredImageID, "registry.com/namespace/imagename:newImageID123"; actual != expected { t.Errorf("Expected last triggered image %q, got %q", expected, actual) } } func TestNonExistentImageStream(t *testing.T) { // this buildconfig references a non-existent image stream, so an update to the real image stream should not // trigger a build here. buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("otherImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Fatalf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when a different repository was updated!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when a different repository was updated!") } } func TestNewImageDifferentTagUpdate(t *testing.T) { // this buildconfig references a different tag than the one that will be updated buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"otherTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when a different repository was updated!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when a different repository was updated!") } } func TestNewImageDifferentTagUpdate2(t *testing.T) { // this buildconfig references a different tag than the one that will be updated // it has previously run a build for the testTagID123 tag. buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") buildcfg.Spec.Triggers[0].ImageChange.LastTriggeredImageID = "registry.com/namespace/imagename:testTagID123" imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"otherTag": "newImageID123", "testTag": "testTagID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when a different repository was updated!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when a different repository was updated!") } } func TestNewDifferentImageUpdate(t *testing.T) { // this buildconfig references a different image than the one that will be updated buildcfg := mockBuildConfig("registry.com/namespace/imagename1", "registry.com/namespace/imagename1", "testImageRepo1", "testTag1") imageStream := mockImageStream("testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when a different repository was updated!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when a different repository was updated!") } } func TestSameStreamNameDifferentNamespaces(t *testing.T) { // this buildconfig references an image stream with the same name as the one that was just updated, // but the namespaces differ buildcfg := mockBuildConfig("registry.com/namespace/imagename1", "registry.com/namespace/imagename1", "testImageRepo1", "testTag1") imageStream := mockImageStream("testImageRepo1", "registry.com/namespace/imagename2", map[string]string{"testTag1": "newImageID123"}) imageStream.Namespace = "othernamespace" image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when a different repository was updated!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when a different repository was updated!") } } func TestBuildConfigWithDifferentTriggerType(t *testing.T) { // this buildconfig has different (than ImageChangeTrigger) trigger defined buildcfg := mockBuildConfig("registry.com/namespace/imagename1", "", "", "") buildcfg.Spec.Triggers[0].Type = buildapi.GenericWebHookBuildTriggerType imageStream := mockImageStream("testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when a different repository was updated!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when a different trigger was defined!") } } func TestNoImageIDChange(t *testing.T) { // this buildConfig has up to date configuration, but is checked eg. during // startup when we're checking all the imageRepos buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") buildcfg.Spec.Triggers[0].ImageChange.LastTriggeredImageID = "registry.com/namespace/imagename:imageID123" imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"testTag": "imageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when no change happened!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when no change happened!") } } func TestBuildConfigInstantiatorError(t *testing.T) { // valid configuration, but build creation fails, in that situation the buildconfig should not be updated buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename:newImageID123") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcInstantiator.err = fmt.Errorf("instantiating error") bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err == nil || !strings.Contains(err.Error(), "will be retried") { t.Fatalf("Expected 'will be retried' from HandleImageStream, got %s", err.Error()) } if actual, expected := bcInstantiator.newBuild.Spec.Strategy.DockerStrategy.From.Name, "registry.com/namespace/imagename:newImageID123"; actual != expected { t.Errorf("Image substitutions not properly setup for new build. Expected %s, got %s |", expected, actual) } if bcUpdater.updateCount > 1 { t.Fatal("Expected no buildConfig update on BuildCreate error!") } } func TestBuildConfigUpdateError(t *testing.T) { // valid configuration, but build creation fails, in that situation the buildconfig should not be updated buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater bcUpdater.err = kerrors.NewConflict(buildapi.Resource("BuildConfig"), buildcfg.Name, errors.New("foo")) err := controller.HandleImageStream(imageStream) if len(bcInstantiator.name) == 0 { t.Error("Expected build generation when new image was created!") } if err == nil || !strings.Contains(err.Error(), "will be retried") { t.Fatalf("Expected 'will be retried' from HandleImageStream, got %s", err.Error()) } } func TestNewImageIDNoDockerRepo(t *testing.T) { // No docker repository associated with the imageStream, so no build can be created buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("testImageStream", "", map[string]string{"testTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater err := controller.HandleImageStream(imageStream) if err != nil { t.Errorf("Unexpected error %v from HandleImageStream", err) } if len(bcInstantiator.name) != 0 { t.Error("New build generated when no change happened!") } if bcUpdater.buildcfg != nil { t.Error("BuildConfig was updated when no change happened!") } } type mockBuildConfigUpdater struct { updateCount int buildcfg *buildapi.BuildConfig err error } func (m *mockBuildConfigUpdater) Update(buildcfg *buildapi.BuildConfig) error { m.buildcfg = buildcfg m.updateCount++ return m.err } func mockBuildConfig(baseImage, triggerImage, repoName, repoTag string) *buildapi.BuildConfig { dockerfile := "FROM foo" return &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{ Name: "testBuildCfg", Namespace: kapi.NamespaceDefault, }, Spec: buildapi.BuildConfigSpec{ CommonSpec: buildapi.CommonSpec{ Source: buildapi.BuildSource{ Dockerfile: &dockerfile, }, Strategy: buildapi.BuildStrategy{ DockerStrategy: &buildapi.DockerBuildStrategy{ From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: repoName + ":" + repoTag, }, }, }, }, Triggers: []buildapi.BuildTriggerPolicy{ { Type: buildapi.ImageChangeBuildTriggerType, ImageChange: &buildapi.ImageChangeTrigger{}, }, }, }, } } func mockImageStream(repoName, dockerImageRepo string, tags map[string]string) *imageapi.ImageStream { tagHistory := make(map[string]imageapi.TagEventList) for tag, imageID := range tags { tagHistory[tag] = imageapi.TagEventList{ Items: []imageapi.TagEvent{ { Image: imageID, DockerImageReference: fmt.Sprintf("%s:%s", dockerImageRepo, imageID), }, }, } } return &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: repoName, Namespace: kapi.NamespaceDefault, }, Status: imageapi.ImageStreamStatus{ DockerImageRepository: dockerImageRepo, Tags: tagHistory, }, } } func mockImage(name, dockerSpec string) *imageapi.Image { return &imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: name, }, DockerImageReference: dockerSpec, } } type buildConfigInstantiator struct { generator buildgenerator.BuildGenerator buildConfigUpdater *mockBuildConfigUpdater name string newBuild *buildapi.Build err error } func (i *buildConfigInstantiator) Instantiate(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error) { i.name = request.Name return i.generator.Instantiate(kapi.WithNamespace(kapi.NewContext(), namespace), request) } func mockBuildConfigInstantiator(buildcfg *buildapi.BuildConfig, imageStream *imageapi.ImageStream, image *imageapi.Image) *buildConfigInstantiator { builderAccount := kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{Name: bootstrappolicy.BuilderServiceAccountName, Namespace: kapi.NamespaceDefault}, Secrets: []kapi.ObjectReference{}, } instantiator := &buildConfigInstantiator{} instantiator.buildConfigUpdater = &mockBuildConfigUpdater{} generator := buildgenerator.BuildGenerator{ Secrets: fake.NewSimpleClientset().Core(), ServiceAccounts: fake.NewSimpleClientset(&builderAccount).Core(), Client: buildgenerator.Client{ GetBuildConfigFunc: func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) { return buildcfg, nil }, UpdateBuildConfigFunc: func(ctx kapi.Context, buildConfig *buildapi.BuildConfig) error { return instantiator.buildConfigUpdater.Update(buildConfig) }, CreateBuildFunc: func(ctx kapi.Context, build *buildapi.Build) error { instantiator.newBuild = build return instantiator.err }, GetBuildFunc: func(ctx kapi.Context, name string) (*buildapi.Build, error) { return instantiator.newBuild, nil }, GetImageStreamFunc: func(ctx kapi.Context, name string) (*imageapi.ImageStream, error) { return imageStream, nil }, GetImageStreamTagFunc: func(ctx kapi.Context, name string) (*imageapi.ImageStreamTag, error) { return &imageapi.ImageStreamTag{Image: *image}, nil }, GetImageStreamImageFunc: func(ctx kapi.Context, name string) (*imageapi.ImageStreamImage, error) { return &imageapi.ImageStreamImage{Image: *image}, nil }, }} instantiator.generator = generator return instantiator } func mockImageChangeController(buildcfg *buildapi.BuildConfig, imageStream *imageapi.ImageStream, image *imageapi.Image) *ImageChangeController { return &ImageChangeController{ BuildConfigIndex: buildtest.NewFakeBuildConfigIndex(buildcfg), BuildConfigInstantiator: mockBuildConfigInstantiator(buildcfg, imageStream, image), } }