// // Copyright (c) 2012-2019 Red Hat, Inc. // This program and the accompanying materials are made // available under the terms of the Eclipse Public License 2.0 // which is available at https://www.eclipse.org/legal/epl-2.0/ // // SPDX-License-Identifier: EPL-2.0 // // Contributors: // Red Hat, Inc. - initial API and implementation // package che import ( "context" "fmt" "io/ioutil" "os" mocks "github.com/eclipse-che/che-operator/mocks" "reflect" "time" chev1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/pkg/apis/che/v1alpha1" "github.com/golang/mock/gomock" identity_provider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider" "github.com/google/go-cmp/cmp" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/util" console "github.com/openshift/api/console/v1" orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" configv1 "github.com/openshift/api/config/v1" oauth "github.com/openshift/api/oauth/v1" routev1 "github.com/openshift/api/route/v1" userv1 "github.com/openshift/api/user/v1" operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" packagesv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" che_mocks "github.com/eclipse-che/che-operator/mocks/pkg/controller/che" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/discovery" fakeDiscovery "k8s.io/client-go/discovery/fake" fakeclientset "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/yaml" "testing" ) var ( name = "eclipse-che" namespace = "eclipse-che" csvName = "kubernetes-imagepuller-operator.v0.0.4" packageManifest = &packagesv1.PackageManifest{ ObjectMeta: metav1.ObjectMeta{ Name: "kubernetes-imagepuller-operator", Namespace: namespace, }, Status: packagesv1.PackageManifestStatus{ CatalogSource: "community-operators", CatalogSourceNamespace: "olm", DefaultChannel: "stable", PackageName: "kubernetes-imagepuller-operator", }, } operatorGroup = &operatorsv1.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ Name: "kubernetes-imagepuller-operator", Namespace: namespace, }, Spec: operatorsv1.OperatorGroupSpec{ TargetNamespaces: []string{ namespace, }, }, } subscription = &operatorsv1alpha1.Subscription{ ObjectMeta: metav1.ObjectMeta{ Name: "kubernetes-imagepuller-operator", Namespace: namespace, }, Spec: &operatorsv1alpha1.SubscriptionSpec{ CatalogSource: "community-operators", Channel: "stable", CatalogSourceNamespace: "olm", InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, Package: "kubernetes-imagepuller-operator", }, } wrongSubscription = &operatorsv1alpha1.Subscription{ ObjectMeta: metav1.ObjectMeta{ Name: "kubernetes-imagepuller-operator", Namespace: namespace, }, Spec: &operatorsv1alpha1.SubscriptionSpec{ CatalogSource: "community-operators", Channel: "beta", CatalogSourceNamespace: "olm", InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, Package: "kubernetes-imagepuller-operator", }, } valueTrue = true defaultImagePuller = &chev1alpha1.KubernetesImagePuller{ TypeMeta: metav1.TypeMeta{ APIVersion: "che.eclipse.org/v1alpha1", Kind: "KubernetesImagePuller", }, ObjectMeta: metav1.ObjectMeta{ Name: "eclipse-che-image-puller", Namespace: namespace, Labels: map[string]string{ "app.kubernetes.io/part-of": name, "app": "che", "component": "kubernetes-image-puller", }, ResourceVersion: "1", OwnerReferences: []metav1.OwnerReference{ { APIVersion: "org.eclipse.che/v1", Kind: "CheCluster", Controller: &valueTrue, BlockOwnerDeletion: &valueTrue, Name: "eclipse-che", }, }, }, Spec: chev1alpha1.KubernetesImagePullerSpec{ DeploymentName: "kubernetes-image-puller", ConfigMapName: "k8s-image-puller", Images: "che-workspace-plugin-broker-metadata=quay.io/eclipse/che-plugin-metadata-broker:v3.4.0;che-workspace-plugin-broker-artifacts=quay.io/eclipse/che-plugin-artifacts-broker:v3.4.0;", }, } clusterServiceVersion = &operatorsv1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: csvName, }, } nonEmptyUserList = &userv1.UserList{ Items: []userv1.User{ { ObjectMeta: metav1.ObjectMeta{ Name: "user1", }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "user2", }, }, }, } oAuthClient = &oauth.OAuthClient{} oAuthWithNoIdentityProviders = &configv1.OAuth{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster", Namespace: namespace, }, } oAuthWithIdentityProvider = &configv1.OAuth{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster", Namespace: namespace, }, Spec: configv1.OAuthSpec{ IdentityProviders: []configv1.IdentityProvider{ { Name: "htpasswd", }, }, }, } route = &routev1.Route{} ) func init() { operator := &appsv1.Deployment{} data, err := ioutil.ReadFile("../../../deploy/operator.yaml") yaml.Unmarshal(data, operator) if err == nil { for _, env := range operator.Spec.Template.Spec.Containers[0].Env { os.Setenv(env.Name, env.Value) } } } func TestCaseAutoDetectOAuth(t *testing.T) { type testCase struct { name string initObjects []runtime.Object openshiftVersion string initialOAuthValue *bool oAuthExpected *bool initialOpenShiftOAuthUserEnabled *bool OpenShiftOAuthUserCredentialsSecret string mockFunction func(ctrl *gomock.Controller, crNamespace string, usernamePrefix string) *che_mocks.MockOpenShiftOAuthUserHandler } testCases := []testCase{ { name: "che-operator should auto enable oAuth when Che CR with oAuth nil value on the Openshift 3 with users > 0", initObjects: []runtime.Object{ nonEmptyUserList, &oauth.OAuthClient{}, }, openshiftVersion: "3", initialOAuthValue: nil, oAuthExpected: util.NewBoolPointer(true), }, { name: "che-operator should auto disable oAuth when Che CR with nil oAuth on the Openshift 3 with no users", initObjects: []runtime.Object{ &userv1.UserList{}, &oauth.OAuthClient{}, }, openshiftVersion: "3", initialOAuthValue: util.NewBoolPointer(false), oAuthExpected: util.NewBoolPointer(false), }, { name: "che-operator should respect oAuth = true even if there no users on the Openshift 3", initObjects: []runtime.Object{ &userv1.UserList{}, &oauth.OAuthClient{}, }, openshiftVersion: "3", initialOAuthValue: util.NewBoolPointer(true), oAuthExpected: util.NewBoolPointer(true), }, { name: "che-operator should respect oAuth = true even if there are some users on the Openshift 3", initObjects: []runtime.Object{ nonEmptyUserList, &oauth.OAuthClient{}, }, openshiftVersion: "3", initialOAuthValue: util.NewBoolPointer(true), oAuthExpected: util.NewBoolPointer(true), }, { name: "che-operator should respect oAuth = false even if there are some users on the Openshift 3", initObjects: []runtime.Object{ nonEmptyUserList, &oauth.OAuthClient{}, }, openshiftVersion: "3", initialOAuthValue: util.NewBoolPointer(false), oAuthExpected: util.NewBoolPointer(false), }, { name: "che-operator should respect oAuth = false even if no users on the Openshift 3", initObjects: []runtime.Object{ &userv1.UserList{}, &oauth.OAuthClient{}, }, openshiftVersion: "3", initialOAuthValue: util.NewBoolPointer(false), oAuthExpected: util.NewBoolPointer(false), }, { name: "che-operator should auto enable oAuth when Che CR with nil value on the Openshift 4 with identity providers", initObjects: []runtime.Object{ oAuthWithIdentityProvider, }, openshiftVersion: "4", initialOAuthValue: nil, oAuthExpected: util.NewBoolPointer(true), }, { name: "che-operator should respect oAuth = true even if there no indentity providers on the Openshift 4", initObjects: []runtime.Object{ oAuthWithNoIdentityProviders, }, openshiftVersion: "4", initialOAuthValue: util.NewBoolPointer(true), oAuthExpected: util.NewBoolPointer(true), initialOpenShiftOAuthUserEnabled: util.NewBoolPointer(true), }, { name: "che-operator should create initial user and enable oAuth, when oAuth = true, initialOpenShiftOAuthUserEnabled = true and there no indentity providers on the Openshift 4", initObjects: []runtime.Object{ oAuthWithNoIdentityProviders, }, openshiftVersion: "4", initialOAuthValue: nil, oAuthExpected: util.NewBoolPointer(true), initialOpenShiftOAuthUserEnabled: util.NewBoolPointer(true), mockFunction: func(ctrl *gomock.Controller, crNamespace string, userNamePrefix string) *che_mocks.MockOpenShiftOAuthUserHandler { m := che_mocks.NewMockOpenShiftOAuthUserHandler(ctrl) m.EXPECT().SyncOAuthInitialUser(gomock.Any(), gomock.Any()).Return(true, nil) return m }, OpenShiftOAuthUserCredentialsSecret: openShiftOAuthUserCredentialsSecret, }, { name: "che-operator should respect oAuth = true even if there are some users on the Openshift 4", initObjects: []runtime.Object{ oAuthWithIdentityProvider, }, openshiftVersion: "4", initialOAuthValue: util.NewBoolPointer(true), oAuthExpected: util.NewBoolPointer(true), initialOpenShiftOAuthUserEnabled: util.NewBoolPointer(true), }, { name: "che-operator should respect oAuth = false even if there no indentity providers on the Openshift 4", initObjects: []runtime.Object{ oAuthWithNoIdentityProviders, }, openshiftVersion: "4", initialOAuthValue: util.NewBoolPointer(false), oAuthExpected: util.NewBoolPointer(false), }, { name: "che-operator should respect oAuth = false even if there are some users on the Openshift 4", initObjects: []runtime.Object{ oAuthWithIdentityProvider, }, openshiftVersion: "4", initialOAuthValue: util.NewBoolPointer(false), oAuthExpected: util.NewBoolPointer(false), }, { name: "che-operator should auto disable oAuth on error retieve identity providers", initObjects: []runtime.Object{}, openshiftVersion: "4", initialOAuthValue: nil, initialOpenShiftOAuthUserEnabled: util.NewBoolPointer(true), oAuthExpected: util.NewBoolPointer(false), }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.LoggerTo(os.Stdout, true)) scheme := scheme.Scheme orgv1.SchemeBuilder.AddToScheme(scheme) scheme.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient) scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{}) scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.OAuth{}, &configv1.Proxy{}) scheme.AddKnownTypes(routev1.GroupVersion, route) initCR := InitCheWithSimpleCR().DeepCopy() initCR.Spec.Auth.OpenShiftoAuth = testCase.initialOAuthValue testCase.initObjects = append(testCase.initObjects, initCR) initCR.Spec.Auth.InitialOpenShiftOAuthUser = testCase.initialOpenShiftOAuthUserEnabled cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) clientSet := fakeclientset.NewSimpleClientset() fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery) fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{} if !ok { t.Fatal("Error creating fake discovery client") } // prepare mocks var userHandlerMock *che_mocks.MockOpenShiftOAuthUserHandler if testCase.mockFunction != nil { ctrl := gomock.NewController(t) userHandlerMock = testCase.mockFunction(ctrl, initCR.Namespace, deploy.DefaultCheFlavor(initCR)) defer ctrl.Finish() } r := &ReconcileChe{ client: cli, nonCachedClient: nonCachedClient, discoveryClient: fakeDiscovery, scheme: scheme, tests: true, userHandler: userHandlerMock, } req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } util.IsOpenShift = true util.IsOpenShift4 = testCase.openshiftVersion == "4" _, err := r.Reconcile(req) if err != nil { t.Fatalf("Error reconciling: %v", err) } cheCR := &orgv1.CheCluster{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, cheCR); err != nil { t.Errorf("CR not found") } if cheCR.Spec.Auth.OpenShiftoAuth == nil { t.Error("OAuth should not stay with nil value.") } if *cheCR.Spec.Auth.OpenShiftoAuth != *testCase.oAuthExpected { t.Errorf("Openshift oAuth should be %t", *testCase.oAuthExpected) } if cheCR.Status.OpenShiftOAuthUserCredentialsSecret != testCase.OpenShiftOAuthUserCredentialsSecret { t.Errorf("Expected initial openshift oAuth user secret %s in the CR status", testCase.OpenShiftOAuthUserCredentialsSecret) } }) } } func TestEnsureServerExposureStrategy(t *testing.T) { type testCase struct { name string expectedCr *orgv1.CheCluster devWorkspaceEnabled bool initObjects []runtime.Object } testCases := []testCase{ { name: "Single Host should be enabled if devWorkspace is enabled", expectedCr: &orgv1.CheCluster{ Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ ServerExposureStrategy: "single-host", }, }, }, devWorkspaceEnabled: true, }, { name: "Multi Host should be enabled if devWorkspace is not enabled", expectedCr: &orgv1.CheCluster{ Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ ServerExposureStrategy: "multi-host", }, }, }, devWorkspaceEnabled: false, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.LoggerTo(os.Stdout, true)) scheme := scheme.Scheme orgv1.SchemeBuilder.AddToScheme(scheme) initCR := InitCheWithSimpleCR().DeepCopy() testCase.initObjects = append(testCase.initObjects, initCR) if testCase.devWorkspaceEnabled { initCR.Spec.DevWorkspace.Enable = true } cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) clientSet := fakeclientset.NewSimpleClientset() fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery) fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{} if !ok { t.Fatal("Error creating fake discovery client") } r := &ReconcileChe{ client: cli, nonCachedClient: nonCachedClient, discoveryClient: fakeDiscovery, scheme: scheme, tests: true, } req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } _, err := r.Reconcile(req) if err != nil { t.Fatalf("Error reconciling: %v", err) } cr := &orgv1.CheCluster{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, cr); err != nil { t.Errorf("CR not found") } if !reflect.DeepEqual(testCase.expectedCr.Spec.Server.ServerExposureStrategy, cr.Spec.Server.ServerExposureStrategy) { t.Errorf("Expected CR and CR returned from API server are different (-want +got): %v", cmp.Diff(testCase.expectedCr.Spec.Server.ServerExposureStrategy, cr.Spec.Server.ServerExposureStrategy)) } }) } } func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) { type testCase struct { name string isOpenShift bool isOpenShift4 bool initObjects []runtime.Object cheCluster *orgv1.CheCluster expectedDevfileRegistryURL string } testCases := []testCase{ { name: "Test Status.DevfileRegistryURL #1", cheCluster: &orgv1.CheCluster{ TypeMeta: metav1.TypeMeta{ Kind: "CheCluster", APIVersion: "org.eclipse.che/v1", }, ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ ExternalDevfileRegistry: false, }, }, }, expectedDevfileRegistryURL: "http://devfile-registry-eclipse-che./", }, { name: "Test Status.DevfileRegistryURL #2", cheCluster: &orgv1.CheCluster{ TypeMeta: metav1.TypeMeta{ Kind: "CheCluster", APIVersion: "org.eclipse.che/v1", }, ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ ExternalDevfileRegistry: false, DevfileRegistryUrl: "https://devfile-registry.external.1", ExternalDevfileRegistries: []orgv1.ExternalDevfileRegistries{ {Url: "https://devfile-registry.external.2"}, }, }, }, }, expectedDevfileRegistryURL: "http://devfile-registry-eclipse-che./", }, { name: "Test Status.DevfileRegistryURL #2", cheCluster: &orgv1.CheCluster{ TypeMeta: metav1.TypeMeta{ Kind: "CheCluster", APIVersion: "org.eclipse.che/v1", }, ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ ExternalDevfileRegistry: true, DevfileRegistryUrl: "https://devfile-registry.external.1", ExternalDevfileRegistries: []orgv1.ExternalDevfileRegistries{ {Url: "https://devfile-registry.external.2"}, }, }, }, }, expectedDevfileRegistryURL: "", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.LoggerTo(os.Stdout, true)) scheme := scheme.Scheme orgv1.SchemeBuilder.AddToScheme(scheme) testCase.initObjects = append(testCase.initObjects, testCase.cheCluster) cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) clientSet := fakeclientset.NewSimpleClientset() fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery) if !ok { t.Fatal("Error creating fake discovery client") } fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{} r := &ReconcileChe{ client: cli, nonCachedClient: nonCachedClient, discoveryClient: fakeDiscovery, scheme: scheme, tests: true, } req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 _, err := r.Reconcile(req) if err != nil { t.Fatalf("Error reconciling: %v", err) } cr := &orgv1.CheCluster{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, cr); err != nil { t.Errorf("CR not found") } if cr.Status.DevfileRegistryURL != testCase.expectedDevfileRegistryURL { t.Fatalf("Exected: %s, but found: %s", testCase.expectedDevfileRegistryURL, cr.Status.DevfileRegistryURL) } }) } } func TestImagePullerConfiguration(t *testing.T) { type testCase struct { name string initCR *orgv1.CheCluster initObjects []runtime.Object expectedCR *orgv1.CheCluster expectedOperatorGroup *operatorsv1.OperatorGroup expectedSubscription *operatorsv1alpha1.Subscription expectedImagePuller *chev1alpha1.KubernetesImagePuller shouldDelete bool } testCases := []testCase{ { name: "image puller enabled, no operatorgroup, should create an operatorgroup", initCR: InitCheCRWithImagePullerEnabled(), initObjects: []runtime.Object{ packageManifest, }, expectedOperatorGroup: operatorGroup, }, { name: "image puller enabled, operatorgroup exists, should create a subscription", initCR: InitCheCRWithImagePullerEnabled(), initObjects: []runtime.Object{ packageManifest, operatorGroup, }, expectedSubscription: subscription, }, { name: "image puller enabled, subscription created but has changed, should update subscription, this shouldn't happen", initCR: InitCheCRWithImagePullerEnabled(), initObjects: []runtime.Object{ packageManifest, operatorGroup, wrongSubscription, }, expectedSubscription: subscription, }, { name: "image puller enabled, subscription created, should add finalizer", initCR: InitCheCRWithImagePullerEnabled(), expectedCR: ExpectedCheCRWithImagePullerFinalizer(), initObjects: []runtime.Object{ packageManifest, operatorGroup, subscription, }, }, { name: "image puller enabled with finalizer but default values are empty, subscription exists, should update the CR", initCR: InitCheCRWithImagePullerFinalizer(), expectedCR: &orgv1.CheCluster{ TypeMeta: metav1.TypeMeta{ Kind: "CheCluster", APIVersion: "org.eclipse.che/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, ResourceVersion: "1", Finalizers: []string{ "kubernetesimagepullers.finalizers.che.eclipse.org", }, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, Spec: chev1alpha1.KubernetesImagePullerSpec{ DeploymentName: "kubernetes-image-puller", ConfigMapName: "k8s-image-puller", }, }, Server: orgv1.CheClusterSpecServer{ ServerExposureStrategy: "multi-host", }, }, }, initObjects: []runtime.Object{ packageManifest, operatorGroup, subscription, }, }, { name: "image puller enabled default values already set, subscription exists, should create a KubernetesImagePuller", initCR: InitCheCRWithImagePullerEnabledAndDefaultValuesSet(), initObjects: []runtime.Object{ packageManifest, operatorGroup, subscription, }, expectedImagePuller: defaultImagePuller, }, { name: "image puller enabled, user images set, subscription exists, should create a KubernetesImagePuller with user images", initCR: InitCheCRWithImagePullerEnabledAndImagesSet(), initObjects: []runtime.Object{ packageManifest, operatorGroup, subscription, }, expectedImagePuller: &chev1alpha1.KubernetesImagePuller{ TypeMeta: metav1.TypeMeta{ APIVersion: "che.eclipse.org/v1alpha1", Kind: "KubernetesImagePuller", }, ObjectMeta: metav1.ObjectMeta{ Name: "eclipse-che-image-puller", Namespace: namespace, Labels: map[string]string{ "app.kubernetes.io/part-of": name, "app": "che", "component": "kubernetes-image-puller", }, ResourceVersion: "1", OwnerReferences: []metav1.OwnerReference{ { APIVersion: "org.eclipse.che/v1", Kind: "CheCluster", Controller: &valueTrue, BlockOwnerDeletion: &valueTrue, Name: "eclipse-che", }, }, }, Spec: chev1alpha1.KubernetesImagePullerSpec{ DeploymentName: "kubernetes-image-puller", ConfigMapName: "k8s-image-puller", Images: "image=image_url", }, }, }, { name: "image puller enabled, KubernetesImagePuller created and spec in CheCluster is different, should update the KubernetesImagePuller", initCR: InitCheCRWithImagePullerEnabledAndNewValuesSet(), initObjects: []runtime.Object{ packageManifest, operatorGroup, subscription, defaultImagePuller, }, expectedImagePuller: &chev1alpha1.KubernetesImagePuller{ TypeMeta: metav1.TypeMeta{Kind: "KubernetesImagePuller", APIVersion: "che.eclipse.org/v1alpha1"}, ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "2", Name: name + "-image-puller", Namespace: namespace, Labels: map[string]string{ "app": "che", "component": "kubernetes-image-puller", "app.kubernetes.io/part-of": "eclipse-che", }, OwnerReferences: []metav1.OwnerReference{ { APIVersion: "org.eclipse.che/v1", Kind: "CheCluster", BlockOwnerDeletion: &valueTrue, Controller: &valueTrue, Name: name, }, }, }, Spec: chev1alpha1.KubernetesImagePullerSpec{ ConfigMapName: "k8s-image-puller-trigger-update", DeploymentName: "kubernetes-image-puller-trigger-update", }, }, }, { name: "image puller already created, imagePuller disabled, should delete everything", initCR: InitCheCRWithImagePullerDisabled(), initObjects: []runtime.Object{ packageManifest, operatorGroup, subscription, clusterServiceVersion, defaultImagePuller, }, shouldDelete: true, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.LoggerTo(os.Stdout, true)) orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) packagesv1.AddToScheme(scheme.Scheme) operatorsv1alpha1.AddToScheme(scheme.Scheme) operatorsv1.AddToScheme(scheme.Scheme) chev1alpha1.AddToScheme(scheme.Scheme) routev1.AddToScheme(scheme.Scheme) testCase.initObjects = append(testCase.initObjects, testCase.initCR) cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) nonCachedClient := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) clientSet := fakeclientset.NewSimpleClientset() fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery) fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{ { GroupVersion: "packages.operators.coreos.com/v1", APIResources: []metav1.APIResource{ { Kind: "PackageManifest", }, }, }, { GroupVersion: "operators.coreos.com/v1alpha1", APIResources: []metav1.APIResource{ {Kind: "OperatorGroup"}, {Kind: "Subscription"}, {Kind: "ClusterServiceVersion"}, }, }, { GroupVersion: "che.eclipse.org/v1alpha1", APIResources: []metav1.APIResource{ {Kind: "KubernetesImagePuller"}, }, }, } if !ok { t.Error("Error creating fake discovery client") os.Exit(1) } r := &ReconcileChe{ client: cli, nonCachedClient: nonCachedClient, discoveryClient: fakeDiscovery, scheme: scheme.Scheme, tests: true, } req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } _, err := r.Reconcile(req) if err != nil { t.Fatalf("Error reconciling: %v", err) } if testCase.expectedOperatorGroup != nil { gotOperatorGroup := &operatorsv1.OperatorGroup{} err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedOperatorGroup.Namespace, Name: testCase.expectedOperatorGroup.Name}, gotOperatorGroup) if err != nil { t.Errorf("Error getting OperatorGroup: %v", err) } if !reflect.DeepEqual(testCase.expectedOperatorGroup.Spec.TargetNamespaces, gotOperatorGroup.Spec.TargetNamespaces) { t.Errorf("Error expected target namespace %v but got %v", testCase.expectedOperatorGroup.Spec.TargetNamespaces, gotOperatorGroup.Spec.TargetNamespaces) } } if testCase.expectedSubscription != nil { gotSubscription := &operatorsv1alpha1.Subscription{} err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedSubscription.Namespace, Name: testCase.expectedSubscription.Name}, gotSubscription) if err != nil { t.Errorf("Error getting Subscription: %v", err) } if !reflect.DeepEqual(testCase.expectedSubscription.Spec, gotSubscription.Spec) { t.Errorf("Error, subscriptions differ (-want +got) %v", cmp.Diff(testCase.expectedSubscription.Spec, gotSubscription.Spec)) } } // if expectedCR is not set, don't check it if testCase.expectedCR != nil && !reflect.DeepEqual(testCase.initCR, testCase.expectedCR) { gotCR := &orgv1.CheCluster{} err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, gotCR) if err != nil { t.Errorf("Error getting CheCluster: %v", err) } if !reflect.DeepEqual(testCase.expectedCR, gotCR) { t.Errorf("Expected CR and CR returned from API server are different (-want +got): %v", cmp.Diff(testCase.expectedCR, gotCR)) } } if testCase.expectedImagePuller != nil { gotImagePuller := &chev1alpha1.KubernetesImagePuller{} err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedImagePuller.Namespace, Name: testCase.expectedImagePuller.Name}, gotImagePuller) if err != nil { t.Errorf("Error getting KubernetesImagePuller: %v", err) } if !reflect.DeepEqual(testCase.expectedImagePuller, gotImagePuller) { t.Errorf("Expected KubernetesImagePuller and KubernetesImagePuller returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedImagePuller, gotImagePuller)) } } if testCase.shouldDelete { imagePuller := &chev1alpha1.KubernetesImagePuller{} err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name + "-image-puller"}, imagePuller) if err == nil || !errors.IsNotFound(err) { t.Fatalf("Should not have found KubernetesImagePuller: %v", err) } clusterServiceVersion := &operatorsv1alpha1.ClusterServiceVersion{} err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: csvName}, clusterServiceVersion) if err == nil || !errors.IsNotFound(err) { t.Fatalf("Should not have found ClusterServiceVersion: %v", err) } subscription := &operatorsv1alpha1.Subscription{} err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: "kubernetes-imagepuller-operator"}, subscription) if err == nil || !errors.IsNotFound(err) { t.Fatalf("Should not have found subscription: %v", err) } operatorGroup := &operatorsv1.OperatorGroup{} err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: "kubernetes-imagepuller-operator"}, operatorGroup) if err == nil || !errors.IsNotFound(err) { t.Fatalf("Should not have found subscription: %v", err) } } }) } } func TestCheController(t *testing.T) { util.IsOpenShift = true util.IsOpenShift4 = false // Set the logger to development mode for verbose logs. logf.SetLogger(logf.ZapLogger(true)) cl, dc, scheme := Init() // Create a ReconcileChe object with the scheme and fake client r := &ReconcileChe{client: cl, nonCachedClient: cl, scheme: &scheme, discoveryClient: dc, tests: true} // get CR cheCR := &orgv1.CheCluster{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, cheCR); err != nil { t.Errorf("CR not found") } // Mock request to simulate Reconcile() being called on an event for a // watched resource . req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } _, err := r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } // get devfile-registry configmap devfilecm := &corev1.ConfigMap{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DevfileRegistryName, Namespace: cheCR.Namespace}, devfilecm); err != nil { t.Errorf("ConfigMap %s not found: %s", devfilecm.Name, err) } // update CR and make sure Che configmap has been updated cheCR.Spec.Server.TlsSupport = true if err := cl.Update(context.TODO(), cheCR); err != nil { t.Error("Failed to update CheCluster custom resource") } // reconcile again _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } // get configmap cm := &corev1.ConfigMap{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, cm); err != nil { t.Errorf("ConfigMap %s not found: %s", cm.Name, err) } customCm := &corev1.ConfigMap{} // Custom ConfigMap should be gone err = cl.Get(context.TODO(), types.NamespacedName{Name: "custom", Namespace: cheCR.Namespace}, customCm) if !errors.IsNotFound(err) { t.Errorf("Custom config map should be deleted and merged with Che ConfigMap") } // Get the custom role binding that should have been created for the role we passed in rb := &rbac.RoleBinding{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che-workspace-custom", Namespace: cheCR.Namespace}, rb); err != nil { t.Errorf("Custom role binding %s not found: %s", rb.Name, err) } // run a few checks to make sure the operator reconciled tls routes and updated configmap if cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] != "true" { t.Errorf("ConfigMap wasn't updated. Extecting true, got: %s", cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"]) } route := &routev1.Route{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultCheFlavor(cheCR), Namespace: cheCR.Namespace}, route); err != nil { t.Errorf("Route %s not found: %s", cm.Name, err) } if route.Spec.TLS.Termination != "edge" { t.Errorf("Test failed as %s %s is not a TLS route", route.Kind, route.Name) } // update CR and make sure Che configmap has been updated cheCR.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(true) if err := cl.Update(context.TODO(), cheCR); err != nil { t.Error("Failed to update CheCluster custom resource") } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } // get configmap and check if identity provider name and workspace project name are correctly set cm = &corev1.ConfigMap{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, cm); err != nil { t.Errorf("ConfigMap %s not found: %s", cm.Name, err) } _, isOpenshiftv4, err := util.DetectOpenShift() if err != nil { logrus.Errorf("Error detecting openshift version: %v", err) } expectedIdentityProviderName := "openshift-v3" if isOpenshiftv4 { expectedIdentityProviderName = "openshift-v4" } if cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"] != expectedIdentityProviderName { t.Errorf("ConfigMap wasn't updated properly. Expecting '%s', got: '%s'", expectedIdentityProviderName, cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"]) } clusterAPI := deploy.ClusterAPI{ Client: r.client, NonCachedClient: r.client, Scheme: r.scheme, } deployContext := &deploy.DeployContext{ CheCluster: cheCR, ClusterAPI: clusterAPI, } if err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.Name, Namespace: cheCR.Namespace}, cheCR); err != nil { t.Errorf("Failed to get the Che custom resource %s: %s", cheCR.Name, err) } if _, err = identity_provider.SyncOpenShiftIdentityProviderItems(deployContext); err != nil { t.Errorf("Failed to create the items for the identity provider: %s", err) } oAuthClientName := cheCR.Spec.Auth.OAuthClientName oauthSecret := cheCR.Spec.Auth.OAuthSecret oAuthClient := &oauth.OAuthClient{} if err = r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil { t.Errorf("Failed to Get oAuthClient %s: %s", oAuthClient.Name, err) } if oAuthClient.Secret != oauthSecret { t.Errorf("Secrets do not match. Expecting %s, got %s", oauthSecret, oAuthClient.Secret) } // check if a new Postgres deployment is not created when spec.Database.ExternalDB is true cheCR.Spec.Database.ExternalDb = true if err := cl.Update(context.TODO(), cheCR); err != nil { t.Error("Failed to update CheCluster custom resource") } postgresDeployment := &appsv1.Deployment{} err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: cheCR.Namespace}, postgresDeployment) err = r.client.Delete(context.TODO(), postgresDeployment) _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: cheCR.Namespace}, postgresDeployment) if err == nil { t.Fatalf("Deployment postgres shoud not exist") } // check of storageClassName ends up in pvc spec fakeStorageClassName := "fake-storage-class-name" cheCR.Spec.Storage.PostgresPVCStorageClassName = fakeStorageClassName cheCR.Spec.Database.ExternalDb = false if err := r.client.Update(context.TODO(), cheCR); err != nil { t.Fatalf("Failed to update %s CR: %s", cheCR.Name, err) } pvc := &corev1.PersistentVolumeClaim{} if err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: cheCR.Namespace}, pvc); err != nil { t.Fatalf("Failed to get PVC: %s", err) } if err = r.client.Delete(context.TODO(), pvc); err != nil { t.Fatalf("Failed to delete PVC %s: %s", pvc.Name, err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } pvc = &corev1.PersistentVolumeClaim{} if err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: cheCR.Namespace}, pvc); err != nil { t.Fatalf("Failed to get PVC: %s", err) } actualStorageClassName := pvc.Spec.StorageClassName if len(*actualStorageClassName) != len(fakeStorageClassName) { t.Fatalf("Expecting %s storageClassName, got %s", fakeStorageClassName, *actualStorageClassName) } // check if oAuthClient is deleted after CR is deleted (finalizer logic) // since fake api does not set deletion timestamp, CR is updated in tests rather than deleted logrus.Info("Updating CR with deletion timestamp") deletionTimestamp := &metav1.Time{Time: time.Now()} cheCR.DeletionTimestamp = deletionTimestamp if err := r.client.Update(context.TODO(), cheCR); err != nil { t.Fatalf("Failed to update CR: %s", err) } if err := deploy.ReconcileOAuthClientFinalizer(deployContext); err != nil { t.Fatal("Failed to reconcile oAuthClient") } oauthClientName := cheCR.Spec.Auth.OAuthClientName oauthClient := &oauth.OAuthClient{} err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName}, oauthClient) if err == nil { t.Fatalf("OauthClient %s has not been deleted", oauthClientName) } logrus.Infof("Disregard the error above. OauthClient %s has been deleted", oauthClientName) } func TestConfiguringLabelsForRoutes(t *testing.T) { util.IsOpenShift = true // Set the logger to development mode for verbose logs. logf.SetLogger(logf.ZapLogger(true)) cl, dc, scheme := Init() // Create a ReconcileChe object with the scheme and fake client r := &ReconcileChe{client: cl, nonCachedClient: cl, scheme: &scheme, discoveryClient: dc, tests: true} // get CR cheCR := &orgv1.CheCluster{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, cheCR); err != nil { t.Errorf("CR not found") } // Mock request to simulate Reconcile() being called on an event for a // watched resource . req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } // reconcile _, err := r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } cheCR.Spec.Server.CheServerRoute.Labels = "route=one" if err := cl.Update(context.TODO(), cheCR); err != nil { t.Error("Failed to update CheCluster custom resource") } // reconcile again _, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) } // get route route := &routev1.Route{} if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultCheFlavor(cheCR), Namespace: cheCR.Namespace}, route); err != nil { t.Errorf("Route %s not found: %s", route.Name, err) } if route.ObjectMeta.Labels["route"] != "one" { t.Fatalf("Route '%s' does not have label '%s'", route.Name, route) } } func TestShouldDelegatePermissionsForCheWorkspaces(t *testing.T) { util.IsOpenShift = true type testCase struct { name string initObjects []runtime.Object clusterRole bool checluster *orgv1.CheCluster } // the same namespace with Che crWsInTheSameNs1 := InitCheWithSimpleCR().DeepCopy() crWsInTheSameNs1.Spec.Server.WorkspaceNamespaceDefault = crWsInTheSameNs1.Namespace crWsInTheSameNs2 := InitCheWithSimpleCR().DeepCopy() crWsInTheSameNs2.Spec.Server.WorkspaceNamespaceDefault = "" crWsInTheSameNs3 := InitCheWithSimpleCR().DeepCopy() crWsInTheSameNs3.Spec.Server.CustomCheProperties = make(map[string]string) crWsInTheSameNs3.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = "" crWsInTheSameNs4 := InitCheWithSimpleCR().DeepCopy() crWsInTheSameNs4.Spec.Server.CustomCheProperties = make(map[string]string) crWsInTheSameNs4.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = crWsInTheSameNs1.Namespace // differ namespace with Che crWsInAnotherNs1 := InitCheWithSimpleCR().DeepCopy() crWsInAnotherNs1.Spec.Server.WorkspaceNamespaceDefault = "some-test-namespace" crWsInAnotherNs2 := InitCheWithSimpleCR().DeepCopy() crWsInAnotherNs2.Spec.Server.CustomCheProperties = make(map[string]string) crWsInAnotherNs2.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = "some-test-namespace" crWsInAnotherNs3 := InitCheWithSimpleCR().DeepCopy() crWsInAnotherNs3.Spec.Server.CustomCheProperties = make(map[string]string) crWsInAnotherNs3.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = crWsInTheSameNs1.Namespace crWsInAnotherNs3.Spec.Server.WorkspaceNamespaceDefault = "some-test-namespace" testCases := []testCase{ { name: "che-operator should delegate permission for workspaces in differ namespace than Che. WorkspaceNamespaceDefault = 'some-test-namespace'", initObjects: []runtime.Object{}, clusterRole: true, checluster: crWsInAnotherNs1, }, { name: "che-operator should delegate permission for workspaces in differ namespace than Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT = 'some-test-namespace'", initObjects: []runtime.Object{}, clusterRole: true, checluster: crWsInAnotherNs2, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.LoggerTo(os.Stdout, true)) scheme := scheme.Scheme orgv1.SchemeBuilder.AddToScheme(scheme) scheme.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient) scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{}) scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.OAuth{}, &configv1.Proxy{}) scheme.AddKnownTypes(routev1.GroupVersion, route) initCR := testCase.checluster initCR.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(false) testCase.initObjects = append(testCase.initObjects, initCR) cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...) clientSet := fakeclientset.NewSimpleClientset() // todo do we need fake discovery fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery) fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{} if !ok { t.Fatal("Error creating fake discovery client") } var m *mocks.MockPermissionChecker if testCase.clusterRole { ctrl := gomock.NewController(t) m = mocks.NewMockPermissionChecker(ctrl) m.EXPECT().GetNotPermittedPolicyRules(gomock.Any(), "").Return([]rbac.PolicyRule{}, nil).MaxTimes(2) defer ctrl.Finish() } r := &ReconcileChe{ client: cli, nonCachedClient: nonCachedClient, discoveryClient: fakeDiscovery, scheme: scheme, permissionChecker: m, tests: true, } req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: name, Namespace: namespace, }, } _, err := r.Reconcile(req) if err != nil { t.Fatalf("Error reconciling: %v", err) } _, err = r.Reconcile(req) if err != nil { t.Fatalf("Error reconciling: %v", err) } if !testCase.clusterRole { viewRole := &rbac.Role{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.ViewRoleName, Namespace: namespace}, viewRole); err != nil { t.Errorf("role '%s' not found", deploy.ViewRoleName) } viewRoleBinding := &rbac.RoleBinding{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ViewRoleBindingName, Namespace: namespace}, viewRoleBinding); err != nil { t.Errorf("rolebinding '%s' not found", ViewRoleBindingName) } execRole := &rbac.Role{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.ExecRoleName, Namespace: namespace}, execRole); err != nil { t.Errorf("role '%s' not found", deploy.ExecRoleName) } execRoleBinding := &rbac.RoleBinding{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ExecRoleBindingName, Namespace: namespace}, execRoleBinding); err != nil { t.Errorf("rolebinding '%s' not found", ExecRoleBindingName) } editRoleBinding := &rbac.RoleBinding{} if err := r.client.Get(context.TODO(), types.NamespacedName{Name: EditRoleBindingName, Namespace: namespace}, editRoleBinding); err != nil { t.Errorf("rolebinding '%s' not found", EditRoleBindingName) } } else { manageNamespacesClusterRoleName := fmt.Sprintf(CheNamespaceEditorClusterRoleNameTemplate, namespace) cheManageNamespaceClusterRole := &rbac.ClusterRole{} if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: manageNamespacesClusterRoleName}, cheManageNamespaceClusterRole); err != nil { t.Errorf("role '%s' not found", manageNamespacesClusterRoleName) } cheManageNamespaceClusterRoleBinding := &rbac.ClusterRoleBinding{} if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: manageNamespacesClusterRoleName}, cheManageNamespaceClusterRoleBinding); err != nil { t.Errorf("rolebinding '%s' not found", manageNamespacesClusterRoleName) } cheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, namespace) cheWorkspacesClusterRole := &rbac.ClusterRole{} if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: cheWorkspacesClusterRoleName}, cheWorkspacesClusterRole); err != nil { t.Errorf("role '%s' not found", cheWorkspacesClusterRole) } cheWorkspacesClusterRoleBinding := &rbac.ClusterRoleBinding{} if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: cheWorkspacesClusterRoleName}, cheWorkspacesClusterRoleBinding); err != nil { t.Errorf("rolebinding '%s' not found", cheWorkspacesClusterRole) } } }) } } func Init() (client.Client, discovery.DiscoveryInterface, runtime.Scheme) { objs, ds, scheme := createAPIObjects() oAuthClient := &oauth.OAuthClient{} users := &userv1.UserList{} user := &userv1.User{} // Register operator types with the runtime scheme scheme.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient) scheme.AddKnownTypes(userv1.SchemeGroupVersion, users, user) scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Proxy{}) // Create a fake client to mock API calls return fake.NewFakeClient(objs...), ds, scheme } func createAPIObjects() ([]runtime.Object, discovery.DiscoveryInterface, runtime.Scheme) { pgPod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: "fake-pg-pod", Namespace: "eclipse-che", Labels: map[string]string{ "component": deploy.PostgresName, }, }, } // A CheCluster custom resource with metadata and spec cheCR := InitCheWithSimpleCR() route := &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ Name: deploy.DefaultCheFlavor(cheCR), Namespace: namespace, }, } packageManifest := &packagesv1.PackageManifest{ ObjectMeta: metav1.ObjectMeta{ Name: "kubernetes-imagepuller-operator", Namespace: namespace, }, } // Objects to track in the fake client. objs := []runtime.Object{ cheCR, pgPod, route, packageManifest, } // Register operator types with the runtime scheme scheme := scheme.Scheme scheme.AddKnownTypes(orgv1.SchemeGroupVersion, cheCR) scheme.AddKnownTypes(routev1.SchemeGroupVersion, route) scheme.AddKnownTypes(console.GroupVersion, &console.ConsoleLink{}) chev1alpha1.AddToScheme(scheme) packagesv1.AddToScheme(scheme) operatorsv1.AddToScheme(scheme) operatorsv1alpha1.AddToScheme(scheme) cli := fakeclientset.NewSimpleClientset() fakeDiscovery, ok := cli.Discovery().(*fakeDiscovery.FakeDiscovery) if !ok { logrus.Error("Error creating fake discovery client") os.Exit(1) } // Create a fake client to mock API calls return objs, fakeDiscovery, *scheme } func InitCheWithSimpleCR() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: orgv1.CheClusterSpec{ // todo add some spec to check controller ifs like external db, ssl etc Server: orgv1.CheClusterSpecServer{ CheWorkspaceClusterRole: "cluster-admin", }, Auth: orgv1.CheClusterSpecAuth{ OpenShiftoAuth: util.NewBoolPointer(false), }, }, } } func InitCheCRWithImagePullerEnabled() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, }, Server: orgv1.CheClusterSpecServer{ ServerExposureStrategy: "multi-host", }, }, } } func InitCheCRWithImagePullerFinalizer() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Finalizers: []string{ "kubernetesimagepullers.finalizers.che.eclipse.org", }, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, }, Server: orgv1.CheClusterSpecServer{ ServerExposureStrategy: "multi-host", }, }, } } func ExpectedCheCRWithImagePullerFinalizer() *orgv1.CheCluster { return &orgv1.CheCluster{ TypeMeta: metav1.TypeMeta{ Kind: "CheCluster", APIVersion: "org.eclipse.che/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Finalizers: []string{ "kubernetesimagepullers.finalizers.che.eclipse.org", }, ResourceVersion: "1", }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, }, Server: orgv1.CheClusterSpecServer{ ServerExposureStrategy: "multi-host", }, }, } } func InitCheCRWithImagePullerDisabled() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: false, }, }, } } func InitCheCRWithImagePullerEnabledAndDefaultValuesSet() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Finalizers: []string{ "kubernetesimagepullers.finalizers.che.eclipse.org", }, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, Spec: chev1alpha1.KubernetesImagePullerSpec{ DeploymentName: "kubernetes-image-puller", ConfigMapName: "k8s-image-puller", }, }, Auth: orgv1.CheClusterSpecAuth{ OpenShiftoAuth: util.NewBoolPointer(false), }, }, } } func InitCheCRWithImagePullerEnabledAndImagesSet() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Finalizers: []string{ "kubernetesimagepullers.finalizers.che.eclipse.org", }, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, Spec: chev1alpha1.KubernetesImagePullerSpec{ DeploymentName: "kubernetes-image-puller", ConfigMapName: "k8s-image-puller", Images: "image=image_url", }, }, Auth: orgv1.CheClusterSpecAuth{ OpenShiftoAuth: util.NewBoolPointer(false), }, }, } } func InitCheCRWithImagePullerEnabledAndNewValuesSet() *orgv1.CheCluster { return &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Finalizers: []string{ "kubernetesimagepullers.finalizers.che.eclipse.org", }, }, Spec: orgv1.CheClusterSpec{ ImagePuller: orgv1.CheClusterSpecImagePuller{ Enable: true, Spec: chev1alpha1.KubernetesImagePullerSpec{ DeploymentName: "kubernetes-image-puller-trigger-update", ConfigMapName: "k8s-image-puller-trigger-update", }, }, Auth: orgv1.CheClusterSpecAuth{ OpenShiftoAuth: util.NewBoolPointer(false), }, }, } }