From dc193493f73b97ce1098be2db1acc82a7913810a Mon Sep 17 00:00:00 2001 From: Anatolii Bazko <abazko@redhat.com> Date: Wed, 8 Dec 2021 12:26:24 +0200 Subject: [PATCH 1/6] chore: Refactoring Signed-off-by: Anatolii Bazko <abazko@redhat.com> --- controllers/che/checluster_controller.go | 184 +---- controllers/che/checluster_controller_test.go | 670 +++--------------- controllers/che/create.go | 261 ------- mocks/permission_checker_mock.go | 50 -- pkg/deploy/consolelink.go | 104 --- pkg/deploy/consolelink_test.go | 111 --- pkg/deploy/dashboard/dashboard.go | 84 +-- .../dashboard/dashboard_deployment_test.go | 137 +--- pkg/deploy/dashboard/dashboard_test.go | 253 ++----- pkg/deploy/dashboard/deployment_dashboard.go | 46 +- pkg/deploy/dashboard/rbac.go | 9 +- pkg/deploy/devfileregistry/devfileregistry.go | 81 ++- .../devfileregistry_configmap.go | 10 +- .../devfileregistry_deployment.go | 16 +- .../devfileregistry_deployment_test.go | 21 +- .../devfileregistry/devfileregistry_test.go | 154 ++-- pkg/deploy/gateway/gateway.go | 22 + .../identity-provider/deployment_keycloak.go | 2 +- .../deployment_keycloak_test.go | 2 +- pkg/deploy/identity-provider/exec.go | 2 +- .../identity-provider/identity_provider.go | 377 ---------- .../identity_provider_test.go | 211 ------ pkg/deploy/identity-provider/init_test.go | 2 +- .../identity-provider/keycloak_readiness.go | 2 +- pkg/deploy/oauthclient.go | 72 -- pkg/deploy/pluginregistry/pluginregistry.go | 88 ++- .../pluginregistry_configmap.go | 10 +- .../pluginregistry_deployment.go | 16 +- .../pluginregistry_deployment_test.go | 26 +- .../pluginregistry/pluginregistry_test.go | 80 +-- pkg/deploy/postgres/postgres.go | 87 +-- pkg/deploy/postgres/postgres_deployment.go | 26 +- pkg/deploy/postgres/postgres_test.go | 75 +- pkg/deploy/server/server.go | 334 --------- pkg/deploy/server/server_configmap.go | 142 ++-- pkg/deploy/server/server_configmap_test.go | 73 +- pkg/deploy/server/server_deployment.go | 62 +- pkg/deploy/server/server_deployment_test.go | 28 +- pkg/deploy/server/server_test.go | 285 -------- pkg/deploy/test_util.go | 5 +- 40 files changed, 750 insertions(+), 3470 deletions(-) delete mode 100644 controllers/che/create.go delete mode 100644 mocks/permission_checker_mock.go delete mode 100644 pkg/deploy/consolelink.go delete mode 100644 pkg/deploy/consolelink_test.go delete mode 100644 pkg/deploy/identity-provider/identity_provider.go delete mode 100644 pkg/deploy/identity-provider/identity_provider_test.go delete mode 100644 pkg/deploy/oauthclient.go delete mode 100644 pkg/deploy/server/server.go delete mode 100644 pkg/deploy/server/server_test.go diff --git a/controllers/che/checluster_controller.go b/controllers/che/checluster_controller.go index a714ec2840..27fb97da51 100644 --- a/controllers/che/checluster_controller.go +++ b/controllers/che/checluster_controller.go @@ -17,10 +17,12 @@ import ( "time" "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/deploy/consolelink" "github.com/eclipse-che/che-operator/pkg/deploy/dashboard" devworkspace "github.com/eclipse-che/che-operator/pkg/deploy/dev-workspace" "github.com/eclipse-che/che-operator/pkg/deploy/devfileregistry" "github.com/eclipse-che/che-operator/pkg/deploy/gateway" + identityprovider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider" imagepuller "github.com/eclipse-che/che-operator/pkg/deploy/image-puller" "github.com/eclipse-che/che-operator/pkg/deploy/migration" openshiftoauth "github.com/eclipse-che/che-operator/pkg/deploy/openshift-oauth" @@ -30,7 +32,6 @@ import ( "github.com/eclipse-che/che-operator/pkg/deploy/server" "github.com/eclipse-che/che-operator/pkg/deploy/tls" - identity_provider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider" "github.com/eclipse-che/che-operator/pkg/util" "github.com/go-logr/logr" routev1 "github.com/openshift/api/route/v1" @@ -46,7 +47,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" orgv1 "github.com/eclipse-che/che-operator/api/v1" @@ -71,7 +71,6 @@ type CheClusterReconciler struct { // A discovery client to check for the existence of certain APIs registered // in the API Server discoveryClient discovery.DiscoveryInterface - tests bool reconcileManager *deploy.ReconcileManager // the namespace to which to limit the reconciliation. If empty, all namespaces are considered namespace string @@ -103,6 +102,19 @@ func NewReconciler( reconcileManager.RegisterReconciler(rbac.NewCheServerPermissionsReconciler()) reconcileManager.RegisterReconciler(rbac.NewGatewayPermissionsReconciler()) reconcileManager.RegisterReconciler(rbac.NewWorkspacePermissionsReconciler()) + reconcileManager.RegisterReconciler(server.NewDefaultValuesReconciler()) + + // we have to expose che endpoint independently of syncing other server + // resources since che host is used for dashboard deployment and che config map + reconcileManager.RegisterReconciler(server.NewCheHostReconciler()) + reconcileManager.RegisterReconciler(postgres.NewPostgresReconciler()) + reconcileManager.RegisterReconciler(identityprovider.NewIdentityProviderReconciler()) + reconcileManager.RegisterReconciler(devfileregistry.NewDevfileRegistryReconciler()) + reconcileManager.RegisterReconciler(pluginregistry.NewPluginRegistryReconciler()) + reconcileManager.RegisterReconciler(dashboard.NewDashboardReconciler()) + reconcileManager.RegisterReconciler(gateway.NewGatewayReconciler()) + reconcileManager.RegisterReconciler(server.NewServerReconciler()) + reconcileManager.RegisterReconciler(consolelink.NewConsoleLinkReconciler()) return &CheClusterReconciler{ Scheme: scheme, @@ -298,177 +310,17 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) result, done, err := r.reconcileManager.ReconcileAll(deployContext) if !done { return result, err - // TODO: uncomment when all items added to ReconcilerManager - // } else { - // logrus.Info("Successfully reconciled.") - // return ctrl.Result{}, nil + } else { + logrus.Info("Successfully reconciled.") + return ctrl.Result{}, nil } } else { r.reconcileManager.FinalizeAll(deployContext) } - // Reconcile finalizers before CR is deleted - // TODO remove in favor of r.reconcileManager.FinalizeAll(deployContext) - r.reconcileFinalizers(deployContext) - - if err := r.GenerateAndSaveFields(deployContext); err != nil { - _ = deploy.ReloadCheClusterCR(deployContext) - return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 1}, err - } - - if !deployContext.CheCluster.Spec.Database.ExternalDb { - postgres := postgres.NewPostgres(deployContext) - done, err := postgres.SyncAll() - if !done { - if err != nil { - logrus.Error(err) - } - return ctrl.Result{}, err - } - } - - // we have to expose che endpoint independently of syncing other server - // resources since che host is used for dashboard deployment and che config map - server := server.NewServer(deployContext) - done, err := server.ExposeCheServiceAndEndpoint() - if !done { - if err != nil { - logrus.Error(err) - } - return ctrl.Result{}, err - } - - // create and provision Keycloak related objects - if !checluster.Spec.Auth.ExternalIdentityProvider { - provisioned, err := identity_provider.SyncIdentityProviderToCluster(deployContext) - if !provisioned { - if err != nil { - logrus.Errorf("Error provisioning the identity provider to cluster: %v", err) - } - return ctrl.Result{}, err - } - } else { - keycloakURL := checluster.Spec.Auth.IdentityProviderURL - if checluster.Status.KeycloakURL != keycloakURL { - checluster.Status.KeycloakURL = keycloakURL - if err := deploy.UpdateCheCRStatus(deployContext, "status: Keycloak URL", keycloakURL); err != nil { - return reconcile.Result{}, err - } - } - } - - devfileRegistry := devfileregistry.NewDevfileRegistry(deployContext) - if !checluster.Spec.Server.ExternalDevfileRegistry { - done, err := devfileRegistry.SyncAll() - if !done { - if err != nil { - logrus.Error(err) - } - return ctrl.Result{}, err - } - } - - if !checluster.Spec.Server.ExternalPluginRegistry { - pluginRegistry := pluginregistry.NewPluginRegistry(deployContext) - done, err := pluginRegistry.SyncAll() - if !done { - if err != nil { - logrus.Error(err) - } - return ctrl.Result{}, err - } - } else { - if checluster.Spec.Server.PluginRegistryUrl != checluster.Status.PluginRegistryURL { - checluster.Status.PluginRegistryURL = checluster.Spec.Server.PluginRegistryUrl - if err := deploy.UpdateCheCRStatus(deployContext, "status: Plugin Registry URL", checluster.Spec.Server.PluginRegistryUrl); err != nil { - return reconcile.Result{}, err - } - } - } - - d := dashboard.NewDashboard(deployContext) - done, err = d.Reconcile() - if !done { - if err != nil { - logrus.Errorf("Error provisioning '%s' to cluster: %v", d.GetComponentName(), err) - } - return ctrl.Result{}, err - } - - err = gateway.SyncGatewayToCluster(deployContext) - if err != nil { - logrus.Errorf("Failed to create the Server Gateway: %s", err) - return ctrl.Result{}, err - } - - done, err = server.SyncAll() - if !done { - if err != nil { - logrus.Error(err) - } - return reconcile.Result{}, err - } - - // we can now try to create consolelink, after che instance is available - done, err = deploy.ReconcileConsoleLink(deployContext) - if !done { - if err != nil { - logrus.Error(err) - } - // We should `Requeue` since we created cluster object - return ctrl.Result{RequeueAfter: time.Second}, err - } - - // Delete OpenShift identity provider if OpenShift oAuth is false in spec - // but OpenShiftoAuthProvisioned is true in CR status, e.g. when oAuth has been turned on and then turned off - deleted, err := identity_provider.ReconcileIdentityProvider(deployContext) - if deleted { - // ignore error - deploy.DeleteFinalizer(deployContext, deploy.OAuthFinalizerName) - for { - checluster.Status.OpenShiftoAuthProvisioned = false - if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "false"); err != nil && - errors.IsConflict(err) { - _ = deploy.ReloadCheClusterCR(deployContext) - continue - } - break - } - for { - checluster.Spec.Auth.OAuthSecret = "" - checluster.Spec.Auth.OAuthClientName = "" - if err := deploy.UpdateCheCRStatus(deployContext, "clean oAuth secret name and client name", ""); err != nil && - errors.IsConflict(err) { - _ = deploy.ReloadCheClusterCR(deployContext) - continue - } - break - } - } - - logrus.Info("Successfully reconciled.") return ctrl.Result{}, nil } -func (r *CheClusterReconciler) reconcileFinalizers(deployContext *deploy.DeployContext) { - if util.IsOpenShift && deployContext.CheCluster.IsOpenShiftOAuthEnabled() { - if err := deploy.ReconcileOAuthClientFinalizer(deployContext); err != nil { - logrus.Error(err) - } - } - - if err := deploy.ReconcileConsoleLinkFinalizer(deployContext); err != nil { - logrus.Error(err) - } - - if !deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() { - done, err := dashboard.NewDashboard(deployContext).Finalize() - if !done { - logrus.Error(err) - } - } -} - func (r *CheClusterReconciler) GetCR(request ctrl.Request) (instance *orgv1.CheCluster, err error) { instance = &orgv1.CheCluster{} err = r.client.Get(context.TODO(), request.NamespacedName, instance) diff --git a/controllers/che/checluster_controller_test.go b/controllers/che/checluster_controller_test.go index efd6904b97..44e63f32fe 100644 --- a/controllers/che/checluster_controller_test.go +++ b/controllers/che/checluster_controller_test.go @@ -15,17 +15,11 @@ package che import ( "context" "os" - "strconv" - "reflect" "time" chev1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1" - crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - - devworkspace "github.com/eclipse-che/che-operator/pkg/deploy/dev-workspace" - identity_provider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider" - "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/util" @@ -44,9 +38,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/utils/pointer" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -58,8 +50,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" configv1 "github.com/openshift/api/config/v1" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" "testing" @@ -69,350 +59,21 @@ var ( namespace = "eclipse-che" ) -func TestNativeUserModeEnabled(t *testing.T) { - type testCase struct { - name string - initObjects []runtime.Object - isOpenshift bool - devworkspaceEnabled bool - initialNativeUserValue *bool - expectedNativeUserValue *bool - } - - testCases := []testCase{ - { - name: "che-operator should use nativeUserMode when devworkspaces on openshift and no initial value in CR for nativeUserMode", - isOpenshift: true, - devworkspaceEnabled: true, - initialNativeUserValue: nil, - expectedNativeUserValue: pointer.BoolPtr(true), - }, - { - name: "che-operator should use nativeUserMode value from initial CR", - isOpenshift: true, - devworkspaceEnabled: true, - initialNativeUserValue: pointer.BoolPtr(false), - expectedNativeUserValue: pointer.BoolPtr(false), - }, - { - name: "che-operator should use nativeUserMode value from initial CR", - isOpenshift: true, - devworkspaceEnabled: true, - initialNativeUserValue: pointer.BoolPtr(true), - expectedNativeUserValue: pointer.BoolPtr(true), - }, - { - name: "che-operator should use nativeUserMode when devworkspaces on kubernetes and no initial value in CR for nativeUserMode", - isOpenshift: false, - devworkspaceEnabled: true, - initialNativeUserValue: nil, - expectedNativeUserValue: pointer.BoolPtr(true), - }, - { - name: "che-operator not modify nativeUserMode when devworkspace not enabled", - isOpenshift: true, - devworkspaceEnabled: false, - initialNativeUserValue: nil, - expectedNativeUserValue: nil, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - - scheme := scheme.Scheme - orgv1.SchemeBuilder.AddToScheme(scheme) - scheme.AddKnownTypes(routev1.GroupVersion, &routev1.Route{}) - scheme.AddKnownTypes(oauthv1.SchemeGroupVersion, &oauthv1.OAuthClient{}) - scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Proxy{}) - scheme.AddKnownTypes(crdv1.SchemeGroupVersion, &crdv1.CustomResourceDefinition{}) - - initCR := InitCheWithSimpleCR().DeepCopy() - initCR.Spec.DevWorkspace.Enable = testCase.devworkspaceEnabled - initCR.Spec.Auth.NativeUserMode = testCase.initialNativeUserValue - testCase.initObjects = append(testCase.initObjects, initCR) - - util.IsOpenShift = testCase.isOpenshift - - // reread templates (workaround after setting IsOpenShift value) - devworkspace.DevWorkspaceTemplates = devworkspace.DevWorkspaceTemplatesPath() - devworkspace.DevWorkspaceIssuerFile = devworkspace.DevWorkspaceTemplates + "/devworkspace-controller-selfsigned-issuer.Issuer.yaml" - devworkspace.DevWorkspaceCertificateFile = devworkspace.DevWorkspaceTemplates + "/devworkspace-controller-serving-cert.Certificate.yaml" - - 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 := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "") - r.tests = true - - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: os.Getenv("CHE_FLAVOR"), - Namespace: namespace, - }, - } - - _, err := r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("Error reconciling: %v", err) - } - cr := &orgv1.CheCluster{} - if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cr); err != nil { - t.Errorf("CR not found") - } - - if !reflect.DeepEqual(testCase.expectedNativeUserValue, cr.Spec.Auth.NativeUserMode) { - expectedValue, actualValue := "nil", "nil" - if testCase.expectedNativeUserValue != nil { - expectedValue = strconv.FormatBool(*testCase.expectedNativeUserValue) - } - if cr.Spec.Auth.NativeUserMode != nil { - actualValue = strconv.FormatBool(*cr.Spec.Auth.NativeUserMode) - } - - t.Errorf("Expected nativeUserMode '%+v', but found '%+v' for input '%+v'", - expectedValue, actualValue, testCase) - } - }) - } -} - -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.New(zap.WriteTo(os.Stdout), zap.UseDevMode(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 := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "") - r.tests = true - - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: os.Getenv("CHE_FLAVOR"), - Namespace: namespace, - }, - } - - util.IsOpenShift = true - util.IsOpenShift4 = false - _, err := r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("Error reconciling: %v", err) - } - cr := &orgv1.CheCluster{} - if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), 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: os.Getenv("CHE_FLAVOR"), - }, - 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: os.Getenv("CHE_FLAVOR"), - }, - 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: os.Getenv("CHE_FLAVOR"), - }, - 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.New(zap.WriteTo(os.Stdout), zap.UseDevMode(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 := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "") - r.tests = true - - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: os.Getenv("CHE_FLAVOR"), - Namespace: namespace, - }, - } - - util.IsOpenShift = testCase.isOpenShift - util.IsOpenShift4 = testCase.isOpenShift4 - - _, err := r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("Error reconciling: %v", err) - } - - cr := &orgv1.CheCluster{} - if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), 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 TestCheController(t *testing.T) { var err error util.IsOpenShift = true - util.IsOpenShift4 = false + util.IsOpenShift4 = true cl, dc, scheme := Init() // Create a ReconcileChe object with the scheme and fake client r := NewReconciler(cl, cl, dc, &scheme, "") - r.tests = true // get CR - cheCR := &orgv1.CheCluster{ - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{ - CheHost: "eclipse.org", - }, - }, - } - if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil { - t.Errorf("CR not found") - } + checluster := &orgv1.CheCluster{} + err = cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, checluster) + assert.Nil(t, err) // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -423,270 +84,123 @@ func TestCheController(t *testing.T) { }, } - reconcileLoops := 4 - for i := 0; i < reconcileLoops; i++ { - _, err = r.Reconcile(context.TODO(), 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) - } + _, err = r.Reconcile(context.TODO(), req) + assert.Nil(t, err) - // get plugin-registry configmap - pluginRegistrycm := &corev1.ConfigMap{} - if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DevfileRegistryName, Namespace: cheCR.Namespace}, pluginRegistrycm); err != nil { - t.Errorf("ConfigMap %s not found: %s", pluginRegistrycm.Name, err) - } + assert.True(t, util.IsObjectExists(cl, types.NamespacedName{Name: deploy.DevfileRegistryName, Namespace: checluster.Namespace}, &corev1.ConfigMap{})) + assert.True(t, util.IsObjectExists(cl, types.NamespacedName{Name: deploy.PluginRegistryName, Namespace: checluster.Namespace}, &corev1.ConfigMap{})) - // get CR - if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil { - t.Errorf("CR not found") - } + // reade checluster + err = cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, checluster) + assert.Nil(t, 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") - } + checluster.Spec.Server.TlsSupport = true + err = cl.Update(context.TODO(), checluster) + assert.Nil(t, err) - // reconcile again + // reconcile several times + reconcileLoops := 4 for i := 0; i < reconcileLoops; i++ { _, err = r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } + assert.Nil(t, 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{} + err = cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: checluster.Namespace}, cm) + assert.Nil(t, err) + assert.Equal(t, cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"], "true") // 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") - } + assert.False(t, util.IsObjectExists(cl, types.NamespacedName{Name: "custom", Namespace: checluster.Namespace}, &corev1.ConfigMap{})) // Get the custom role binding that should have been created for the role we passed in - rb := &rbacv1.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) - } + assert.True(t, util.IsObjectExists(cl, types.NamespacedName{Name: "che-workspace-custom", Namespace: checluster.Namespace}, &rbacv1.RoleBinding{})) - // run a few checks to make sure the operator reconciled tls routes and updated configmap - if cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] != "true" { - // If the test fails here without obvious reason, it could mean that there was not enought reconcile loops before. - // To fix the above problem, just increase reconcileLoops variable above. - t.Errorf("ConfigMap wasn't updated. Expecting true, but 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) - } + err = cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultCheFlavor(checluster), Namespace: checluster.Namespace}, route) + assert.Nil(t, err) + assert.Equal(t, route.Spec.TLS.Termination, routev1.TLSTerminationType("edge")) - // get CR - if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil { - t.Errorf("CR not found") - } + // reread checluster + err = cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, checluster) + assert.Nil(t, err) // 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") - } + checluster.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(true) + err = cl.Update(context.TODO(), checluster) + assert.Nil(t, err) _, err = r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } + assert.Nil(t, 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, - NonCachingClient: r.client, - Scheme: r.Scheme, - } + err = cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: checluster.Namespace}, cm) + assert.Nil(t, err) + assert.Equal(t, cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"], "openshift-v4") - 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 := &oauthv1.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) - } + // reread checluster + err = cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, checluster) + assert.Nil(t, err) + assert.True(t, util.IsObjectExists(cl, types.NamespacedName{Name: checluster.Spec.Auth.OAuthClientName}, &oauthv1.OAuthClient{})) // 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") - } + checluster.Spec.Database.ExternalDb = true + err = cl.Update(context.TODO(), checluster) + assert.Nil(t, err) + postgresDeployment := &appsv1.Deployment{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: cheCR.Namespace}, postgresDeployment) + err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: checluster.Namespace}, postgresDeployment) + assert.Nil(t, err) + err = r.client.Delete(context.TODO(), postgresDeployment) + assert.Nil(t, err) + _, err = r.Reconcile(context.TODO(), 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") - } + assert.Nil(t, err) + + assert.False(t, util.IsObjectExists(cl, types.NamespacedName{Name: deploy.PostgresName, Namespace: checluster.Namespace}, &appsv1.Deployment{})) // 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) - } + checluster.Spec.Storage.PostgresPVCStorageClassName = fakeStorageClassName + checluster.Spec.Database.ExternalDb = false + err = r.client.Update(context.TODO(), checluster) + assert.Nil(t, 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.client.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: checluster.Namespace}, pvc) + assert.Nil(t, err) + + err = r.client.Delete(context.TODO(), pvc) + assert.Nil(t, err) + _, err = r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } + assert.Nil(t, 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) - } + err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: checluster.Namespace}, pvc) + assert.Nil(t, err) + assert.Equal(t, fakeStorageClassName, *pvc.Spec.StorageClassName) - // Get CheCR one more time to get it with newer Che url in the status. - r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.GetName(), Namespace: cheCR.GetNamespace()}, cheCR) - if err != nil { - t.Fatalf("Failed to get custom resource Eclipse Che: %s", err.Error()) - } - if cheCR.Status.CheURL != "https://eclipse.org" { - t.Fatalf("Expected che host url in the custom resource status: %s, but got %s", "https://eclipse.org", cheCR.Status.CheURL) - } + // reread checluster + err = cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, checluster) + assert.Nil(t, err) + assert.Equal(t, "https://eclipse.org", checluster.Status.CheURL) // 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 := &oauthv1.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(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - - cl, dc, scheme := Init() - - // Create a ReconcileChe object with the scheme and fake client - r := NewReconciler(cl, cl, dc, &scheme, "") - r.tests = true - - // get CR - cheCR := &orgv1.CheCluster{} - if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil { - t.Errorf("CR not found") - } + checluster.DeletionTimestamp = deletionTimestamp + err = r.client.Update(context.TODO(), checluster) + assert.Nil(t, err) - // Mock request to simulate Reconcile() being called on an event for a - // watched resource . - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: os.Getenv("CHE_FLAVOR"), - Namespace: namespace, - }, - } - - // reconcile - _, err := r.Reconcile(context.TODO(), req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil { - t.Errorf("CR not found") - } - - 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(context.TODO(), req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } + assert.Nil(t, 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) - } + assert.False(t, util.IsObjectExists(cl, types.NamespacedName{Name: checluster.Spec.Auth.OAuthClientName}, &oauthv1.OAuthClient{})) } func Init() (client.Client, discovery.DiscoveryInterface, runtime.Scheme) { @@ -721,7 +235,18 @@ func createAPIObjects() ([]runtime.Object, discovery.DiscoveryInterface, runtime } // A CheCluster custom resource with metadata and spec - cheCR := InitCheWithSimpleCR() + cheCR := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: os.Getenv("CHE_FLAVOR"), + Namespace: namespace, + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + CheHost: "eclipse.org", + CheWorkspaceClusterRole: "cluster-admin", + }, + }, + } route := &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ @@ -762,20 +287,3 @@ func createAPIObjects() ([]runtime.Object, discovery.DiscoveryInterface, runtime // Create a fake client to mock API calls return objs, fakeDiscovery, *scheme } - -func InitCheWithSimpleCR() *orgv1.CheCluster { - return &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: os.Getenv("CHE_FLAVOR"), - Namespace: namespace, - }, - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{ - CheWorkspaceClusterRole: "cluster-admin", - }, - Auth: orgv1.CheClusterSpecAuth{ - OpenShiftoAuth: pointer.BoolPtr(false), - }, - }, - } -} diff --git a/controllers/che/create.go b/controllers/che/create.go deleted file mode 100644 index 1f145d1d7a..0000000000 --- a/controllers/che/create.go +++ /dev/null @@ -1,261 +0,0 @@ -// -// Copyright (c) 2019-2021 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 ( - "strconv" - - "github.com/eclipse-che/che-operator/pkg/deploy" - "github.com/eclipse-che/che-operator/pkg/util" - "github.com/sirupsen/logrus" - appsv1 "k8s.io/api/apps/v1" -) - -func (r *CheClusterReconciler) GenerateAndSaveFields(deployContext *deploy.DeployContext) (err error) { - cheFlavor := deploy.DefaultCheFlavor(deployContext.CheCluster) - cheNamespace := deployContext.CheCluster.Namespace - if len(deployContext.CheCluster.Spec.Server.CheFlavor) < 1 { - deployContext.CheCluster.Spec.Server.CheFlavor = cheFlavor - if err := deploy.UpdateCheCRSpec(deployContext, "installation flavor", cheFlavor); err != nil { - return err - } - } - - if len(deployContext.CheCluster.Spec.Database.ChePostgresSecret) < 1 { - if len(deployContext.CheCluster.Spec.Database.ChePostgresUser) < 1 || len(deployContext.CheCluster.Spec.Database.ChePostgresPassword) < 1 { - chePostgresSecret := deploy.DefaultChePostgresSecret() - _, err := deploy.SyncSecretToCluster(deployContext, chePostgresSecret, cheNamespace, map[string][]byte{"user": []byte(deploy.DefaultChePostgresUser), "password": []byte(util.GeneratePasswd(12))}) - if err != nil { - return err - } - deployContext.CheCluster.Spec.Database.ChePostgresSecret = chePostgresSecret - if err := deploy.UpdateCheCRSpec(deployContext, "Postgres Secret", chePostgresSecret); err != nil { - return err - } - } else { - if len(deployContext.CheCluster.Spec.Database.ChePostgresUser) < 1 { - deployContext.CheCluster.Spec.Database.ChePostgresUser = deploy.DefaultChePostgresUser - if err := deploy.UpdateCheCRSpec(deployContext, "Postgres User", deployContext.CheCluster.Spec.Database.ChePostgresUser); err != nil { - return err - } - } - if len(deployContext.CheCluster.Spec.Database.ChePostgresPassword) < 1 { - deployContext.CheCluster.Spec.Database.ChePostgresPassword = util.GeneratePasswd(12) - if err := deploy.UpdateCheCRSpec(deployContext, "auto-generated CheCluster DB password", "password-hidden"); err != nil { - return err - } - } - } - } - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderPostgresSecret) < 1 { - keycloakPostgresPassword := util.GeneratePasswd(12) - keycloakDeployment := &appsv1.Deployment{} - exists, err := deploy.GetNamespacedObject(deployContext, deploy.IdentityProviderName, keycloakDeployment) - if err != nil { - logrus.Error(err) - } - if exists { - keycloakPostgresPassword = util.GetDeploymentEnv(keycloakDeployment, "DB_PASSWORD") - } - - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderPostgresPassword) < 1 { - identityPostgresSecret := deploy.DefaultCheIdentityPostgresSecret() - _, err := deploy.SyncSecretToCluster(deployContext, identityPostgresSecret, cheNamespace, map[string][]byte{"password": []byte(keycloakPostgresPassword)}) - if err != nil { - return err - } - deployContext.CheCluster.Spec.Auth.IdentityProviderPostgresSecret = identityPostgresSecret - if err := deploy.UpdateCheCRSpec(deployContext, "Identity Provider Postgres Secret", identityPostgresSecret); err != nil { - return err - } - } - } - - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderSecret) < 1 { - keycloakAdminUserName := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderAdminUserName, "admin") - keycloakAdminPassword := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderPassword, util.GeneratePasswd(12)) - - keycloakDeployment := &appsv1.Deployment{} - exists, _ := deploy.GetNamespacedObject(deployContext, deploy.IdentityProviderName, keycloakDeployment) - if exists { - keycloakAdminUserName = util.GetDeploymentEnv(keycloakDeployment, "SSO_ADMIN_USERNAME") - keycloakAdminPassword = util.GetDeploymentEnv(keycloakDeployment, "SSO_ADMIN_PASSWORD") - } - - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderAdminUserName) < 1 || len(deployContext.CheCluster.Spec.Auth.IdentityProviderPassword) < 1 { - identityProviderSecret := deploy.DefaultCheIdentitySecret() - _, err = deploy.SyncSecretToCluster(deployContext, identityProviderSecret, cheNamespace, map[string][]byte{"user": []byte(keycloakAdminUserName), "password": []byte(keycloakAdminPassword)}) - if err != nil { - return err - } - deployContext.CheCluster.Spec.Auth.IdentityProviderSecret = identityProviderSecret - if err := deploy.UpdateCheCRSpec(deployContext, "Identity Provider Secret", identityProviderSecret); err != nil { - return err - } - } else { - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderPassword) < 1 { - deployContext.CheCluster.Spec.Auth.IdentityProviderPassword = keycloakAdminPassword - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak admin password", "password hidden"); err != nil { - return err - } - } - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderAdminUserName) < 1 { - deployContext.CheCluster.Spec.Auth.IdentityProviderAdminUserName = keycloakAdminUserName - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak admin username", keycloakAdminUserName); err != nil { - return err - } - } - } - } - - chePostgresDb := util.GetValue(deployContext.CheCluster.Spec.Database.ChePostgresDb, "dbche") - if len(deployContext.CheCluster.Spec.Database.ChePostgresDb) < 1 { - deployContext.CheCluster.Spec.Database.ChePostgresDb = chePostgresDb - if err := deploy.UpdateCheCRSpec(deployContext, "Postgres DB", chePostgresDb); err != nil { - return err - } - } - chePostgresHostName := util.GetValue(deployContext.CheCluster.Spec.Database.ChePostgresHostName, deploy.DefaultChePostgresHostName) - if len(deployContext.CheCluster.Spec.Database.ChePostgresHostName) < 1 { - deployContext.CheCluster.Spec.Database.ChePostgresHostName = chePostgresHostName - if err := deploy.UpdateCheCRSpec(deployContext, "Postgres hostname", chePostgresHostName); err != nil { - return err - } - } - chePostgresPort := util.GetValue(deployContext.CheCluster.Spec.Database.ChePostgresPort, deploy.DefaultChePostgresPort) - if len(deployContext.CheCluster.Spec.Database.ChePostgresPort) < 1 { - deployContext.CheCluster.Spec.Database.ChePostgresPort = chePostgresPort - if err := deploy.UpdateCheCRSpec(deployContext, "Postgres port", chePostgresPort); err != nil { - return err - } - } - - if !deployContext.CheCluster.IsNativeUserModeEnabled() { - keycloakRealm := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor) - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm) < 1 { - deployContext.CheCluster.Spec.Auth.IdentityProviderRealm = keycloakRealm - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak realm", keycloakRealm); err != nil { - return err - } - } - keycloakClientId := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderClientId) < 1 { - deployContext.CheCluster.Spec.Auth.IdentityProviderClientId = keycloakClientId - - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak client ID", keycloakClientId); err != nil { - return err - } - } - } - - cheLogLevel := util.GetValue(deployContext.CheCluster.Spec.Server.CheLogLevel, deploy.DefaultCheLogLevel) - if len(deployContext.CheCluster.Spec.Server.CheLogLevel) < 1 { - deployContext.CheCluster.Spec.Server.CheLogLevel = cheLogLevel - if err := deploy.UpdateCheCRSpec(deployContext, "log level", cheLogLevel); err != nil { - return err - } - } - cheDebug := util.GetValue(deployContext.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) - if len(deployContext.CheCluster.Spec.Server.CheDebug) < 1 { - deployContext.CheCluster.Spec.Server.CheDebug = cheDebug - if err := deploy.UpdateCheCRSpec(deployContext, "debug", cheDebug); err != nil { - return err - } - } - pvcStrategy := util.GetValue(deployContext.CheCluster.Spec.Storage.PvcStrategy, deploy.DefaultPvcStrategy) - if len(deployContext.CheCluster.Spec.Storage.PvcStrategy) < 1 { - deployContext.CheCluster.Spec.Storage.PvcStrategy = pvcStrategy - if err := deploy.UpdateCheCRSpec(deployContext, "pvc strategy", pvcStrategy); err != nil { - return err - } - } - pvcClaimSize := util.GetValue(deployContext.CheCluster.Spec.Storage.PvcClaimSize, deploy.DefaultPvcClaimSize) - if len(deployContext.CheCluster.Spec.Storage.PvcClaimSize) < 1 { - deployContext.CheCluster.Spec.Storage.PvcClaimSize = pvcClaimSize - if err := deploy.UpdateCheCRSpec(deployContext, "pvc claim size", pvcClaimSize); err != nil { - return err - } - } - - // This is only to correctly manage defaults during the transition - // from Upstream 7.0.0 GA to the next - // version that should fixed bug https://github.com/eclipse/che/issues/13714 - // Or for the transition from CRW 1.2 to 2.0 - - if deployContext.CheCluster.Spec.Storage.PvcJobsImage == deploy.OldDefaultPvcJobsUpstreamImageToDetect || - (deploy.MigratingToCRW2_0(deployContext.CheCluster) && deployContext.CheCluster.Spec.Storage.PvcJobsImage != "") { - deployContext.CheCluster.Spec.Storage.PvcJobsImage = "" - if err := deploy.UpdateCheCRSpec(deployContext, "pvc jobs image", deployContext.CheCluster.Spec.Storage.PvcJobsImage); err != nil { - return err - } - } - - if deployContext.CheCluster.Spec.Database.PostgresImage == deploy.OldDefaultPostgresUpstreamImageToDetect || - (deploy.MigratingToCRW2_0(deployContext.CheCluster) && deployContext.CheCluster.Spec.Database.PostgresImage != "") { - deployContext.CheCluster.Spec.Database.PostgresImage = "" - if err := deploy.UpdateCheCRSpec(deployContext, "postgres image", deployContext.CheCluster.Spec.Database.PostgresImage); err != nil { - return err - } - } - - if deployContext.CheCluster.Spec.Auth.IdentityProviderImage == deploy.OldDefaultKeycloakUpstreamImageToDetect || - (deploy.MigratingToCRW2_0(deployContext.CheCluster) && deployContext.CheCluster.Spec.Auth.IdentityProviderImage != "") { - deployContext.CheCluster.Spec.Auth.IdentityProviderImage = "" - if err := deploy.UpdateCheCRSpec(deployContext, "keycloak image", deployContext.CheCluster.Spec.Auth.IdentityProviderImage); err != nil { - return err - } - } - - if deploy.MigratingToCRW2_0(deployContext.CheCluster) && - !deployContext.CheCluster.Spec.Server.ExternalPluginRegistry && - deployContext.CheCluster.Spec.Server.PluginRegistryUrl == deploy.OldCrwPluginRegistryUrl { - deployContext.CheCluster.Spec.Server.PluginRegistryUrl = "" - if err := deploy.UpdateCheCRSpec(deployContext, "plugin registry url", deployContext.CheCluster.Spec.Server.PluginRegistryUrl); err != nil { - return err - } - } - - if deploy.MigratingToCRW2_0(deployContext.CheCluster) && - deployContext.CheCluster.Spec.Server.CheImage == deploy.OldDefaultCodeReadyServerImageRepo { - deployContext.CheCluster.Spec.Server.CheImage = "" - if err := deploy.UpdateCheCRSpec(deployContext, "che image repo", deployContext.CheCluster.Spec.Server.CheImage); err != nil { - return err - } - } - - if deploy.MigratingToCRW2_0(deployContext.CheCluster) && - deployContext.CheCluster.Spec.Server.CheImageTag == deploy.OldDefaultCodeReadyServerImageTag { - deployContext.CheCluster.Spec.Server.CheImageTag = "" - if err := deploy.UpdateCheCRSpec(deployContext, "che image tag", deployContext.CheCluster.Spec.Server.CheImageTag); err != nil { - return err - } - } - - if deployContext.CheCluster.Spec.Server.ServerExposureStrategy == "" && deployContext.CheCluster.Spec.K8s.IngressStrategy == "" { - strategy := util.GetServerExposureStrategy(deployContext.CheCluster) - deployContext.CheCluster.Spec.Server.ServerExposureStrategy = strategy - if err := deploy.UpdateCheCRSpec(deployContext, "serverExposureStrategy", strategy); err != nil { - return err - } - } - - if deployContext.CheCluster.Spec.DevWorkspace.Enable && deployContext.CheCluster.Spec.Auth.NativeUserMode == nil { - newNativeUserModeValue := util.NewBoolPointer(true) - deployContext.CheCluster.Spec.Auth.NativeUserMode = newNativeUserModeValue - if err := deploy.UpdateCheCRSpec(deployContext, "nativeUserMode", strconv.FormatBool(*newNativeUserModeValue)); err != nil { - return err - } - } - - return nil -} diff --git a/mocks/permission_checker_mock.go b/mocks/permission_checker_mock.go deleted file mode 100644 index fe12700d19..0000000000 --- a/mocks/permission_checker_mock.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: pkg/controller/che/permission_checker.go - -// Package mock_che is a generated GoMock package. -package mock_che - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - v1 "k8s.io/api/rbac/v1" -) - -// MockPermissionChecker is a mock of PermissionChecker interface -type MockPermissionChecker struct { - ctrl *gomock.Controller - recorder *MockPermissionCheckerMockRecorder -} - -// MockPermissionCheckerMockRecorder is the mock recorder for MockPermissionChecker -type MockPermissionCheckerMockRecorder struct { - mock *MockPermissionChecker -} - -// NewMockPermissionChecker creates a new mock instance -func NewMockPermissionChecker(ctrl *gomock.Controller) *MockPermissionChecker { - mock := &MockPermissionChecker{ctrl: ctrl} - mock.recorder = &MockPermissionCheckerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockPermissionChecker) EXPECT() *MockPermissionCheckerMockRecorder { - return m.recorder -} - -// GetNotPermittedPolicyRules mocks base method -func (m *MockPermissionChecker) GetNotPermittedPolicyRules(policies []v1.PolicyRule, namespace string) ([]v1.PolicyRule, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetNotPermittedPolicyRules", policies, namespace) - ret0, _ := ret[0].([]v1.PolicyRule) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetNotPermittedPolicyRules indicates an expected call of GetNotPermittedPolicyRules -func (mr *MockPermissionCheckerMockRecorder) GetNotPermittedPolicyRules(policies, namespace interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotPermittedPolicyRules", reflect.TypeOf((*MockPermissionChecker)(nil).GetNotPermittedPolicyRules), policies, namespace) -} diff --git a/pkg/deploy/consolelink.go b/pkg/deploy/consolelink.go deleted file mode 100644 index eaf3f67353..0000000000 --- a/pkg/deploy/consolelink.go +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright (c) 2019-2021 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 deploy - -import ( - "fmt" - "strings" - - "github.com/eclipse-che/che-operator/pkg/util" - consolev1 "github.com/openshift/api/console/v1" - "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - ConsoleLinkFinalizerName = "consolelink.finalizers.che.eclipse.org" - ConsoleLinksResourceName = "consolelinks" -) - -func ReconcileConsoleLink(deployContext *DeployContext) (bool, error) { - if !util.IsOpenShift4 || !util.HasK8SResourceObject(deployContext.ClusterAPI.DiscoveryClient, ConsoleLinksResourceName) { - // console link is supported only on OpenShift >= 4.2 - logrus.Debug("Console link won't be created. Consolelinks is not supported by OpenShift cluster.") - return true, nil - } - - if !deployContext.CheCluster.Spec.Server.TlsSupport { - // console link is supported only with https - logrus.Debug("Console link won't be created. HTTP protocol is not supported.") - return true, nil - } - - if deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() { - return createConsoleLink(deployContext) - } - return true, nil -} - -func ReconcileConsoleLinkFinalizer(deployContext *DeployContext) error { - if !deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() { - return DeleteObjectWithFinalizer(deployContext, client.ObjectKey{Name: DefaultConsoleLinkName()}, &consolev1.ConsoleLink{}, ConsoleLinkFinalizerName) - } - return nil -} - -func createConsoleLink(deployContext *DeployContext) (bool, error) { - consoleLinkSpec := getConsoleLinkSpec(deployContext) - _, err := CreateIfNotExists(deployContext, consoleLinkSpec) - if err != nil { - return false, err - } - - consoleLink := &consolev1.ConsoleLink{} - exists, err := Get(deployContext, client.ObjectKey{Name: DefaultConsoleLinkName()}, consoleLink) - if !exists || err != nil { - return false, err - } - - // consolelink is for this specific instance of Eclipse Che - if strings.Index(consoleLink.Spec.Link.Href, deployContext.CheCluster.Spec.Server.CheHost) != -1 { - err = AppendFinalizer(deployContext, ConsoleLinkFinalizerName) - return err == nil, err - } - - return true, nil -} - -func getConsoleLinkSpec(deployContext *DeployContext) *consolev1.ConsoleLink { - cheHost := deployContext.CheCluster.Spec.Server.CheHost - consoleLink := &consolev1.ConsoleLink{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConsoleLink", - APIVersion: consolev1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: DefaultConsoleLinkName(), - Annotations: map[string]string{ - CheEclipseOrgNamespace: deployContext.CheCluster.Namespace, - }, - }, - Spec: consolev1.ConsoleLinkSpec{ - Link: consolev1.Link{ - Href: "https://" + cheHost, - Text: DefaultConsoleLinkDisplayName()}, - Location: consolev1.ApplicationMenu, - ApplicationMenu: &consolev1.ApplicationMenuSpec{ - Section: DefaultConsoleLinkSection(), - ImageURL: fmt.Sprintf("https://%s%s", cheHost, DefaultConsoleLinkImage()), - }, - }, - } - - return consoleLink -} diff --git a/pkg/deploy/consolelink_test.go b/pkg/deploy/consolelink_test.go deleted file mode 100644 index 832f092e23..0000000000 --- a/pkg/deploy/consolelink_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// -// Copyright (c) 2019-2021 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 deploy - -import ( - "context" - "time" - - orgv1 "github.com/eclipse-che/che-operator/api/v1" - "github.com/eclipse-che/che-operator/pkg/util" - console "github.com/openshift/api/console/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - 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/fake" - - "testing" -) - -func TestReconcileConsoleLink(t *testing.T) { - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{ - TlsSupport: true, - }, - }, - } - - scheme := scheme.Scheme - scheme.AddKnownTypes(orgv1.SchemeBuilder.GroupVersion, &orgv1.CheCluster{}) - scheme.AddKnownTypes(console.GroupVersion, &console.ConsoleLink{}) - cli := fake.NewFakeClientWithScheme(scheme, cheCluster) - clientSet := fakeclientset.NewSimpleClientset() - fakeDiscovery, _ := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery) - fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{ - { - APIResources: []metav1.APIResource{ - {Name: ConsoleLinksResourceName}, - }, - }, - } - - util.IsOpenShift4 = true - deployContext := &DeployContext{ - CheCluster: cheCluster, - ClusterAPI: ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme, - DiscoveryClient: fakeDiscovery, - }, - } - - done, err := ReconcileConsoleLink(deployContext) - if !done || err != nil { - t.Fatalf("Failed to reconcile consolelink: %v", err) - } - - // check consolelink object existence - consoleLink := &console.ConsoleLink{} - exists, err := Get(deployContext, types.NamespacedName{Name: DefaultConsoleLinkName()}, consoleLink) - if !exists || err != nil { - t.Fatalf("Failed to get consolelink: %v", err) - } - - // check finalizer - c := &orgv1.CheCluster{} - err = cli.Get(context.TODO(), types.NamespacedName{Namespace: "eclipse-che", Name: "eclipse-che"}, c) - if err != nil { - t.Fatalf("Failed to get checluster: %v", err) - } - if !util.ContainsString(c.ObjectMeta.Finalizers, ConsoleLinkFinalizerName) { - t.Fatalf("Failed to add finalizer") - } - - // Initialize DeletionTimestamp => checluster is being deleted - cheCluster.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} - err = ReconcileConsoleLinkFinalizer(deployContext) - if err != nil { - t.Fatalf("Failed to reconcile consolelink: %v", err) - } - - // check consolelink object existence - exists, err = Get(deployContext, types.NamespacedName{Name: DefaultConsoleLinkName()}, consoleLink) - if exists || err != nil { - t.Fatalf("Failed to remove consolelink") - } - - // check finalizer - c = &orgv1.CheCluster{} - err = cli.Get(context.TODO(), types.NamespacedName{Namespace: "eclipse-che", Name: "eclipse-che"}, c) - if !errors.IsNotFound(err) { - t.Fatalf("Failed to get checluster: %v", err) - } -} diff --git a/pkg/deploy/dashboard/dashboard.go b/pkg/deploy/dashboard/dashboard.go index 394fd957d1..203972090c 100644 --- a/pkg/deploy/dashboard/dashboard.go +++ b/pkg/deploy/dashboard/dashboard.go @@ -16,6 +16,7 @@ import ( "fmt" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/deploy/expose" @@ -33,97 +34,98 @@ var ( log = ctrl.Log.WithName("dashboard") ) -type Dashboard struct { - deployContext *deploy.DeployContext - component string +type DashboardReconciler struct { + deploy.Reconcilable } -func NewDashboard(deployContext *deploy.DeployContext) *Dashboard { - return &Dashboard{ - deployContext: deployContext, - component: deploy.DefaultCheFlavor(deployContext.CheCluster) + "-dashboard", - } +func NewDashboardReconciler() *DashboardReconciler { + return &DashboardReconciler{} } -func (d *Dashboard) GetComponentName() string { - return d.component +func (d *DashboardReconciler) getComponentName(ctx *deploy.DeployContext) string { + return deploy.DefaultCheFlavor(ctx.CheCluster) + "-dashboard" } -func (d *Dashboard) Reconcile() (done bool, err error) { +func (d *DashboardReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { // Create a new dashboard service - done, err = deploy.SyncServiceToCluster(d.deployContext, d.component, []string{"http"}, []int32{8080}, d.component) + done, err := deploy.SyncServiceToCluster(ctx, d.getComponentName(ctx), []string{"http"}, []int32{8080}, d.getComponentName(ctx)) if !done { - return false, err + return reconcile.Result{}, false, err } // Expose dashboard service with route or ingress - _, done, err = expose.ExposeWithHostPath(d.deployContext, d.component, d.deployContext.CheCluster.Spec.Server.CheHost, + _, done, err = expose.ExposeWithHostPath(ctx, d.getComponentName(ctx), ctx.CheCluster.Spec.Server.CheHost, exposePath, - d.deployContext.CheCluster.Spec.Server.DashboardRoute, - d.deployContext.CheCluster.Spec.Server.DashboardIngress, - d.createGatewayConfig(), + ctx.CheCluster.Spec.Server.DashboardRoute, + ctx.CheCluster.Spec.Server.DashboardIngress, + d.createGatewayConfig(ctx), ) if !done { - return false, err + return reconcile.Result{}, false, err } // we create dashboard SA in any case to keep a track on resources we access withing it - done, err = deploy.SyncServiceAccountToCluster(d.deployContext, DashboardSA) + done, err = deploy.SyncServiceAccountToCluster(ctx, DashboardSA) if !done { - return done, err + return reconcile.Result{}, false, err } // on Kubernetes Dashboard needs privileged SA to work with user's objects // for time being until Kubernetes did not get authentication if !util.IsOpenShift { - done, err = deploy.SyncClusterRoleToCluster(d.deployContext, d.getClusterRoleName(), GetPrivilegedPoliciesRulesForKubernetes()) + done, err = deploy.SyncClusterRoleToCluster(ctx, d.getClusterRoleName(ctx), GetPrivilegedPoliciesRulesForKubernetes()) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = deploy.SyncClusterRoleBindingToCluster(d.deployContext, d.getClusterRoleBindingName(), DashboardSA, d.getClusterRoleName()) + done, err = deploy.SyncClusterRoleBindingToCluster(ctx, d.getClusterRoleBindingName(ctx), DashboardSA, d.getClusterRoleName(ctx)) if !done { - return false, err + return reconcile.Result{}, false, err } - err = deploy.AppendFinalizer(d.deployContext, ClusterPermissionsDashboardFinalizer) + err = deploy.AppendFinalizer(ctx, ClusterPermissionsDashboardFinalizer) if err != nil { - return false, err + return reconcile.Result{}, false, err } } // Deploy dashboard - spec, err := d.getDashboardDeploymentSpec() + spec, err := d.getDashboardDeploymentSpec(ctx) if err != nil { - return false, err + return reconcile.Result{}, false, err } - return deploy.SyncDeploymentSpecToCluster(d.deployContext, spec, deploy.DefaultDeploymentDiffOpts) + + done, err = deploy.SyncDeploymentSpecToCluster(ctx, spec, deploy.DefaultDeploymentDiffOpts) + if !done { + return reconcile.Result{}, false, err + } + + return reconcile.Result{}, true, nil } -func (d *Dashboard) Finalize() (done bool, err error) { - done, err = deploy.Delete(d.deployContext, types.NamespacedName{Name: d.getClusterRoleName()}, &rbacv1.ClusterRole{}) +func (d *DashboardReconciler) Finalize(ctx *deploy.DeployContext) error { + done, err := deploy.Delete(ctx, types.NamespacedName{Name: d.getClusterRoleName(ctx)}, &rbacv1.ClusterRole{}) if !done { - return false, err + return err } - done, err = deploy.Delete(d.deployContext, types.NamespacedName{Name: d.getClusterRoleBindingName()}, &rbacv1.ClusterRoleBinding{}) + done, err = deploy.Delete(ctx, types.NamespacedName{Name: d.getClusterRoleBindingName(ctx)}, &rbacv1.ClusterRoleBinding{}) if !done { - return false, err + return err } - err = deploy.DeleteFinalizer(d.deployContext, ClusterPermissionsDashboardFinalizer) - return err == nil, err + return deploy.DeleteFinalizer(ctx, ClusterPermissionsDashboardFinalizer) } -func (d *Dashboard) createGatewayConfig() *gateway.TraefikConfig { +func (d *DashboardReconciler) createGatewayConfig(ctx *deploy.DeployContext) *gateway.TraefikConfig { cfg := gateway.CreateCommonTraefikConfig( - d.component, + d.getComponentName(ctx), fmt.Sprintf("PathPrefix(`%s`)", exposePath), 10, - "http://"+d.component+":8080", + "http://"+d.getComponentName(ctx)+":8080", []string{}) - if util.IsOpenShift && d.deployContext.CheCluster.IsNativeUserModeEnabled() { - cfg.AddAuthHeaderRewrite(d.component) + if util.IsOpenShift && ctx.CheCluster.IsNativeUserModeEnabled() { + cfg.AddAuthHeaderRewrite(d.getComponentName(ctx)) } return cfg } diff --git a/pkg/deploy/dashboard/dashboard_deployment_test.go b/pkg/deploy/dashboard/dashboard_deployment_test.go index 736c7c5d84..5538a7b868 100644 --- a/pkg/deploy/dashboard/dashboard_deployment_test.go +++ b/pkg/deploy/dashboard/dashboard_deployment_test.go @@ -16,6 +16,7 @@ import ( "os" configv1 "github.com/openshift/api/config/v1" + "github.com/stretchr/testify/assert" "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" @@ -27,8 +28,6 @@ import ( orgv1 "github.com/eclipse-che/che-operator/api/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -37,35 +36,12 @@ import ( ) func TestDashboardDeploymentSecurityContext(t *testing.T) { - logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) - cli := fake.NewFakeClientWithScheme(scheme.Scheme) - - deployContext := &deploy.DeployContext{ - CheCluster: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - }, - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{}, - }, - }, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - deployContext.ClusterAPI.Scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Console{}) - - dashboard := NewDashboard(deployContext) - deployment, err := dashboard.getDashboardDeploymentSpec() - if err != nil { - t.Fatalf("Failed to evaluate dashboard deployment spec: %v", err) - } + dashboard := NewDashboardReconciler() + deployment, err := dashboard.getDashboardDeploymentSpec(ctx) + assert.Nil(t, err) util.ValidateSecurityContext(deployment, t) } @@ -91,6 +67,7 @@ func TestDashboardDeploymentResources(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, }, }, @@ -104,6 +81,7 @@ func TestDashboardDeploymentResources(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ @@ -120,27 +98,11 @@ func TestDashboardDeploymentResources(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &deploy.DeployContext{ - CheCluster: testCase.cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - deployContext.ClusterAPI.Scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Console{}) - - dashboard := NewDashboard(deployContext) - deployment, err := dashboard.getDashboardDeploymentSpec() - if err != nil { - t.Fatalf("Failed to evaluate dashboard deployment spec: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + dashboard := NewDashboardReconciler() + deployment, err := dashboard.getDashboardDeploymentSpec(ctx) + assert.Nil(t, err) util.CompareResources(deployment, util.TestExpectedResources{ MemoryLimit: testCase.memoryLimit, @@ -185,6 +147,7 @@ func TestDashboardDeploymentEnvVars(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ @@ -213,6 +176,7 @@ func TestDashboardDeploymentEnvVars(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ @@ -256,6 +220,7 @@ func TestDashboardDeploymentEnvVars(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ @@ -269,36 +234,14 @@ func TestDashboardDeploymentEnvVars(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &deploy.DeployContext{ - CheCluster: testCase.cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - deployContext.ClusterAPI.Scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Console{}) + ctx := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) - dashboard := NewDashboard(deployContext) - deployment, err := dashboard.getDashboardDeploymentSpec() - if err != nil { - t.Fatalf("Failed to evaluate dashboard deployment spec: %v", err) - } - containers := deployment.Spec.Template.Spec.Containers - if len(containers) != 1 { - t.Fatalf("Dashboard deployment is expected to have only one container but is has %v", len(containers)) - } + dashboard := NewDashboardReconciler() + deployment, err := dashboard.getDashboardDeploymentSpec(ctx) - dashboardContainer := containers[0] - diff := cmp.Diff(testCase.envVars, dashboardContainer.Env) - if diff != "" { - t.Fatalf("Container env var does not match expected. Diff: %s", diff) - } + assert.Nil(t, err) + assert.Equal(t, len(deployment.Spec.Template.Spec.Containers), 1) + assert.Empty(t, cmp.Diff(testCase.envVars, deployment.Spec.Template.Spec.Containers[0].Env)) }) } } @@ -334,6 +277,7 @@ func TestDashboardDeploymentVolumes(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, }, }, @@ -379,6 +323,7 @@ func TestDashboardDeploymentVolumes(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, }, }, @@ -387,40 +332,14 @@ func TestDashboardDeploymentVolumes(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &deploy.DeployContext{ - CheCluster: testCase.cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - - dashboard := NewDashboard(deployContext) - deployment, err := dashboard.getDashboardDeploymentSpec() - if err != nil { - t.Fatalf("Failed to evaluate dashboard deployment spec: %v", err) - } - containers := deployment.Spec.Template.Spec.Containers - if len(containers) != 1 { - t.Fatalf("Dashboard deployment is expected to have only one container but is has %v", len(containers)) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) - diff := cmp.Diff(testCase.volumes, deployment.Spec.Template.Spec.Volumes) - if diff != "" { - t.Fatalf("Dashboard deployment volume not match expected. Diff: %s", diff) - } + dashboard := NewDashboardReconciler() + deployment, err := dashboard.getDashboardDeploymentSpec(ctx) - dashboardContainer := containers[0] - diff = cmp.Diff(testCase.volumeMounts, dashboardContainer.VolumeMounts) - if diff != "" { - t.Fatalf("Dashboard deployment volume not match expected. Diff: %s", diff) - } + assert.Nil(t, err) + assert.Equal(t, len(deployment.Spec.Template.Spec.Containers), 1) + assert.Empty(t, cmp.Diff(testCase.volumeMounts, deployment.Spec.Template.Spec.Containers[0].VolumeMounts)) }) } } diff --git a/pkg/deploy/dashboard/dashboard_test.go b/pkg/deploy/dashboard/dashboard_test.go index 1fc307bca8..62f66136e8 100644 --- a/pkg/deploy/dashboard/dashboard_test.go +++ b/pkg/deploy/dashboard/dashboard_test.go @@ -13,24 +13,17 @@ package dashboard import ( - "context" - "fmt" - "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/util" + "github.com/stretchr/testify/assert" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/apimachinery/pkg/runtime" - orgv1 "github.com/eclipse-che/che-operator/api/v1" routev1 "github.com/openshift/api/route/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "testing" ) @@ -38,215 +31,61 @@ import ( const Namespace = "eclipse-che" func TestDashboardOpenShift(t *testing.T) { - //given - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: "eclipse-che", - }, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - - //when util.IsOpenShift = true - dashboard := NewDashboard(deployContext) - done, err := dashboard.Reconcile() - if !done || err != nil { - t.Fatalf("Failed to sync Dashboard: %v", err) - } - - //then - verifyDashboardServiceExist(t, cli, dashboard) - verifyDashboardRouteExist(t, cli, dashboard) - verifyDashboardDeploymentExists(t, cli, dashboard) - verifyDashboardServiceAccountExists(t, cli) - verifyClusterRoleDoesNotExist(t, cli) - verifyClusterRoleBindingDoesNotExist(t, cli) - verifyFinalizerIsNotSet(t, cheCluster) + + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) + dashboard := NewDashboardReconciler() + _, done, err := dashboard.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &corev1.Service{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &routev1.Route{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &corev1.ServiceAccount{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.False(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleBindingName(ctx), Namespace: "eclipse-che"}, &rbacv1.ClusterRoleBinding{})) + assert.False(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleName(ctx), Namespace: "eclipse-che"}, &rbacv1.ClusterRole{})) + assert.False(t, util.ContainsString(ctx.CheCluster.Finalizers, ClusterPermissionsDashboardFinalizer)) } func TestDashboardKubernetes(t *testing.T) { - //given - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: "eclipse-che", - }, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - - //when util.IsOpenShift = false - dashboard := NewDashboard(deployContext) - done, err := dashboard.Reconcile() - if !done || err != nil { - t.Fatalf("Failed to sync Dashboard: %v", err) - } - - //then - verifyDashboardDeploymentExists(t, cli, dashboard) - verifyDashboardServiceExist(t, cli, dashboard) - verifyDashboardIngressExist(t, cli, dashboard) - verifyDashboardServiceAccountExists(t, cli) - verifyDashboardClusterRoleExists(t, cli) - verifyDashboardClusterRoleBindingExists(t, cli) - verifyFinalizerIsSet(t, cheCluster) + + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) + dashboard := NewDashboardReconciler() + _, done, err := dashboard.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &corev1.Service{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &networkingv1.Ingress{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &corev1.ServiceAccount{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleBindingName(ctx)}, &rbacv1.ClusterRoleBinding{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleName(ctx)}, &rbacv1.ClusterRole{})) + assert.True(t, util.ContainsString(ctx.CheCluster.Finalizers, ClusterPermissionsDashboardFinalizer)) } func TestDashboardClusterRBACFinalizerOnKubernetes(t *testing.T) { - //given - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: "eclipse-che", - }, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - - //when util.IsOpenShift = false - dashboard := NewDashboard(deployContext) - done, err := dashboard.Reconcile() - if !done || err != nil { - t.Fatalf("Failed to sync Dashboard: %v", err) - } - verifyDashboardClusterRoleExists(t, cli) - verifyDashboardClusterRoleBindingExists(t, cli) - verifyFinalizerIsSet(t, cheCluster) - done, err = dashboard.Finalize() - if err != nil { - t.Fatalf("Can't finalize dashboard %v", err) - } - - //then - verifyClusterRoleDoesNotExist(t, cli) - verifyClusterRoleBindingDoesNotExist(t, cli) - verifyFinalizerIsNotSet(t, cheCluster) -} - -func verifyFinalizerIsSet(t *testing.T, cheCluster *orgv1.CheCluster) { - if !hasFinalizer(ClusterPermissionsDashboardFinalizer, cheCluster) { - t.Fatal("CheCluster did not get Dashboard Cluster Permissions finalizer on Kubernetes") - } -} -func verifyDashboardClusterRoleBindingExists(t *testing.T, cli client.Client) { - clusterRoleBinding := &rbacv1.ClusterRoleBinding{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf(DashboardSAClusterRoleBindingTemplate, Namespace)}, clusterRoleBinding) - if err != nil { - t.Fatalf("ClusterRoleBinding is not found on k8s: %v", err) - } -} - -func verifyDashboardClusterRoleExists(t *testing.T, cli client.Client) { - clusterRole := &rbacv1.ClusterRole{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf(DashboardSAClusterRoleTemplate, Namespace)}, clusterRole) - if err != nil { - t.Fatalf("ClusterRole is not found on K8s: %v", err) - } -} + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) + dashboard := NewDashboardReconciler() + _, done, err := dashboard.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) -func verifyDashboardIngressExist(t *testing.T, cli client.Client, dashboard *Dashboard) { - ingress := &networkingv1.Ingress{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: dashboard.component, Namespace: "eclipse-che"}, ingress) - if err != nil { - t.Fatalf("Ingress not found: %v", err) - } -} - -func verifyFinalizerIsNotSet(t *testing.T, cheCluster *orgv1.CheCluster) { - if hasFinalizer(ClusterPermissionsDashboardFinalizer, cheCluster) { - t.Fatal("CheCluster got Dashboard Cluster Permissions finalizer but not expected") - } -} + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleBindingName(ctx)}, &rbacv1.ClusterRoleBinding{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleName(ctx)}, &rbacv1.ClusterRole{})) + assert.True(t, util.ContainsString(ctx.CheCluster.Finalizers, ClusterPermissionsDashboardFinalizer)) -func verifyClusterRoleBindingDoesNotExist(t *testing.T, cli client.Client) { - err := cli.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf(DashboardSAClusterRoleBindingTemplate, Namespace)}, &rbacv1.ClusterRoleBinding{}) - if err == nil || !errors.IsNotFound(err) { - t.Fatalf("ClusterRoleBinding is created or failed to check on OpenShift: %v", err) - } -} - -func verifyClusterRoleDoesNotExist(t *testing.T, cli client.Client) { - err := cli.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf(DashboardSAClusterRoleTemplate, Namespace)}, &rbacv1.ClusterRole{}) - if err == nil || !errors.IsNotFound(err) { - t.Fatalf("ClusterRole is created or failed to check on OpenShift: %v", err) - } -} - -func verifyDashboardServiceAccountExists(t *testing.T, cli client.Client) { - sa := &corev1.ServiceAccount{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: DashboardSA, Namespace: "eclipse-che"}, sa) - if err != nil { - t.Fatalf("Service account not found: %v", err) - } -} - -func verifyDashboardDeploymentExists(t *testing.T, cli client.Client, dashboard *Dashboard) { - deployment := &appsv1.Deployment{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: dashboard.component, Namespace: "eclipse-che"}, deployment) - if err != nil { - t.Fatalf("Deployment not found: %v", err) - } -} - -func verifyDashboardRouteExist(t *testing.T, cli client.Client, dashboard *Dashboard) { - route := &routev1.Route{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: dashboard.component, Namespace: "eclipse-che"}, route) - if err != nil { - t.Fatalf("Route not found: %v", err) - } -} - -func verifyDashboardServiceExist(t *testing.T, cli client.Client, dashboard *Dashboard) { - service := &corev1.Service{} - err := cli.Get(context.TODO(), types.NamespacedName{Name: dashboard.component, Namespace: "eclipse-che"}, service) - if err != nil { - t.Fatalf("Service not found: %v", err) - } -} + err = dashboard.Finalize(ctx) + assert.Nil(t, err) -func hasFinalizer(name string, cheCluster *orgv1.CheCluster) bool { - for _, finalizer := range cheCluster.ObjectMeta.Finalizers { - if finalizer == name { - return true - } - } - return false + assert.False(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleBindingName(ctx)}, &rbacv1.ClusterRoleBinding{})) + assert.False(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: dashboard.getClusterRoleName(ctx)}, &rbacv1.ClusterRole{})) + assert.False(t, util.ContainsString(ctx.CheCluster.Finalizers, ClusterPermissionsDashboardFinalizer)) } diff --git a/pkg/deploy/dashboard/deployment_dashboard.go b/pkg/deploy/dashboard/deployment_dashboard.go index 4b8678e228..87b8af508d 100644 --- a/pkg/deploy/dashboard/deployment_dashboard.go +++ b/pkg/deploy/dashboard/deployment_dashboard.go @@ -32,14 +32,14 @@ import ( const CHE_SELF_SIGNED_MOUNT_PATH = "/public-certs/che-self-signed" const CHE_CUSTOM_CERTS_MOUNT_PATH = "/public-certs/custom" -func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { +func (d *DashboardReconciler) getDashboardDeploymentSpec(ctx *deploy.DeployContext) (*appsv1.Deployment, error) { var volumes []corev1.Volume var volumeMounts []corev1.VolumeMount var envVars []corev1.EnvVar volumes, volumeMounts = d.provisionCustomPublicCA(volumes, volumeMounts) - selfSignedCertSecretExist, err := tls.IsSelfSignedCASecretExists(d.deployContext) + selfSignedCertSecretExist, err := tls.IsSelfSignedCASecretExists(ctx) if err != nil { return nil, err } @@ -52,17 +52,17 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { // CHE_HOST is here for backward compatibility. Replaced with CHE_URL corev1.EnvVar{ Name: "CHE_HOST", - Value: util.GetCheURL(d.deployContext.CheCluster)}, + Value: util.GetCheURL(ctx.CheCluster)}, corev1.EnvVar{ Name: "CHE_URL", - Value: util.GetCheURL(d.deployContext.CheCluster)}, + Value: util.GetCheURL(ctx.CheCluster)}, ) - if d.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() { + if ctx.CheCluster.IsInternalClusterSVCNamesEnabled() { envVars = append(envVars, corev1.EnvVar{ Name: "CHE_INTERNAL_URL", - Value: fmt.Sprintf("http://%s.%s.svc:8080/api", deploy.CheServiceName, d.deployContext.CheCluster.Namespace)}, + Value: fmt.Sprintf("http://%s.%s.svc:8080/api", deploy.CheServiceName, ctx.CheCluster.Namespace)}, ) } @@ -70,14 +70,14 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { envVars = append(envVars, corev1.EnvVar{ Name: "OPENSHIFT_CONSOLE_URL", - Value: d.evaluateOpenShiftConsoleURL()}) + Value: d.evaluateOpenShiftConsoleURL(ctx)}) } terminationGracePeriodSeconds := int64(30) - labels, labelsSelector := deploy.GetLabelsAndSelector(d.deployContext.CheCluster, d.component) + labels, labelsSelector := deploy.GetLabelsAndSelector(ctx.CheCluster, d.getComponentName(ctx)) - dashboardImageAndTag := util.GetValue(d.deployContext.CheCluster.Spec.Server.DashboardImage, deploy.DefaultDashboardImage(d.deployContext.CheCluster)) - pullPolicy := corev1.PullPolicy(util.GetValue(d.deployContext.CheCluster.Spec.Server.DashboardImagePullPolicy, deploy.DefaultPullPolicyFromDockerImage(dashboardImageAndTag))) + dashboardImageAndTag := util.GetValue(ctx.CheCluster.Spec.Server.DashboardImage, deploy.DefaultDashboardImage(ctx.CheCluster)) + pullPolicy := corev1.PullPolicy(util.GetValue(ctx.CheCluster.Spec.Server.DashboardImagePullPolicy, deploy.DefaultPullPolicyFromDockerImage(dashboardImageAndTag))) deployment := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ @@ -85,8 +85,8 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: d.component, - Namespace: d.deployContext.CheCluster.Namespace, + Name: d.getComponentName(ctx), + Namespace: ctx.CheCluster.Namespace, Labels: labels, }, Spec: appsv1.DeploymentSpec{ @@ -102,7 +102,7 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { ServiceAccountName: DashboardSA, Containers: []corev1.Container{ { - Name: d.component, + Name: d.getComponentName(ctx), ImagePullPolicy: pullPolicy, Image: dashboardImageAndTag, Ports: []corev1.ContainerPort{ @@ -115,18 +115,18 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - d.deployContext.CheCluster.Spec.Server.DashboardMemoryRequest, + ctx.CheCluster.Spec.Server.DashboardMemoryRequest, deploy.DefaultDashboardMemoryRequest), corev1.ResourceCPU: util.GetResourceQuantity( - d.deployContext.CheCluster.Spec.Server.DashboardCpuRequest, + ctx.CheCluster.Spec.Server.DashboardCpuRequest, deploy.DefaultDashboardCpuRequest), }, Limits: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - d.deployContext.CheCluster.Spec.Server.DashboardMemoryLimit, + ctx.CheCluster.Spec.Server.DashboardMemoryLimit, deploy.DefaultDashboardMemoryLimit), corev1.ResourceCPU: util.GetResourceQuantity( - d.deployContext.CheCluster.Spec.Server.DashboardCpuLimit, + ctx.CheCluster.Spec.Server.DashboardCpuLimit, deploy.DefaultDashboardCpuLimit), }, }, @@ -182,11 +182,11 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { } if !util.IsOpenShift { - runAsUser, err := strconv.ParseInt(util.GetValue(d.deployContext.CheCluster.Spec.K8s.SecurityContextRunAsUser, deploy.DefaultSecurityContextRunAsUser), 10, 64) + runAsUser, err := strconv.ParseInt(util.GetValue(ctx.CheCluster.Spec.K8s.SecurityContextRunAsUser, deploy.DefaultSecurityContextRunAsUser), 10, 64) if err != nil { return nil, err } - fsGroup, err := strconv.ParseInt(util.GetValue(d.deployContext.CheCluster.Spec.K8s.SecurityContextFsGroup, deploy.DefaultSecurityContextFsGroup), 10, 64) + fsGroup, err := strconv.ParseInt(util.GetValue(ctx.CheCluster.Spec.K8s.SecurityContextFsGroup, deploy.DefaultSecurityContextFsGroup), 10, 64) if err != nil { return nil, err } @@ -199,10 +199,10 @@ func (d *Dashboard) getDashboardDeploymentSpec() (*appsv1.Deployment, error) { return deployment, nil } -func (d *Dashboard) evaluateOpenShiftConsoleURL() string { +func (d *DashboardReconciler) evaluateOpenShiftConsoleURL(ctx *deploy.DeployContext) string { console := &configv1.Console{} - err := d.deployContext.ClusterAPI.NonCachingClient.Get(context.TODO(), types.NamespacedName{ + err := ctx.ClusterAPI.NonCachingClient.Get(context.TODO(), types.NamespacedName{ Name: "cluster", Namespace: "openshift-console", }, console) @@ -215,7 +215,7 @@ func (d *Dashboard) evaluateOpenShiftConsoleURL() string { return console.Status.ConsoleURL } -func (d *Dashboard) provisionCheSelfSignedCA(volumes []corev1.Volume, volumeMounts []corev1.VolumeMount) ([]corev1.Volume, []corev1.VolumeMount) { +func (d *DashboardReconciler) provisionCheSelfSignedCA(volumes []corev1.Volume, volumeMounts []corev1.VolumeMount) ([]corev1.Volume, []corev1.VolumeMount) { cheSelfSigned := corev1.Volume{ Name: "che-self-signed-ca", VolumeSource: corev1.VolumeSource{ @@ -238,7 +238,7 @@ func (d *Dashboard) provisionCheSelfSignedCA(volumes []corev1.Volume, volumeMoun return volumes, volumeMounts } -func (d *Dashboard) provisionCustomPublicCA(volumes []corev1.Volume, volumeMounts []corev1.VolumeMount) ([]corev1.Volume, []corev1.VolumeMount) { +func (d *DashboardReconciler) provisionCustomPublicCA(volumes []corev1.Volume, volumeMounts []corev1.VolumeMount) ([]corev1.Volume, []corev1.VolumeMount) { customPublicCertsVolume := corev1.Volume{ Name: "che-custom-ca", VolumeSource: corev1.VolumeSource{ diff --git a/pkg/deploy/dashboard/rbac.go b/pkg/deploy/dashboard/rbac.go index 36b510ea3d..9dfdc0c2ff 100644 --- a/pkg/deploy/dashboard/rbac.go +++ b/pkg/deploy/dashboard/rbac.go @@ -15,6 +15,7 @@ package dashboard import ( "fmt" + "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/util" rbacv1 "k8s.io/api/rbac/v1" @@ -59,10 +60,10 @@ func GetPrivilegedPoliciesRulesForKubernetes() []rbacv1.PolicyRule { return rules } -func (d *Dashboard) getClusterRoleName() string { - return fmt.Sprintf(DashboardSAClusterRoleTemplate, d.deployContext.CheCluster.Namespace) +func (d *DashboardReconciler) getClusterRoleName(ctx *deploy.DeployContext) string { + return fmt.Sprintf(DashboardSAClusterRoleTemplate, ctx.CheCluster.Namespace) } -func (d *Dashboard) getClusterRoleBindingName() string { - return fmt.Sprintf(DashboardSAClusterRoleBindingTemplate, d.deployContext.CheCluster.Namespace) +func (d *DashboardReconciler) getClusterRoleBindingName(ctx *deploy.DeployContext) string { + return fmt.Sprintf(DashboardSAClusterRoleBindingTemplate, ctx.CheCluster.Namespace) } diff --git a/pkg/deploy/devfileregistry/devfileregistry.go b/pkg/deploy/devfileregistry/devfileregistry.go index dd00c217c0..c443a89067 100644 --- a/pkg/deploy/devfileregistry/devfileregistry.go +++ b/pkg/deploy/devfileregistry/devfileregistry.go @@ -17,84 +17,91 @@ import ( "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/deploy/expose" "github.com/eclipse-che/che-operator/pkg/deploy/gateway" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -type DevfileRegistry struct { - deployContext *deploy.DeployContext +type DevfileRegistryReconciler struct { + deploy.Reconcilable } -func NewDevfileRegistry(deployContext *deploy.DeployContext) *DevfileRegistry { - return &DevfileRegistry{ - deployContext: deployContext, - } +func NewDevfileRegistryReconciler() *DevfileRegistryReconciler { + return &DevfileRegistryReconciler{} } -func (p *DevfileRegistry) SyncAll() (bool, error) { - done, err := p.SyncService() +func (d *DevfileRegistryReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + if ctx.CheCluster.Spec.Server.ExternalDevfileRegistry { + return reconcile.Result{}, true, nil + } + + done, err := d.syncService(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - endpoint, done, err := p.ExposeEndpoint() + endpoint, done, err := d.exposeEndpoint(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.UpdateStatus(endpoint) + done, err = d.updateStatus(endpoint, ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.SyncConfigMap() + done, err = d.syncConfigMap(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.SyncDeployment() + done, err = d.syncDeployment(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - return true, nil + return reconcile.Result{}, true, nil +} + +func (d *DevfileRegistryReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil } -func (p *DevfileRegistry) SyncService() (bool, error) { +func (d *DevfileRegistryReconciler) syncService(ctx *deploy.DeployContext) (bool, error) { return deploy.SyncServiceToCluster( - p.deployContext, + ctx, deploy.DevfileRegistryName, []string{"http"}, []int32{8080}, deploy.DevfileRegistryName) } -func (p *DevfileRegistry) SyncConfigMap() (bool, error) { - data, err := p.GetConfigMapData() +func (d *DevfileRegistryReconciler) syncConfigMap(ctx *deploy.DeployContext) (bool, error) { + data, err := d.getConfigMapData(ctx) if err != nil { return false, err } - return deploy.SyncConfigMapDataToCluster(p.deployContext, deploy.DevfileRegistryName, data, deploy.DevfileRegistryName) + return deploy.SyncConfigMapDataToCluster(ctx, deploy.DevfileRegistryName, data, deploy.DevfileRegistryName) } -func (p *DevfileRegistry) ExposeEndpoint() (string, bool, error) { +func (d *DevfileRegistryReconciler) exposeEndpoint(ctx *deploy.DeployContext) (string, bool, error) { return expose.Expose( - p.deployContext, + ctx, deploy.DevfileRegistryName, - p.deployContext.CheCluster.Spec.Server.DevfileRegistryRoute, - p.deployContext.CheCluster.Spec.Server.DevfileRegistryIngress, - p.createGatewayConfig()) + ctx.CheCluster.Spec.Server.DevfileRegistryRoute, + ctx.CheCluster.Spec.Server.DevfileRegistryIngress, + d.createGatewayConfig()) } -func (p *DevfileRegistry) UpdateStatus(endpoint string) (bool, error) { +func (d *DevfileRegistryReconciler) updateStatus(endpoint string, ctx *deploy.DeployContext) (bool, error) { var devfileRegistryURL string - if p.deployContext.CheCluster.Spec.Server.TlsSupport { + if ctx.CheCluster.Spec.Server.TlsSupport { devfileRegistryURL = "https://" + endpoint } else { devfileRegistryURL = "http://" + endpoint } - if devfileRegistryURL != p.deployContext.CheCluster.Status.DevfileRegistryURL { - p.deployContext.CheCluster.Status.DevfileRegistryURL = devfileRegistryURL - if err := deploy.UpdateCheCRStatus(p.deployContext, "status: Devfile Registry URL", devfileRegistryURL); err != nil { + if devfileRegistryURL != ctx.CheCluster.Status.DevfileRegistryURL { + ctx.CheCluster.Status.DevfileRegistryURL = devfileRegistryURL + if err := deploy.UpdateCheCRStatus(ctx, "status: Devfile Registry URL", devfileRegistryURL); err != nil { return false, err } } @@ -102,12 +109,12 @@ func (p *DevfileRegistry) UpdateStatus(endpoint string) (bool, error) { return true, nil } -func (p *DevfileRegistry) SyncDeployment() (bool, error) { - spec := p.GetDevfileRegistryDeploymentSpec() - return deploy.SyncDeploymentSpecToCluster(p.deployContext, spec, deploy.DefaultDeploymentDiffOpts) +func (d *DevfileRegistryReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, error) { + spec := d.getDevfileRegistryDeploymentSpec(ctx) + return deploy.SyncDeploymentSpecToCluster(ctx, spec, deploy.DefaultDeploymentDiffOpts) } -func (p *DevfileRegistry) createGatewayConfig() *gateway.TraefikConfig { +func (d *DevfileRegistryReconciler) createGatewayConfig() *gateway.TraefikConfig { pathPrefix := "/" + deploy.DevfileRegistryName cfg := gateway.CreateCommonTraefikConfig( deploy.DevfileRegistryName, diff --git a/pkg/deploy/devfileregistry/devfileregistry_configmap.go b/pkg/deploy/devfileregistry/devfileregistry_configmap.go index fcc2e8f607..94df134447 100644 --- a/pkg/deploy/devfileregistry/devfileregistry_configmap.go +++ b/pkg/deploy/devfileregistry/devfileregistry_configmap.go @@ -13,6 +13,8 @@ package devfileregistry import ( "encoding/json" + + "github.com/eclipse-che/che-operator/pkg/deploy" ) type DevFileRegistryConfigMap struct { @@ -21,12 +23,12 @@ type DevFileRegistryConfigMap struct { CheDevfileRegistryURL string `json:"CHE_DEVFILE_REGISTRY_URL"` } -func (p *DevfileRegistry) GetConfigMapData() (map[string]string, error) { +func (d *DevfileRegistryReconciler) getConfigMapData(ctx *deploy.DeployContext) (map[string]string, error) { devfileRegistryEnv := make(map[string]string) data := &DevFileRegistryConfigMap{ - CheDevfileImagesRegistryURL: p.deployContext.CheCluster.Spec.Server.AirGapContainerRegistryHostname, - CheDevfileImagesRegistryOrganization: p.deployContext.CheCluster.Spec.Server.AirGapContainerRegistryOrganization, - CheDevfileRegistryURL: p.deployContext.CheCluster.Status.DevfileRegistryURL, + CheDevfileImagesRegistryURL: ctx.CheCluster.Spec.Server.AirGapContainerRegistryHostname, + CheDevfileImagesRegistryOrganization: ctx.CheCluster.Spec.Server.AirGapContainerRegistryOrganization, + CheDevfileRegistryURL: ctx.CheCluster.Status.DevfileRegistryURL, } out, err := json.Marshal(data) diff --git a/pkg/deploy/devfileregistry/devfileregistry_deployment.go b/pkg/deploy/devfileregistry/devfileregistry_deployment.go index 4485538491..9531283406 100644 --- a/pkg/deploy/devfileregistry/devfileregistry_deployment.go +++ b/pkg/deploy/devfileregistry/devfileregistry_deployment.go @@ -19,34 +19,34 @@ import ( v1 "k8s.io/api/core/v1" ) -func (p *DevfileRegistry) GetDevfileRegistryDeploymentSpec() *appsv1.Deployment { +func (p *DevfileRegistryReconciler) getDevfileRegistryDeploymentSpec(ctx *deploy.DeployContext) *appsv1.Deployment { registryType := "devfile" - registryImage := util.GetValue(p.deployContext.CheCluster.Spec.Server.DevfileRegistryImage, deploy.DefaultDevfileRegistryImage(p.deployContext.CheCluster)) - registryImagePullPolicy := v1.PullPolicy(util.GetValue(string(p.deployContext.CheCluster.Spec.Server.DevfileRegistryPullPolicy), deploy.DefaultPullPolicyFromDockerImage(registryImage))) + registryImage := util.GetValue(ctx.CheCluster.Spec.Server.DevfileRegistryImage, deploy.DefaultDevfileRegistryImage(ctx.CheCluster)) + registryImagePullPolicy := v1.PullPolicy(util.GetValue(string(ctx.CheCluster.Spec.Server.DevfileRegistryPullPolicy), deploy.DefaultPullPolicyFromDockerImage(registryImage))) probePath := "/devfiles/" devfileImagesEnv := util.GetEnvByRegExp("^.*devfile_registry_image.*$") resources := v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceMemory: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.DevfileRegistryMemoryRequest, + ctx.CheCluster.Spec.Server.DevfileRegistryMemoryRequest, deploy.DefaultDevfileRegistryMemoryRequest), v1.ResourceCPU: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.DevfileRegistryCpuRequest, + ctx.CheCluster.Spec.Server.DevfileRegistryCpuRequest, deploy.DefaultDevfileRegistryCpuRequest), }, Limits: v1.ResourceList{ v1.ResourceMemory: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.DevfileRegistryMemoryLimit, + ctx.CheCluster.Spec.Server.DevfileRegistryMemoryLimit, deploy.DefaultDevfileRegistryMemoryLimit), v1.ResourceCPU: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.DevfileRegistryCpuLimit, + ctx.CheCluster.Spec.Server.DevfileRegistryCpuLimit, deploy.DefaultDevfileRegistryCpuLimit), }, } return registry.GetSpecRegistryDeployment( - p.deployContext, + ctx, registryType, registryImage, devfileImagesEnv, diff --git a/pkg/deploy/devfileregistry/devfileregistry_deployment_test.go b/pkg/deploy/devfileregistry/devfileregistry_deployment_test.go index ccf37a7c28..14fccae8aa 100644 --- a/pkg/deploy/devfileregistry/devfileregistry_deployment_test.go +++ b/pkg/deploy/devfileregistry/devfileregistry_deployment_test.go @@ -21,8 +21,6 @@ import ( orgv1 "github.com/eclipse-che/che-operator/api/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -51,6 +49,7 @@ func TestGetDevfileRegistryDeploymentSpec(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, }, }, @@ -63,6 +62,7 @@ func TestGetDevfileRegistryDeploymentSpec(t *testing.T) { memoryRequest: "150Mi", cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ + Name: "eclipse-che", Namespace: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ @@ -80,21 +80,10 @@ func TestGetDevfileRegistryDeploymentSpec(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - deployContext := &deploy.DeployContext{ - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - CheCluster: testCase.cheCluster, - } - - devfileregistry := NewDevfileRegistry(deployContext) - deployment := devfileregistry.GetDevfileRegistryDeploymentSpec() + devfileregistry := NewDevfileRegistryReconciler() + deployment := devfileregistry.getDevfileRegistryDeploymentSpec(ctx) util.CompareResources(deployment, util.TestExpectedResources{ diff --git a/pkg/deploy/devfileregistry/devfileregistry_test.go b/pkg/deploy/devfileregistry/devfileregistry_test.go index ad8f32f185..bc6e07f354 100644 --- a/pkg/deploy/devfileregistry/devfileregistry_test.go +++ b/pkg/deploy/devfileregistry/devfileregistry_test.go @@ -12,81 +12,127 @@ package devfileregistry import ( - "context" + "os" + orgv1 "github.com/eclipse-che/che-operator/api/v1" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/util" - - orgv1 "github.com/eclipse-che/che-operator/api/v1" routev1 "github.com/openshift/api/route/v1" + "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "testing" ) -func TestDevfileRegistrySyncAll(t *testing.T) { - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - } +func TestDevfileRegistryReconcile(t *testing.T) { + util.IsOpenShift = true + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } + devfileregistry := NewDevfileRegistryReconciler() + _, done, err := devfileregistry.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) - util.IsOpenShift = true + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, &corev1.Service{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, &routev1.Route{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, &corev1.ConfigMap{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.NotEmpty(t, ctx.CheCluster.Status.DevfileRegistryURL) +} - devfileregistry := NewDevfileRegistry(deployContext) - done, err := devfileregistry.SyncAll() - if !done || err != nil { - t.Fatalf("Failed to sync Devfile Registry: %v", err) +func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) { + type testCase struct { + name string + isOpenShift bool + isOpenShift4 bool + initObjects []runtime.Object + cheCluster *orgv1.CheCluster + expectedDevfileRegistryURL string } - // check service - service := &corev1.Service{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, service) - if err != nil { - t.Fatalf("Service not found: %v", err) + 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: os.Getenv("CHE_FLAVOR"), + }, + 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: os.Getenv("CHE_FLAVOR"), + }, + 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: os.Getenv("CHE_FLAVOR"), + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + ExternalDevfileRegistry: true, + DevfileRegistryUrl: "https://devfile-registry.external.1", + ExternalDevfileRegistries: []orgv1.ExternalDevfileRegistries{ + {Url: "https://devfile-registry.external.2"}, + }, + }, + }, + }, + expectedDevfileRegistryURL: "", + }, } - // check endpoint - route := &routev1.Route{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, route) - if err != nil { - t.Fatalf("Route not found: %v", err) - } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - // check configmap - cm := &corev1.ConfigMap{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, cm) - if err != nil { - t.Fatalf("Config Map not found: %v", err) - } + util.IsOpenShift = testCase.isOpenShift + util.IsOpenShift4 = testCase.isOpenShift4 - // check deployment - deployment := &appsv1.Deployment{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: "eclipse-che"}, deployment) - if err != nil { - t.Fatalf("Deployment not found: %v", err) - } + devfileregistry := NewDevfileRegistryReconciler() + devfileregistry.Reconcile(ctx) - if cheCluster.Status.DevfileRegistryURL == "" { - t.Fatalf("Status wasn't updated") + assert.Equal(t, ctx.CheCluster.Status.DevfileRegistryURL, testCase.expectedDevfileRegistryURL) + }) } } diff --git a/pkg/deploy/gateway/gateway.go b/pkg/deploy/gateway/gateway.go index 2ee81222d6..1dd1e730f5 100644 --- a/pkg/deploy/gateway/gateway.go +++ b/pkg/deploy/gateway/gateway.go @@ -36,6 +36,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ( @@ -58,6 +59,27 @@ var ( secretDiffOpts = cmpopts.IgnoreFields(corev1.Secret{}, "TypeMeta", "ObjectMeta") ) +type GatewayReconciler struct { + deploy.Reconcilable +} + +func NewGatewayReconciler() *GatewayReconciler { + return &GatewayReconciler{} +} + +func (p *GatewayReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + err := SyncGatewayToCluster(ctx) + if err != nil { + return reconcile.Result{}, false, err + } + + return reconcile.Result{}, true, nil +} + +func (p *GatewayReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil +} + // SyncGatewayToCluster installs or deletes the gateway based on the custom resource configuration func SyncGatewayToCluster(deployContext *deploy.DeployContext) error { if (util.GetServerExposureStrategy(deployContext.CheCluster) == "single-host" && diff --git a/pkg/deploy/identity-provider/deployment_keycloak.go b/pkg/deploy/identity-provider/deployment_keycloak.go index 752ea209fd..87237257d9 100644 --- a/pkg/deploy/identity-provider/deployment_keycloak.go +++ b/pkg/deploy/identity-provider/deployment_keycloak.go @@ -9,7 +9,7 @@ // Contributors: // Red Hat, Inc. - initial API and implementation // -package identity_provider +package identityprovider import ( "context" diff --git a/pkg/deploy/identity-provider/deployment_keycloak_test.go b/pkg/deploy/identity-provider/deployment_keycloak_test.go index 8f7bc3e1ee..1dad62b11c 100644 --- a/pkg/deploy/identity-provider/deployment_keycloak_test.go +++ b/pkg/deploy/identity-provider/deployment_keycloak_test.go @@ -9,7 +9,7 @@ // Contributors: // Red Hat, Inc. - initial API and implementation // -package identity_provider +package identityprovider import ( "context" diff --git a/pkg/deploy/identity-provider/exec.go b/pkg/deploy/identity-provider/exec.go index 52c2347b3e..6598572eb1 100644 --- a/pkg/deploy/identity-provider/exec.go +++ b/pkg/deploy/identity-provider/exec.go @@ -9,7 +9,7 @@ // Contributors: // Red Hat, Inc. - initial API and implementation // -package identity_provider +package identityprovider import ( "bytes" diff --git a/pkg/deploy/identity-provider/identity_provider.go b/pkg/deploy/identity-provider/identity_provider.go deleted file mode 100644 index 3ff313a00c..0000000000 --- a/pkg/deploy/identity-provider/identity_provider.go +++ /dev/null @@ -1,377 +0,0 @@ -// -// Copyright (c) 2019-2021 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 identity_provider - -import ( - "context" - "errors" - "strings" - - "github.com/eclipse-che/che-operator/pkg/deploy/gateway" - - orgv1 "github.com/eclipse-che/che-operator/api/v1" - "github.com/eclipse-che/che-operator/pkg/deploy" - "github.com/eclipse-che/che-operator/pkg/deploy/expose" - "github.com/eclipse-che/che-operator/pkg/util" - "github.com/google/go-cmp/cmp/cmpopts" - oauth "github.com/openshift/api/oauth/v1" - "github.com/sirupsen/logrus" - appsv1 "k8s.io/api/apps/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" -) - -var ( - oAuthClientDiffOpts = cmpopts.IgnoreFields(oauth.OAuthClient{}, "TypeMeta", "ObjectMeta") - syncItems = []func(*deploy.DeployContext) (bool, error){ - syncService, - syncExposure, - SyncKeycloakDeploymentToCluster, - syncKeycloakResources, - syncOpenShiftIdentityProvider, - SyncGitHubOAuth, - } - - keycloakUpdated = false - keycloakCheHost = "" -) - -// SyncIdentityProviderToCluster instantiates the identity provider (Keycloak) in the cluster. Returns true if -// the provisioning is complete, false if requeue of the reconcile request is needed. -func SyncIdentityProviderToCluster(deployContext *deploy.DeployContext) (bool, error) { - cr := deployContext.CheCluster - if deployContext.CheCluster.IsNativeUserModeEnabled() { - return syncNativeIdentityProviderItems(deployContext) - } else if cr.Spec.Auth.ExternalIdentityProvider { - return true, nil - } - - for _, syncItem := range syncItems { - provisioned, err := syncItem(deployContext) - if !util.IsTestMode() { - if !provisioned { - return false, err - } - } - } - - return true, nil -} - -func syncService(deployContext *deploy.DeployContext) (bool, error) { - return deploy.SyncServiceToCluster( - deployContext, - deploy.IdentityProviderName, - []string{"http"}, - []int32{8080}, - deploy.IdentityProviderName) -} - -func syncExposure(deployContext *deploy.DeployContext) (bool, error) { - cr := deployContext.CheCluster - - protocol := (map[bool]string{ - true: "https", - false: "http"})[cr.Spec.Server.TlsSupport] - endpoint, done, err := expose.Expose( - deployContext, - deploy.IdentityProviderName, - cr.Spec.Auth.IdentityProviderRoute, - cr.Spec.Auth.IdentityProviderIngress, - createGatewayConfig(deployContext.CheCluster)) - if !done { - return false, err - } - - keycloakURL := protocol + "://" + endpoint - if cr.Spec.Auth.IdentityProviderURL != keycloakURL { - cr.Spec.Auth.IdentityProviderURL = keycloakURL - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak URL", keycloakURL); err != nil { - return false, err - } - - cr.Status.KeycloakURL = keycloakURL - if err := deploy.UpdateCheCRStatus(deployContext, "Keycloak URL", keycloakURL); err != nil { - return false, err - } - } - - return true, nil -} - -func syncKeycloakResources(deployContext *deploy.DeployContext) (bool, error) { - if !util.IsTestMode() { - cr := deployContext.CheCluster - if !cr.Status.KeycloakProvisoned { - if err := ProvisionKeycloakResources(deployContext); err != nil { - return false, err - } - - for { - cr.Status.KeycloakProvisoned = true - if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with Keycloak", "true"); err != nil && - apierrors.IsConflict(err) { - - deploy.ReloadCheClusterCR(deployContext) - continue - } - break - } - } - - // Updates keycloak if chehost has been changed - if !keycloakUpdated || keycloakCheHost != deployContext.CheCluster.Spec.Server.CheHost { - if _, err := util.K8sclient.ExecIntoPod( - deployContext.CheCluster, - deploy.IdentityProviderName, - GetKeycloakUpdateCommand, - "Update redirect URI-s and webOrigins"); err != nil { - return false, err - } else { - keycloakUpdated = true - keycloakCheHost = deployContext.CheCluster.Spec.Server.CheHost - } - } - } - - return true, nil -} - -func syncOpenShiftIdentityProvider(deployContext *deploy.DeployContext) (bool, error) { - cr := deployContext.CheCluster - if util.IsOpenShift && cr.IsOpenShiftOAuthEnabled() { - return SyncOpenShiftIdentityProviderItems(deployContext) - } - return true, nil -} - -func syncNativeIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { - cr := deployContext.CheCluster - - if err := resolveOpenshiftOAuthClientName(deployContext); err != nil { - return false, err - } - if err := resolveOpenshiftOAuthClientSecret(deployContext); err != nil { - return false, err - } - - if util.IsOpenShift { - redirectURIs := []string{"https://" + cr.Spec.Server.CheHost + "/oauth/callback"} - oAuthClient := deploy.GetOAuthClientSpec(cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret, redirectURIs) - provisioned, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) - if !provisioned { - return false, err - } - } - - return true, nil -} - -func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { - cr := deployContext.CheCluster - - if err := resolveOpenshiftOAuthClientName(deployContext); err != nil { - return false, err - } - if err := resolveOpenshiftOAuthClientSecret(deployContext); err != nil { - return false, err - } - - keycloakURL := cr.Spec.Auth.IdentityProviderURL - cheFlavor := deploy.DefaultCheFlavor(cr) - keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) - oAuthClient := deploy.GetKeycloakOAuthClientSpec(cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret, keycloakURL, keycloakRealm, util.IsOpenShift4) - provisioned, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) - if !provisioned { - return false, err - } - - if !util.IsTestMode() { - if !cr.Status.OpenShiftoAuthProvisioned { - // note that this uses the instance.Spec.Auth.IdentityProviderRealm and instance.Spec.Auth.IdentityProviderClientId. - // because we're not doing much of a change detection on those fields, we can't react on them changing here. - _, err := util.K8sclient.ExecIntoPod( - cr, - deploy.IdentityProviderName, - func(cr *orgv1.CheCluster) (string, error) { - return GetOpenShiftIdentityProviderProvisionCommand(cr, cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret) - }, - "Create OpenShift identity provider") - if err != nil { - return false, err - } - - for { - cr.Status.OpenShiftoAuthProvisioned = true - if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "true"); err != nil && - apierrors.IsConflict(err) { - - deploy.ReloadCheClusterCR(deployContext) - continue - } - break - } - } - } - return true, nil -} - -func resolveOpenshiftOAuthClientName(deployContext *deploy.DeployContext) error { - cr := deployContext.CheCluster - oAuthClientName := cr.Spec.Auth.OAuthClientName - if len(oAuthClientName) < 1 { - oAuthClientName = cr.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6)) - cr.Spec.Auth.OAuthClientName = oAuthClientName - if err := deploy.UpdateCheCRSpec(deployContext, "oAuthClient name", oAuthClientName); err != nil { - return err - } - } - return nil -} - -func resolveOpenshiftOAuthClientSecret(deployContext *deploy.DeployContext) error { - cr := deployContext.CheCluster - oauthSecret := cr.Spec.Auth.OAuthSecret - if len(oauthSecret) < 1 { - oauthSecret = util.GeneratePasswd(12) - cr.Spec.Auth.OAuthSecret = oauthSecret - if err := deploy.UpdateCheCRSpec(deployContext, "oAuth secret name", oauthSecret); err != nil { - return err - } - } - return nil -} - -// SyncGitHubOAuth provisions GitHub OAuth if secret with annotation -// `che.eclipse.org/github-oauth-credentials=true` or `che.eclipse.org/oauth-scm-configuration=github` -// is mounted into a container -func SyncGitHubOAuth(deployContext *deploy.DeployContext) (bool, error) { - // get legacy secret - legacySecrets, err := deploy.GetSecrets(deployContext, map[string]string{ - deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, - deploy.KubernetesComponentLabelKey: deploy.IdentityProviderName + "-secret", - }, map[string]string{ - deploy.CheEclipseOrgGithubOAuthCredentials: "true", - }) - if err != nil { - return false, err - } - - secrets, err := deploy.GetSecrets(deployContext, map[string]string{ - deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, - deploy.KubernetesComponentLabelKey: deploy.OAuthScmConfiguration, - }, map[string]string{ - deploy.CheEclipseOrgOAuthScmServer: "github", - }) - - if err != nil { - return false, err - } else if len(secrets)+len(legacySecrets) > 1 { - return false, errors.New("More than 1 GitHub OAuth configuration secrets found") - } - - isGitHubOAuthCredentialsExists := len(secrets) == 1 || len(legacySecrets) == 1 - cr := deployContext.CheCluster - - if isGitHubOAuthCredentialsExists { - if !cr.Status.GitHubOAuthProvisioned { - if !util.IsTestMode() { - _, err := util.K8sclient.ExecIntoPod( - cr, - deploy.IdentityProviderName, - func(cr *orgv1.CheCluster) (string, error) { - return GetGitHubIdentityProviderCreateCommand(deployContext) - }, - "Create GitHub OAuth") - if err != nil { - return false, err - } - } - - cr.Status.GitHubOAuthProvisioned = true - if err := deploy.UpdateCheCRStatus(deployContext, "status: GitHub OAuth provisioned", "true"); err != nil { - return false, err - } - } - } else { - if cr.Status.GitHubOAuthProvisioned { - if !util.IsTestMode() { - _, err := util.K8sclient.ExecIntoPod( - cr, - deploy.IdentityProviderName, - func(cr *orgv1.CheCluster) (string, error) { - return GetIdentityProviderDeleteCommand(cr, "github") - }, - "Delete GitHub OAuth") - if err != nil { - return false, err - } - } - - cr.Status.GitHubOAuthProvisioned = false - if err := deploy.UpdateCheCRStatus(deployContext, "status: GitHub OAuth provisioned", "false"); err != nil { - return false, err - } - } - } - - return true, nil -} - -func ReconcileIdentityProvider(deployContext *deploy.DeployContext) (deleted bool, err error) { - if !deployContext.CheCluster.IsOpenShiftOAuthEnabled() && deployContext.CheCluster.Status.OpenShiftoAuthProvisioned == true { - keycloakDeployment := &appsv1.Deployment{} - if err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: deploy.IdentityProviderName, Namespace: deployContext.CheCluster.Namespace}, keycloakDeployment); err != nil { - logrus.Errorf("Deployment %s not found: %s", keycloakDeployment.Name, err.Error()) - } - - providerName := "openshift-v3" - if util.IsOpenShift4 { - providerName = "openshift-v4" - } - _, err := util.K8sclient.ExecIntoPod( - deployContext.CheCluster, - keycloakDeployment.Name, - func(cr *orgv1.CheCluster) (string, error) { - return GetIdentityProviderDeleteCommand(deployContext.CheCluster, providerName) - }, - "delete OpenShift identity provider") - if err == nil { - oAuthClient := &oauth.OAuthClient{} - oAuthClientName := deployContext.CheCluster.Spec.Auth.OAuthClientName - if err := deployContext.ClusterAPI.NonCachingClient.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil { - logrus.Errorf("OAuthClient %s not found: %s", oAuthClient.Name, err.Error()) - } - if err := deployContext.ClusterAPI.NonCachingClient.Delete(context.TODO(), oAuthClient); err != nil { - logrus.Errorf("Failed to delete %s %s: %s", oAuthClient.Kind, oAuthClient.Name, err.Error()) - } - return true, nil - } - return false, err - } - return false, nil -} - -func createGatewayConfig(cheCluster *orgv1.CheCluster) *gateway.TraefikConfig { - cfg := gateway.CreateCommonTraefikConfig( - deploy.IdentityProviderName, - "PathPrefix(`/auth`)", - 10, - "http://"+deploy.IdentityProviderName+":8080", - []string{}) - - if util.IsOpenShift && cheCluster.IsNativeUserModeEnabled() { - cfg.AddAuthHeaderRewrite(deploy.IdentityProviderName) - } - - return cfg -} diff --git a/pkg/deploy/identity-provider/identity_provider_test.go b/pkg/deploy/identity-provider/identity_provider_test.go deleted file mode 100644 index 271b70bf75..0000000000 --- a/pkg/deploy/identity-provider/identity_provider_test.go +++ /dev/null @@ -1,211 +0,0 @@ -// -// Copyright (c) 2019-2021 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 identity_provider - -import ( - "os" - "reflect" - - "github.com/eclipse-che/che-operator/pkg/deploy" - - "github.com/google/go-cmp/cmp" - - orgv1 "github.com/eclipse-che/che-operator/api/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - "testing" -) - -func TestSyncGitHubOAuth(t *testing.T) { - type testCase struct { - name string - initCR *orgv1.CheCluster - expectedCR *orgv1.CheCluster - initObjects []runtime.Object - } - - testCases := []testCase{ - { - name: "Should provision GitHub OAuth with legacy secret", - initCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "0", - }, - }, - expectedCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "1", - }, - Status: orgv1.CheClusterStatus{ - GitHubOAuthProvisioned: true, - }, - }, - initObjects: []runtime.Object{ - &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "github-credentials", - Namespace: "eclipse-che", - Labels: map[string]string{ - deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, - deploy.KubernetesComponentLabelKey: "keycloak-secret", - }, - Annotations: map[string]string{ - deploy.CheEclipseOrgGithubOAuthCredentials: "true", - }, - }, - Data: map[string][]byte{ - "key": []byte("key-data"), - }, - }, - }, - }, - { - name: "Should provision GitHub OAuth", - initCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "0", - }, - }, - expectedCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "1", - }, - Status: orgv1.CheClusterStatus{ - GitHubOAuthProvisioned: true, - }, - }, - initObjects: []runtime.Object{ - &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "github-oauth-config", - Namespace: "eclipse-che", - Labels: map[string]string{ - "app.kubernetes.io/part-of": "che.eclipse.org", - "app.kubernetes.io/component": "oauth-scm-configuration", - }, - Annotations: map[string]string{ - "che.eclipse.org/oauth-scm-server": "github", - }, - }, - }, - }, - }, - { - name: "Should not provision GitHub OAuth", - initCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "0", - }, - }, - expectedCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "0", - }, - }, - initObjects: []runtime.Object{ - &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "github-credentials", - Namespace: "eclipse-che", - Labels: map[string]string{ - deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, - deploy.KubernetesComponentLabelKey: "keycloak-secret", - }, - }, - Data: map[string][]byte{ - "key": []byte("key-data"), - }, - }, - }, - }, - { - name: "Should delete GitHub OAuth", - initCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "0", - }, - Status: orgv1.CheClusterStatus{ - GitHubOAuthProvisioned: true, - }, - }, - expectedCR: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "che-cluster", - Namespace: "eclipse-che", - ResourceVersion: "1", - }, - Status: orgv1.CheClusterStatus{ - GitHubOAuthProvisioned: false, - }, - }, - initObjects: []runtime.Object{}, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects, testCase.initCR) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &deploy.DeployContext{ - CheCluster: testCase.initCR, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - Scheme: scheme.Scheme, - }, - } - - _, err := SyncGitHubOAuth(deployContext) - if err != nil { - t.Fatalf("Error mounting secret: %v", err) - } - - if !reflect.DeepEqual(testCase.expectedCR, testCase.initCR) { - t.Errorf("Expected CR and CR returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedCR, testCase.initCR)) - } - }) - } -} diff --git a/pkg/deploy/identity-provider/init_test.go b/pkg/deploy/identity-provider/init_test.go index 67d990dbc4..b836a16fac 100644 --- a/pkg/deploy/identity-provider/init_test.go +++ b/pkg/deploy/identity-provider/init_test.go @@ -9,7 +9,7 @@ // Contributors: // Red Hat, Inc. - initial API and implementation // -package identity_provider +package identityprovider import "github.com/eclipse-che/che-operator/pkg/deploy" diff --git a/pkg/deploy/identity-provider/keycloak_readiness.go b/pkg/deploy/identity-provider/keycloak_readiness.go index c70a756685..a34e840b86 100644 --- a/pkg/deploy/identity-provider/keycloak_readiness.go +++ b/pkg/deploy/identity-provider/keycloak_readiness.go @@ -9,7 +9,7 @@ // Contributors: // Red Hat, Inc. - initial API and implementation // -package identity_provider +package identityprovider import ( "fmt" diff --git a/pkg/deploy/oauthclient.go b/pkg/deploy/oauthclient.go deleted file mode 100644 index b5525a3652..0000000000 --- a/pkg/deploy/oauthclient.go +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) 2019-2021 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 deploy - -import ( - "strings" - - oauth "github.com/openshift/api/oauth/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -const ( - OAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org" -) - -func GetKeycloakOAuthClientSpec(name string, oauthSecret string, keycloakURL string, keycloakRealm string, isOpenShift4 bool) *oauth.OAuthClient { - providerName := "openshift-v3" - if isOpenShift4 { - providerName = "openshift-v4" - } - - redirectURLSuffix := "/realms/" + keycloakRealm + "/broker/" + providerName + "/endpoint" - redirectURIs := []string{ - keycloakURL + redirectURLSuffix, - } - - keycloakURL = strings.NewReplacer("https://", "", "http://", "").Replace(keycloakURL) - if !strings.Contains(keycloakURL, "://") { - redirectURIs = []string{ - "http://" + keycloakURL + redirectURLSuffix, - "https://" + keycloakURL + redirectURLSuffix, - } - } - return GetOAuthClientSpec(name, oauthSecret, redirectURIs) -} - -func GetOAuthClientSpec(name string, oauthSecret string, redirectURIs []string) *oauth.OAuthClient { - return &oauth.OAuthClient{ - TypeMeta: metav1.TypeMeta{ - Kind: "OAuthClient", - APIVersion: oauth.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"app": "che"}, - }, - - Secret: oauthSecret, - RedirectURIs: redirectURIs, - GrantMethod: oauth.GrantHandlerPrompt, - } -} - -func ReconcileOAuthClientFinalizer(deployContext *DeployContext) (err error) { - cheCluster := deployContext.CheCluster - if deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() { - return AppendFinalizer(deployContext, OAuthFinalizerName) - } else { - oAuthClientName := cheCluster.Spec.Auth.OAuthClientName - return DeleteObjectWithFinalizer(deployContext, types.NamespacedName{Name: oAuthClientName}, &oauth.OAuthClient{}, OAuthFinalizerName) - } -} diff --git a/pkg/deploy/pluginregistry/pluginregistry.go b/pkg/deploy/pluginregistry/pluginregistry.go index 61940720fd..a8ffda0427 100644 --- a/pkg/deploy/pluginregistry/pluginregistry.go +++ b/pkg/deploy/pluginregistry/pluginregistry.go @@ -16,79 +16,93 @@ import ( "strings" "github.com/eclipse-che/che-operator/pkg/deploy/gateway" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/deploy/expose" ) -type PluginRegistry struct { - deployContext *deploy.DeployContext +type PluginRegistryReconciler struct { + deploy.Reconcilable } -func NewPluginRegistry(deployContext *deploy.DeployContext) *PluginRegistry { - return &PluginRegistry{ - deployContext: deployContext, - } +func NewPluginRegistryReconciler() *PluginRegistryReconciler { + return &PluginRegistryReconciler{} } -func (p *PluginRegistry) SyncAll() (bool, error) { - done, err := p.SyncService() +func (p *PluginRegistryReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + if ctx.CheCluster.Spec.Server.ExternalPluginRegistry { + if ctx.CheCluster.Spec.Server.PluginRegistryUrl != ctx.CheCluster.Status.PluginRegistryURL { + ctx.CheCluster.Status.PluginRegistryURL = ctx.CheCluster.Spec.Server.PluginRegistryUrl + if err := deploy.UpdateCheCRStatus(ctx, "PluginRegistryUrl", ctx.CheCluster.Spec.Server.PluginRegistryUrl); err != nil { + return reconcile.Result{}, false, err + } + } + + return reconcile.Result{}, true, nil + } + + done, err := p.syncService(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - endpoint, done, err := p.ExposeEndpoint() + endpoint, done, err := p.ExposeEndpoint(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.UpdateStatus(endpoint) + done, err = p.updateStatus(endpoint, ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.SyncConfigMap() + done, err = p.syncConfigMap(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.SyncDeployment() + done, err = p.syncDeployment(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - return true, nil + return reconcile.Result{}, true, nil +} + +func (p *PluginRegistryReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil } -func (p *PluginRegistry) SyncService() (bool, error) { +func (p *PluginRegistryReconciler) syncService(ctx *deploy.DeployContext) (bool, error) { return deploy.SyncServiceToCluster( - p.deployContext, + ctx, deploy.PluginRegistryName, []string{"http"}, []int32{8080}, deploy.PluginRegistryName) } -func (p *PluginRegistry) SyncConfigMap() (bool, error) { - data, err := p.GetConfigMapData() +func (p *PluginRegistryReconciler) syncConfigMap(ctx *deploy.DeployContext) (bool, error) { + data, err := p.getConfigMapData(ctx) if err != nil { return false, err } - return deploy.SyncConfigMapDataToCluster(p.deployContext, deploy.PluginRegistryName, data, deploy.PluginRegistryName) + return deploy.SyncConfigMapDataToCluster(ctx, deploy.PluginRegistryName, data, deploy.PluginRegistryName) } -func (p *PluginRegistry) ExposeEndpoint() (string, bool, error) { +func (p *PluginRegistryReconciler) ExposeEndpoint(ctx *deploy.DeployContext) (string, bool, error) { return expose.Expose( - p.deployContext, + ctx, deploy.PluginRegistryName, - p.deployContext.CheCluster.Spec.Server.PluginRegistryRoute, - p.deployContext.CheCluster.Spec.Server.PluginRegistryIngress, - p.createGatewayConfig()) + ctx.CheCluster.Spec.Server.PluginRegistryRoute, + ctx.CheCluster.Spec.Server.PluginRegistryIngress, + p.createGatewayConfig(ctx)) } -func (p *PluginRegistry) UpdateStatus(endpoint string) (bool, error) { +func (p *PluginRegistryReconciler) updateStatus(endpoint string, ctx *deploy.DeployContext) (bool, error) { var pluginRegistryURL string - if p.deployContext.CheCluster.Spec.Server.TlsSupport { + if ctx.CheCluster.Spec.Server.TlsSupport { pluginRegistryURL = "https://" + endpoint } else { pluginRegistryURL = "http://" + endpoint @@ -101,9 +115,9 @@ func (p *PluginRegistry) UpdateStatus(endpoint string) (bool, error) { pluginRegistryURL = pluginRegistryURL + "v3" } - if pluginRegistryURL != p.deployContext.CheCluster.Status.PluginRegistryURL { - p.deployContext.CheCluster.Status.PluginRegistryURL = pluginRegistryURL - if err := deploy.UpdateCheCRStatus(p.deployContext, "status: Plugin Registry URL", pluginRegistryURL); err != nil { + if pluginRegistryURL != ctx.CheCluster.Status.PluginRegistryURL { + ctx.CheCluster.Status.PluginRegistryURL = pluginRegistryURL + if err := deploy.UpdateCheCRStatus(ctx, "status: Plugin Registry URL", pluginRegistryURL); err != nil { return false, err } } @@ -111,12 +125,12 @@ func (p *PluginRegistry) UpdateStatus(endpoint string) (bool, error) { return true, nil } -func (p *PluginRegistry) SyncDeployment() (bool, error) { - spec := p.GetPluginRegistryDeploymentSpec() - return deploy.SyncDeploymentSpecToCluster(p.deployContext, spec, deploy.DefaultDeploymentDiffOpts) +func (p *PluginRegistryReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, error) { + spec := p.getPluginRegistryDeploymentSpec(ctx) + return deploy.SyncDeploymentSpecToCluster(ctx, spec, deploy.DefaultDeploymentDiffOpts) } -func (p *PluginRegistry) createGatewayConfig() *gateway.TraefikConfig { +func (p *PluginRegistryReconciler) createGatewayConfig(ctx *deploy.DeployContext) *gateway.TraefikConfig { pathPrefix := "/" + deploy.PluginRegistryName cfg := gateway.CreateCommonTraefikConfig( deploy.PluginRegistryName, diff --git a/pkg/deploy/pluginregistry/pluginregistry_configmap.go b/pkg/deploy/pluginregistry/pluginregistry_configmap.go index dd5635255e..d7b6ace49e 100644 --- a/pkg/deploy/pluginregistry/pluginregistry_configmap.go +++ b/pkg/deploy/pluginregistry/pluginregistry_configmap.go @@ -13,6 +13,8 @@ package pluginregistry import ( "encoding/json" + + "github.com/eclipse-che/che-operator/pkg/deploy" ) type PluginRegistryConfigMap struct { @@ -21,12 +23,12 @@ type PluginRegistryConfigMap struct { ChePluginRegistryURL string `json:"CHE_PLUGIN_REGISTRY_URL"` } -func (p *PluginRegistry) GetConfigMapData() (map[string]string, error) { +func (p *PluginRegistryReconciler) getConfigMapData(ctx *deploy.DeployContext) (map[string]string, error) { pluginRegistryEnv := make(map[string]string) data := &PluginRegistryConfigMap{ - CheSidecarContainersRegistryURL: p.deployContext.CheCluster.Spec.Server.AirGapContainerRegistryHostname, - CheSidecarContainersRegistryOrganization: p.deployContext.CheCluster.Spec.Server.AirGapContainerRegistryOrganization, - ChePluginRegistryURL: p.deployContext.CheCluster.Status.PluginRegistryURL, + CheSidecarContainersRegistryURL: ctx.CheCluster.Spec.Server.AirGapContainerRegistryHostname, + CheSidecarContainersRegistryOrganization: ctx.CheCluster.Spec.Server.AirGapContainerRegistryOrganization, + ChePluginRegistryURL: ctx.CheCluster.Status.PluginRegistryURL, } out, err := json.Marshal(data) diff --git a/pkg/deploy/pluginregistry/pluginregistry_deployment.go b/pkg/deploy/pluginregistry/pluginregistry_deployment.go index 097160b14e..dc91b680b0 100644 --- a/pkg/deploy/pluginregistry/pluginregistry_deployment.go +++ b/pkg/deploy/pluginregistry/pluginregistry_deployment.go @@ -19,34 +19,34 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (p *PluginRegistry) GetPluginRegistryDeploymentSpec() *appsv1.Deployment { +func (p *PluginRegistryReconciler) getPluginRegistryDeploymentSpec(ctx *deploy.DeployContext) *appsv1.Deployment { registryType := "plugin" - registryImage := util.GetValue(p.deployContext.CheCluster.Spec.Server.PluginRegistryImage, deploy.DefaultPluginRegistryImage(p.deployContext.CheCluster)) - registryImagePullPolicy := corev1.PullPolicy(util.GetValue(string(p.deployContext.CheCluster.Spec.Server.PluginRegistryPullPolicy), deploy.DefaultPullPolicyFromDockerImage(registryImage))) + registryImage := util.GetValue(ctx.CheCluster.Spec.Server.PluginRegistryImage, deploy.DefaultPluginRegistryImage(ctx.CheCluster)) + registryImagePullPolicy := corev1.PullPolicy(util.GetValue(string(ctx.CheCluster.Spec.Server.PluginRegistryPullPolicy), deploy.DefaultPullPolicyFromDockerImage(registryImage))) probePath := "/v3/plugins/" pluginImagesEnv := util.GetEnvByRegExp("^.*plugin_registry_image.*$") resources := corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.PluginRegistryMemoryRequest, + ctx.CheCluster.Spec.Server.PluginRegistryMemoryRequest, deploy.DefaultPluginRegistryMemoryRequest), corev1.ResourceCPU: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.PluginRegistryCpuRequest, + ctx.CheCluster.Spec.Server.PluginRegistryCpuRequest, deploy.DefaultPluginRegistryCpuRequest), }, Limits: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.PluginRegistryMemoryLimit, + ctx.CheCluster.Spec.Server.PluginRegistryMemoryLimit, deploy.DefaultPluginRegistryMemoryLimit), corev1.ResourceCPU: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Server.PluginRegistryCpuLimit, + ctx.CheCluster.Spec.Server.PluginRegistryCpuLimit, deploy.DefaultPluginRegistryCpuLimit), }, } return registry.GetSpecRegistryDeployment( - p.deployContext, + ctx, registryType, registryImage, pluginImagesEnv, diff --git a/pkg/deploy/pluginregistry/pluginregistry_deployment_test.go b/pkg/deploy/pluginregistry/pluginregistry_deployment_test.go index a5c607933a..fb48eb8aea 100644 --- a/pkg/deploy/pluginregistry/pluginregistry_deployment_test.go +++ b/pkg/deploy/pluginregistry/pluginregistry_deployment_test.go @@ -12,8 +12,6 @@ package pluginregistry import ( - "os" - "github.com/eclipse-che/che-operator/pkg/util" "github.com/eclipse-che/che-operator/pkg/deploy" @@ -21,10 +19,6 @@ import ( orgv1 "github.com/eclipse-che/che-operator/api/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" "testing" ) @@ -51,6 +45,7 @@ func TestGetPluginRegistryDeploymentSpec(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, }, }, @@ -64,6 +59,7 @@ func TestGetPluginRegistryDeploymentSpec(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Server: orgv1.CheClusterSpecServer{ @@ -79,22 +75,10 @@ func TestGetPluginRegistryDeploymentSpec(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &deploy.DeployContext{ - CheCluster: testCase.cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - pluginregistry := NewPluginRegistry(deployContext) - deployment := pluginregistry.GetPluginRegistryDeploymentSpec() + pluginregistry := NewPluginRegistryReconciler() + deployment := pluginregistry.getPluginRegistryDeploymentSpec(ctx) util.CompareResources(deployment, util.TestExpectedResources{ diff --git a/pkg/deploy/pluginregistry/pluginregistry_test.go b/pkg/deploy/pluginregistry/pluginregistry_test.go index ba146848f8..9e0beac9cf 100644 --- a/pkg/deploy/pluginregistry/pluginregistry_test.go +++ b/pkg/deploy/pluginregistry/pluginregistry_test.go @@ -12,81 +12,31 @@ package pluginregistry import ( - "context" - "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/util" + "github.com/stretchr/testify/assert" - orgv1 "github.com/eclipse-che/che-operator/api/v1" routev1 "github.com/openshift/api/route/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "testing" ) -func TestPluginRegistrySyncAll(t *testing.T) { - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - +func TestPluginRegistryReconcile(t *testing.T) { util.IsOpenShift = true - - pluginregistry := NewPluginRegistry(deployContext) - done, err := pluginregistry.SyncAll() - if !done || err != nil { - t.Fatalf("Failed to sync Plugin Registry: %v", err) - } - - // check service - service := &corev1.Service{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, service) - if err != nil { - t.Fatalf("Service not found: %v", err) - } - - // check endpoint - route := &routev1.Route{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, route) - if err != nil { - t.Fatalf("Route not found: %v", err) - } - - // check configmap - cm := &corev1.ConfigMap{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, cm) - if err != nil { - t.Fatalf("Config Map not found: %v", err) - } - - // check deployment - deployment := &appsv1.Deployment{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, deployment) - if err != nil { - t.Fatalf("Deployment not found: %v", err) - } - - if cheCluster.Status.PluginRegistryURL == "" { - t.Fatalf("Status wasn't updated") - } + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) + + pluginregistry := NewPluginRegistryReconciler() + _, done, err := pluginregistry.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &corev1.Service{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &routev1.Route{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &corev1.ConfigMap{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.NotEmpty(t, ctx.CheCluster.Status.PluginRegistryURL) } diff --git a/pkg/deploy/postgres/postgres.go b/pkg/deploy/postgres/postgres.go index 64c4194795..a67465b3d0 100644 --- a/pkg/deploy/postgres/postgres.go +++ b/pkg/deploy/postgres/postgres.go @@ -21,62 +21,69 @@ import ( "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -type Postgres struct { - deployContext *deploy.DeployContext +type PostgresReconciler struct { + deploy.Reconcilable } -func NewPostgres(deployContext *deploy.DeployContext) *Postgres { - return &Postgres{ - deployContext: deployContext, - } +func NewPostgresReconciler() *PostgresReconciler { + return &PostgresReconciler{} } -func (p *Postgres) SyncAll() (bool, error) { - done, err := p.SyncService() +func (p *PostgresReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + if ctx.CheCluster.Spec.Database.ExternalDb { + return reconcile.Result{}, true, nil + } + + done, err := p.syncService(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.SyncPVC() + done, err = p.syncPVC(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - done, err = p.SyncDeployment() + done, err = p.syncDeployment(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } - if !p.deployContext.CheCluster.Status.DbProvisoned { + if !ctx.CheCluster.Status.DbProvisoned { if !util.IsTestMode() { // ignore in tests - done, err = p.ProvisionDB() + done, err = p.provisionDB(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } } } - if p.deployContext.CheCluster.Spec.Database.PostgresVersion == "" { + if ctx.CheCluster.Spec.Database.PostgresVersion == "" { if !util.IsTestMode() { // ignore in tests - done, err := p.setDbVersion() + done, err := p.setDbVersion(ctx) if !done { - return false, err + return reconcile.Result{}, false, err } } } - return true, nil + return reconcile.Result{}, true, nil +} + +func (p *PostgresReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil } -func (p *Postgres) SyncService() (bool, error) { - return deploy.SyncServiceToCluster(p.deployContext, deploy.PostgresName, []string{deploy.PostgresName}, []int32{5432}, deploy.PostgresName) +func (p *PostgresReconciler) syncService(ctx *deploy.DeployContext) (bool, error) { + return deploy.SyncServiceToCluster(ctx, deploy.PostgresName, []string{deploy.PostgresName}, []int32{5432}, deploy.PostgresName) } -func (p *Postgres) SyncPVC() (bool, error) { - pvcClaimSize := util.GetValue(p.deployContext.CheCluster.Spec.Database.PvcClaimSize, deploy.DefaultPostgresPvcClaimSize) - done, err := deploy.SyncPVCToCluster(p.deployContext, deploy.DefaultPostgresVolumeClaimName, pvcClaimSize, deploy.PostgresName) +func (p *PostgresReconciler) syncPVC(ctx *deploy.DeployContext) (bool, error) { + pvcClaimSize := util.GetValue(ctx.CheCluster.Spec.Database.PvcClaimSize, deploy.DefaultPostgresPvcClaimSize) + done, err := deploy.SyncPVCToCluster(ctx, deploy.DefaultPostgresVolumeClaimName, pvcClaimSize, deploy.PostgresName) if !done { if err == nil { logrus.Infof("Waiting on pvc '%s' to be bound. Sometimes PVC can be bound only when the first consumer is created.", deploy.DefaultPostgresVolumeClaimName) @@ -85,9 +92,9 @@ func (p *Postgres) SyncPVC() (bool, error) { return done, err } -func (p *Postgres) SyncDeployment() (bool, error) { +func (p *PostgresReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, error) { clusterDeployment := &appsv1.Deployment{} - exists, err := deploy.GetNamespacedObject(p.deployContext, deploy.PostgresName, clusterDeployment) + exists, err := deploy.GetNamespacedObject(ctx, deploy.PostgresName, clusterDeployment) if err != nil { return false, err } @@ -96,20 +103,20 @@ func (p *Postgres) SyncDeployment() (bool, error) { clusterDeployment = nil } - specDeployment, err := p.GetDeploymentSpec(clusterDeployment) + specDeployment, err := p.getDeploymentSpec(clusterDeployment, ctx) if err != nil { return false, err } - return deploy.SyncDeploymentSpecToCluster(p.deployContext, specDeployment, deploy.DefaultDeploymentDiffOpts) + return deploy.SyncDeploymentSpecToCluster(ctx, specDeployment, deploy.DefaultDeploymentDiffOpts) } -func (p *Postgres) ProvisionDB() (bool, error) { - identityProviderPostgresPassword := p.deployContext.CheCluster.Spec.Auth.IdentityProviderPostgresPassword - identityProviderPostgresSecret := p.deployContext.CheCluster.Spec.Auth.IdentityProviderPostgresSecret +func (p *PostgresReconciler) provisionDB(ctx *deploy.DeployContext) (bool, error) { + identityProviderPostgresPassword := ctx.CheCluster.Spec.Auth.IdentityProviderPostgresPassword + identityProviderPostgresSecret := ctx.CheCluster.Spec.Auth.IdentityProviderPostgresSecret if identityProviderPostgresSecret != "" { secret := &corev1.Secret{} - exists, err := deploy.GetNamespacedObject(p.deployContext, identityProviderPostgresSecret, secret) + exists, err := deploy.GetNamespacedObject(ctx, identityProviderPostgresSecret, secret) if err != nil { return false, err } else if !exists { @@ -119,7 +126,7 @@ func (p *Postgres) ProvisionDB() (bool, error) { } _, err := util.K8sclient.ExecIntoPod( - p.deployContext.CheCluster, + ctx.CheCluster, deploy.PostgresName, func(cr *orgv1.CheCluster) (string, error) { return getPostgresProvisionCommand(identityProviderPostgresPassword), nil @@ -129,8 +136,8 @@ func (p *Postgres) ProvisionDB() (bool, error) { return false, err } - p.deployContext.CheCluster.Status.DbProvisoned = true - err = deploy.UpdateCheCRStatus(p.deployContext, "status: provisioned with DB and user", "true") + ctx.CheCluster.Status.DbProvisoned = true + err = deploy.UpdateCheCRStatus(ctx, "status: provisioned with DB and user", "true") if err != nil { return false, err } @@ -138,9 +145,9 @@ func (p *Postgres) ProvisionDB() (bool, error) { return true, nil } -func (p *Postgres) setDbVersion() (bool, error) { +func (p *PostgresReconciler) setDbVersion(ctx *deploy.DeployContext) (bool, error) { postgresVersion, err := util.K8sclient.ExecIntoPod( - p.deployContext.CheCluster, + ctx.CheCluster, deploy.PostgresName, func(cr *orgv1.CheCluster) (string, error) { // don't take into account bugfix version @@ -152,8 +159,8 @@ func (p *Postgres) setDbVersion() (bool, error) { } postgresVersion = strings.TrimSpace(postgresVersion) - p.deployContext.CheCluster.Spec.Database.PostgresVersion = postgresVersion - err = deploy.UpdateCheCRSpec(p.deployContext, "database.postgresVersion", postgresVersion) + ctx.CheCluster.Spec.Database.PostgresVersion = postgresVersion + err = deploy.UpdateCheCRSpec(ctx, "database.postgresVersion", postgresVersion) if err != nil { return false, err } diff --git a/pkg/deploy/postgres/postgres_deployment.go b/pkg/deploy/postgres/postgres_deployment.go index 40dc9534a9..334aa7b01d 100644 --- a/pkg/deploy/postgres/postgres_deployment.go +++ b/pkg/deploy/postgres/postgres_deployment.go @@ -32,15 +32,15 @@ var ( postgresAdminPassword = util.GeneratePasswd(12) ) -func (p *Postgres) GetDeploymentSpec(clusterDeployment *appsv1.Deployment) (*appsv1.Deployment, error) { +func (p *PostgresReconciler) getDeploymentSpec(clusterDeployment *appsv1.Deployment, ctx *deploy.DeployContext) (*appsv1.Deployment, error) { terminationGracePeriodSeconds := int64(30) - labels, labelSelector := deploy.GetLabelsAndSelector(p.deployContext.CheCluster, deploy.PostgresName) - chePostgresDb := util.GetValue(p.deployContext.CheCluster.Spec.Database.ChePostgresDb, "dbche") - postgresImage, err := getPostgresImage(clusterDeployment, p.deployContext.CheCluster) + labels, labelSelector := deploy.GetLabelsAndSelector(ctx.CheCluster, deploy.PostgresName) + chePostgresDb := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresDb, "dbche") + postgresImage, err := getPostgresImage(clusterDeployment, ctx.CheCluster) if err != nil { return nil, err } - pullPolicy := corev1.PullPolicy(util.GetValue(string(p.deployContext.CheCluster.Spec.Database.PostgresImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(postgresImage))) + pullPolicy := corev1.PullPolicy(util.GetValue(string(ctx.CheCluster.Spec.Database.PostgresImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(postgresImage))) if clusterDeployment != nil { clusterContainer := &clusterDeployment.Spec.Template.Spec.Containers[0] @@ -57,7 +57,7 @@ func (p *Postgres) GetDeploymentSpec(clusterDeployment *appsv1.Deployment) (*app }, ObjectMeta: metav1.ObjectMeta{ Name: deploy.PostgresName, - Namespace: p.deployContext.CheCluster.Namespace, + Namespace: ctx.CheCluster.Namespace, Labels: labels, }, Spec: appsv1.DeploymentSpec{ @@ -95,18 +95,18 @@ func (p *Postgres) GetDeploymentSpec(clusterDeployment *appsv1.Deployment) (*app Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Database.ChePostgresContainerResources.Requests.Memory, + ctx.CheCluster.Spec.Database.ChePostgresContainerResources.Requests.Memory, deploy.DefaultPostgresMemoryRequest), corev1.ResourceCPU: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Database.ChePostgresContainerResources.Requests.Cpu, + ctx.CheCluster.Spec.Database.ChePostgresContainerResources.Requests.Cpu, deploy.DefaultPostgresCpuRequest), }, Limits: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Database.ChePostgresContainerResources.Limits.Memory, + ctx.CheCluster.Spec.Database.ChePostgresContainerResources.Limits.Memory, deploy.DefaultPostgresMemoryLimit), corev1.ResourceCPU: util.GetResourceQuantity( - p.deployContext.CheCluster.Spec.Database.ChePostgresContainerResources.Limits.Cpu, + ctx.CheCluster.Spec.Database.ChePostgresContainerResources.Limits.Cpu, deploy.DefaultPostgresCpuLimit), }, }, @@ -170,7 +170,7 @@ func (p *Postgres) GetDeploymentSpec(clusterDeployment *appsv1.Deployment) (*app container := &deployment.Spec.Template.Spec.Containers[0] - chePostgresSecret := p.deployContext.CheCluster.Spec.Database.ChePostgresSecret + chePostgresSecret := ctx.CheCluster.Spec.Database.ChePostgresSecret if len(chePostgresSecret) > 0 { container.Env = append(container.Env, corev1.EnvVar{ @@ -198,10 +198,10 @@ func (p *Postgres) GetDeploymentSpec(clusterDeployment *appsv1.Deployment) (*app container.Env = append(container.Env, corev1.EnvVar{ Name: "POSTGRESQL_USER", - Value: p.deployContext.CheCluster.Spec.Database.ChePostgresUser, + Value: ctx.CheCluster.Spec.Database.ChePostgresUser, }, corev1.EnvVar{ Name: "POSTGRESQL_PASSWORD", - Value: p.deployContext.CheCluster.Spec.Database.ChePostgresPassword, + Value: ctx.CheCluster.Spec.Database.ChePostgresPassword, }) } diff --git a/pkg/deploy/postgres/postgres_test.go b/pkg/deploy/postgres/postgres_test.go index 127b424e73..3fdcad921c 100644 --- a/pkg/deploy/postgres/postgres_test.go +++ b/pkg/deploy/postgres/postgres_test.go @@ -12,7 +12,6 @@ package postgres import ( - "context" "fmt" "os" @@ -27,8 +26,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -57,6 +54,7 @@ func TestDeploymentSpec(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, }, }, @@ -70,6 +68,7 @@ func TestDeploymentSpec(t *testing.T) { cheCluster: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, Spec: orgv1.CheClusterSpec{ Database: orgv1.CheClusterSpecDB{ @@ -92,25 +91,12 @@ func TestDeploymentSpec(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - deployContext := &deploy.DeployContext{ - CheCluster: testCase.cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - - postgres := NewPostgres(deployContext) - deployment, err := postgres.GetDeploymentSpec(nil) - if err != nil { - t.Fatalf("Error creating deployment: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + postgres := NewPostgresReconciler() + deployment, err := postgres.getDeploymentSpec(nil, ctx) + assert.Nil(t, err) util.CompareResources(deployment, util.TestExpectedResources{ MemoryLimit: testCase.memoryLimit, @@ -125,47 +111,18 @@ func TestDeploymentSpec(t *testing.T) { } } -func TestSyncAllToCluster(t *testing.T) { - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme) - deployContext := &deploy.DeployContext{ - CheCluster: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - - postgres := NewPostgres(deployContext) - done, err := postgres.SyncAll() - if !done || err != nil { - t.Fatalf("Failed to sync PostgreSQL: %v", err) - } +func TestPostgresReconcile(t *testing.T) { + util.IsOpenShift = true + ctx := deploy.GetTestDeployContext(nil, []runtime.Object{}) - service := &corev1.Service{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: "eclipse-che"}, service) - if err != nil { - t.Fatalf("Failed to get service: %v", err) - } + postgres := NewPostgresReconciler() + _, done, err := postgres.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) - pvc := &corev1.PersistentVolumeClaim{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: "eclipse-che"}, pvc) - if err != nil { - t.Fatalf("Failed to get pvc: %v", err) - } - - deployment := &appsv1.Deployment{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: "eclipse-che"}, deployment) - if err != nil { - t.Fatalf("Failed to get deployment: %v", err) - } + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "postgres", Namespace: "eclipse-che"}, &corev1.Service{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "postgres-data", Namespace: "eclipse-che"}, &corev1.PersistentVolumeClaim{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "postgres", Namespace: "eclipse-che"}, &appsv1.Deployment{})) } func TestGetPostgresImage(t *testing.T) { diff --git a/pkg/deploy/server/server.go b/pkg/deploy/server/server.go deleted file mode 100644 index 604f59462e..0000000000 --- a/pkg/deploy/server/server.go +++ /dev/null @@ -1,334 +0,0 @@ -// -// Copyright (c) 2019-2021 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 server - -import ( - "context" - - orgv1 "github.com/eclipse-che/che-operator/api/v1" - "github.com/eclipse-che/che-operator/pkg/deploy" - "github.com/eclipse-che/che-operator/pkg/deploy/gateway" - "github.com/eclipse-che/che-operator/pkg/util" - routev1 "github.com/openshift/api/route/v1" - "github.com/sirupsen/logrus" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - networking "k8s.io/api/networking/v1" -) - -const ( - AvailableStatus = "Available" - UnavailableStatus = "Unavailable" - RollingUpdateInProgressStatus = "Available: Rolling update in progress" -) - -type Server struct { - deployContext *deploy.DeployContext - component string -} - -func NewServer(deployContext *deploy.DeployContext) *Server { - return &Server{ - deployContext: deployContext, - component: deploy.DefaultCheFlavor(deployContext.CheCluster), - } -} - -func (s *Server) ExposeCheServiceAndEndpoint() (bool, error) { - done, err := s.DetectDefaultCheHost() - if !done { - return false, err - } - - done, err = s.SyncCheService() - if !done { - return false, err - } - - done, err = s.ExposeCheEndpoint() - if !done { - return false, err - } - - return true, nil -} - -func (s *Server) SyncAll() (bool, error) { - done, err := s.SyncLegacyConfigMap() - if !done { - return false, err - } - - done, err = s.SyncCheConfigMap() - if !done { - return false, err - } - - // ensure configmap is created - // the version of the object is used in the deployment - exists, err := deploy.GetNamespacedObject(s.deployContext, CheConfigMapName, &corev1.ConfigMap{}) - if !exists { - return false, err - } - - done, err = s.SyncDeployment() - if !done { - return false, err - } - - done, err = s.UpdateAvailabilityStatus() - if !done { - return false, err - } - - done, err = s.UpdateCheURL() - if !done { - return false, err - } - - done, err = s.UpdateCheVersion() - if !done { - return false, err - } - - return true, nil -} - -func (s *Server) SyncCheService() (bool, error) { - portName := []string{"http"} - portNumber := []int32{8080} - - if s.deployContext.CheCluster.Spec.Metrics.Enable { - portName = append(portName, "metrics") - portNumber = append(portNumber, deploy.DefaultCheMetricsPort) - } - - if s.deployContext.CheCluster.Spec.Server.CheDebug == "true" { - portName = append(portName, "debug") - portNumber = append(portNumber, deploy.DefaultCheDebugPort) - } - - spec := deploy.GetServiceSpec(s.deployContext, deploy.CheServiceName, portName, portNumber, s.component) - return deploy.Sync(s.deployContext, spec, deploy.ServiceDefaultDiffOpts) -} - -func (s Server) ExposeCheEndpoint() (bool, error) { - cheHost := "" - exposedServiceName := GetServerExposingServiceName(s.deployContext.CheCluster) - - if !util.IsOpenShift { - _, done, err := deploy.SyncIngressToCluster( - s.deployContext, - s.component, - s.deployContext.CheCluster.Spec.Server.CheHost, - "", - exposedServiceName, - 8080, - s.deployContext.CheCluster.Spec.Server.CheServerIngress, - s.component) - if !done { - return false, err - } - - ingress := &networking.Ingress{} - exists, err := deploy.GetNamespacedObject(s.deployContext, s.component, ingress) - if !exists { - return false, err - } - - cheHost = ingress.Spec.Rules[0].Host - } else { - customHost := s.deployContext.CheCluster.Spec.Server.CheHost - if s.deployContext.DefaultCheHost == customHost { - // let OpenShift set a hostname by itself since it requires a routes/custom-host permissions - customHost = "" - } - - done, err := deploy.SyncRouteToCluster( - s.deployContext, - s.component, - customHost, - "/", - exposedServiceName, - 8080, - s.deployContext.CheCluster.Spec.Server.CheServerRoute, - s.component) - if !done { - return false, err - } - - route := &routev1.Route{} - exists, err := deploy.GetNamespacedObject(s.deployContext, s.component, route) - if !exists { - return false, err - } - - if customHost == "" { - s.deployContext.DefaultCheHost = route.Spec.Host - } - cheHost = route.Spec.Host - } - - if s.deployContext.CheCluster.Spec.Server.CheHost != cheHost { - s.deployContext.CheCluster.Spec.Server.CheHost = cheHost - err := deploy.UpdateCheCRSpec(s.deployContext, "CheHost URL", cheHost) - return err == nil, err - } - - return true, nil -} - -func (s Server) UpdateCheURL() (bool, error) { - var cheUrl = util.GetCheURL(s.deployContext.CheCluster) - if s.deployContext.CheCluster.Status.CheURL != cheUrl { - s.deployContext.CheCluster.Status.CheURL = cheUrl - err := deploy.UpdateCheCRStatus(s.deployContext, s.component+" server URL", cheUrl) - return err == nil, err - } - - return true, nil -} - -func (s *Server) SyncCheConfigMap() (bool, error) { - data, err := s.getCheConfigMapData() - if err != nil { - return false, err - } - - return deploy.SyncConfigMapDataToCluster(s.deployContext, CheConfigMapName, data, s.component) -} - -func (s Server) SyncLegacyConfigMap() (bool, error) { - // Get custom ConfigMap - // if it exists, add the data into CustomCheProperties - customConfigMap := &corev1.ConfigMap{} - exists, err := deploy.GetNamespacedObject(s.deployContext, "custom", customConfigMap) - if err != nil { - return false, err - } else if exists { - logrus.Info("Found legacy custom ConfigMap. Adding those values to CheCluster.Spec.Server.CustomCheProperties") - - if s.deployContext.CheCluster.Spec.Server.CustomCheProperties == nil { - s.deployContext.CheCluster.Spec.Server.CustomCheProperties = make(map[string]string) - } - for k, v := range customConfigMap.Data { - s.deployContext.CheCluster.Spec.Server.CustomCheProperties[k] = v - } - - err := s.deployContext.ClusterAPI.Client.Update(context.TODO(), s.deployContext.CheCluster) - if err != nil { - return false, err - } - - return deploy.DeleteNamespacedObject(s.deployContext, "custom", &corev1.ConfigMap{}) - } - - return true, nil -} - -func (s *Server) UpdateAvailabilityStatus() (bool, error) { - cheDeployment := &appsv1.Deployment{} - exists, err := deploy.GetNamespacedObject(s.deployContext, s.component, cheDeployment) - if err != nil { - return false, err - } - - if exists { - if cheDeployment.Status.AvailableReplicas < 1 { - if s.deployContext.CheCluster.Status.CheClusterRunning != UnavailableStatus { - s.deployContext.CheCluster.Status.CheClusterRunning = UnavailableStatus - err := deploy.UpdateCheCRStatus(s.deployContext, "status: Che API", UnavailableStatus) - return err == nil, err - } - } else if cheDeployment.Status.Replicas != 1 { - if s.deployContext.CheCluster.Status.CheClusterRunning != RollingUpdateInProgressStatus { - s.deployContext.CheCluster.Status.CheClusterRunning = RollingUpdateInProgressStatus - err := deploy.UpdateCheCRStatus(s.deployContext, "status: Che API", RollingUpdateInProgressStatus) - return err == nil, err - } - } else { - if s.deployContext.CheCluster.Status.CheClusterRunning != AvailableStatus { - cheFlavor := deploy.DefaultCheFlavor(s.deployContext.CheCluster) - name := "Eclipse Che" - if cheFlavor == "codeready" { - name = "CodeReady Workspaces" - } - - logrus.Infof(name+" is now available at: %s", util.GetCheURL(s.deployContext.CheCluster)) - s.deployContext.CheCluster.Status.CheClusterRunning = AvailableStatus - err := deploy.UpdateCheCRStatus(s.deployContext, "status: Che API", AvailableStatus) - return err == nil, err - } - } - } else { - s.deployContext.CheCluster.Status.CheClusterRunning = UnavailableStatus - err := deploy.UpdateCheCRStatus(s.deployContext, "status: Che API", UnavailableStatus) - return err == nil, err - } - - return true, nil -} - -func (s *Server) SyncDeployment() (bool, error) { - spec, err := s.getDeploymentSpec() - if err != nil { - return false, err - } - - return deploy.SyncDeploymentSpecToCluster(s.deployContext, spec, deploy.DefaultDeploymentDiffOpts) -} - -func (s *Server) DetectDefaultCheHost() (bool, error) { - // only for OpenShift - if !util.IsOpenShift || s.deployContext.DefaultCheHost != "" { - return true, nil - } - - done, err := deploy.SyncRouteToCluster( - s.deployContext, - s.component, - "", - "/", - GetServerExposingServiceName(s.deployContext.CheCluster), - 8080, - s.deployContext.CheCluster.Spec.Server.CheServerRoute, - s.component) - if !done { - return false, err - } - - route := &routev1.Route{} - exists, err := deploy.GetNamespacedObject(s.deployContext, s.component, route) - if !exists { - return false, err - } - - s.deployContext.DefaultCheHost = route.Spec.Host - return true, nil -} - -func (s Server) UpdateCheVersion() (bool, error) { - cheVersion := deploy.DefaultCheVersion() - if s.deployContext.CheCluster.Status.CheVersion != cheVersion { - s.deployContext.CheCluster.Status.CheVersion = cheVersion - err := deploy.UpdateCheCRStatus(s.deployContext, "version", cheVersion) - return err == nil, err - } - return true, nil -} - -func GetServerExposingServiceName(cr *orgv1.CheCluster) string { - if util.GetServerExposureStrategy(cr) == "single-host" && deploy.GetSingleHostExposureType(cr) == deploy.GatewaySingleHostExposureType { - return gateway.GatewayServiceName - } - return deploy.CheServiceName -} diff --git a/pkg/deploy/server/server_configmap.go b/pkg/deploy/server/server_configmap.go index b595a98fb5..cc46e4dab8 100644 --- a/pkg/deploy/server/server_configmap.go +++ b/pkg/deploy/server/server_configmap.go @@ -91,14 +91,14 @@ type CheConfigMap struct { // GetCheConfigMapData gets env values from CR spec and returns a map with key:value // which is used in CheCluster ConfigMap to configure CheCluster master behavior -func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { - cheHost := s.deployContext.CheCluster.Spec.Server.CheHost - identityProviderURL := s.deployContext.CheCluster.Spec.Auth.IdentityProviderURL +func (s *ServerReconciler) getCheConfigMapData(ctx *deploy.DeployContext) (cheEnv map[string]string, err error) { + cheHost := ctx.CheCluster.Spec.Server.CheHost + identityProviderURL := ctx.CheCluster.Spec.Auth.IdentityProviderURL // Adds `/auth` for external identity providers. // If identity provide is deployed by operator then `/auth` is already added. - if !s.deployContext.CheCluster.IsNativeUserModeEnabled() && - s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider && + if !ctx.CheCluster.IsNativeUserModeEnabled() && + ctx.CheCluster.Spec.Auth.ExternalIdentityProvider && !strings.HasSuffix(identityProviderURL, "/auth") { if strings.HasSuffix(identityProviderURL, "/") { identityProviderURL = identityProviderURL + "auth" @@ -107,20 +107,20 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { } } - cheFlavor := deploy.DefaultCheFlavor(s.deployContext.CheCluster) + cheFlavor := deploy.DefaultCheFlavor(ctx.CheCluster) infra := "kubernetes" if util.IsOpenShift { infra = "openshift" } tls := "false" openShiftIdentityProviderId := "NULL" - if util.IsOpenShift && s.deployContext.CheCluster.IsOpenShiftOAuthEnabled() { + if util.IsOpenShift && ctx.CheCluster.IsOpenShiftOAuthEnabled() { openShiftIdentityProviderId = "openshift-v3" if util.IsOpenShift4 { openShiftIdentityProviderId = "openshift-v4" } } - tlsSupport := s.deployContext.CheCluster.Spec.Server.TlsSupport + tlsSupport := ctx.CheCluster.Spec.Server.TlsSupport protocol := "http" if tlsSupport { protocol = "https" @@ -128,85 +128,85 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { } proxyJavaOpts := "" - cheWorkspaceNoProxy := s.deployContext.Proxy.NoProxy - if s.deployContext.Proxy.HttpProxy != "" { - if s.deployContext.Proxy.NoProxy == "" { + cheWorkspaceNoProxy := ctx.Proxy.NoProxy + if ctx.Proxy.HttpProxy != "" { + if ctx.Proxy.NoProxy == "" { cheWorkspaceNoProxy = os.Getenv("KUBERNETES_SERVICE_HOST") } else { cheWorkspaceNoProxy = cheWorkspaceNoProxy + "," + os.Getenv("KUBERNETES_SERVICE_HOST") } - proxyJavaOpts, err = deploy.GenerateProxyJavaOpts(s.deployContext.Proxy, cheWorkspaceNoProxy) + proxyJavaOpts, err = deploy.GenerateProxyJavaOpts(ctx.Proxy, cheWorkspaceNoProxy) if err != nil { logrus.Errorf("Failed to generate java proxy options: %v", err) } } - ingressDomain := s.deployContext.CheCluster.Spec.K8s.IngressDomain - tlsSecretName := s.deployContext.CheCluster.Spec.K8s.TlsSecretName - securityContextFsGroup := util.GetValue(s.deployContext.CheCluster.Spec.K8s.SecurityContextFsGroup, deploy.DefaultSecurityContextFsGroup) - securityContextRunAsUser := util.GetValue(s.deployContext.CheCluster.Spec.K8s.SecurityContextRunAsUser, deploy.DefaultSecurityContextRunAsUser) - pvcStrategy := util.GetValue(s.deployContext.CheCluster.Spec.Storage.PvcStrategy, deploy.DefaultPvcStrategy) - pvcClaimSize := util.GetValue(s.deployContext.CheCluster.Spec.Storage.PvcClaimSize, deploy.DefaultPvcClaimSize) - workspacePvcStorageClassName := s.deployContext.CheCluster.Spec.Storage.WorkspacePVCStorageClassName + ingressDomain := ctx.CheCluster.Spec.K8s.IngressDomain + tlsSecretName := ctx.CheCluster.Spec.K8s.TlsSecretName + securityContextFsGroup := util.GetValue(ctx.CheCluster.Spec.K8s.SecurityContextFsGroup, deploy.DefaultSecurityContextFsGroup) + securityContextRunAsUser := util.GetValue(ctx.CheCluster.Spec.K8s.SecurityContextRunAsUser, deploy.DefaultSecurityContextRunAsUser) + pvcStrategy := util.GetValue(ctx.CheCluster.Spec.Storage.PvcStrategy, deploy.DefaultPvcStrategy) + pvcClaimSize := util.GetValue(ctx.CheCluster.Spec.Storage.PvcClaimSize, deploy.DefaultPvcClaimSize) + workspacePvcStorageClassName := ctx.CheCluster.Spec.Storage.WorkspacePVCStorageClassName - defaultPVCJobsImage := deploy.DefaultPvcJobsImage(s.deployContext.CheCluster) - pvcJobsImage := util.GetValue(s.deployContext.CheCluster.Spec.Storage.PvcJobsImage, defaultPVCJobsImage) + defaultPVCJobsImage := deploy.DefaultPvcJobsImage(ctx.CheCluster) + pvcJobsImage := util.GetValue(ctx.CheCluster.Spec.Storage.PvcJobsImage, defaultPVCJobsImage) preCreateSubPaths := "true" - if !s.deployContext.CheCluster.Spec.Storage.PreCreateSubPaths { + if !ctx.CheCluster.Spec.Storage.PreCreateSubPaths { preCreateSubPaths = "false" } - chePostgresHostName := util.GetValue(s.deployContext.CheCluster.Spec.Database.ChePostgresHostName, deploy.DefaultChePostgresHostName) - chePostgresPort := util.GetValue(s.deployContext.CheCluster.Spec.Database.ChePostgresPort, deploy.DefaultChePostgresPort) - chePostgresDb := util.GetValue(s.deployContext.CheCluster.Spec.Database.ChePostgresDb, deploy.DefaultChePostgresDb) - keycloakRealm := util.GetValue(s.deployContext.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor) - keycloakClientId := util.GetValue(s.deployContext.CheCluster.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") - ingressStrategy := util.GetServerExposureStrategy(s.deployContext.CheCluster) - ingressClass := util.GetValue(s.deployContext.CheCluster.Spec.K8s.IngressClass, deploy.DefaultIngressClass) + chePostgresHostName := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresHostName, deploy.DefaultChePostgresHostName) + chePostgresPort := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresPort, deploy.DefaultChePostgresPort) + chePostgresDb := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresDb, deploy.DefaultChePostgresDb) + keycloakRealm := util.GetValue(ctx.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor) + keycloakClientId := util.GetValue(ctx.CheCluster.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") + ingressStrategy := util.GetServerExposureStrategy(ctx.CheCluster) + ingressClass := util.GetValue(ctx.CheCluster.Spec.K8s.IngressClass, deploy.DefaultIngressClass) // grab first the devfile registry url which is deployed by operator - devfileRegistryURL := s.deployContext.CheCluster.Status.DevfileRegistryURL + devfileRegistryURL := ctx.CheCluster.Status.DevfileRegistryURL // `Spec.Server.DevfileRegistryUrl` is deprecated in favor of `Server.ExternalDevfileRegistries` - if s.deployContext.CheCluster.Spec.Server.DevfileRegistryUrl != "" { - devfileRegistryURL += " " + s.deployContext.CheCluster.Spec.Server.DevfileRegistryUrl + if ctx.CheCluster.Spec.Server.DevfileRegistryUrl != "" { + devfileRegistryURL += " " + ctx.CheCluster.Spec.Server.DevfileRegistryUrl } - for _, r := range s.deployContext.CheCluster.Spec.Server.ExternalDevfileRegistries { + for _, r := range ctx.CheCluster.Spec.Server.ExternalDevfileRegistries { if strings.Index(devfileRegistryURL, r.Url) == -1 { devfileRegistryURL += " " + r.Url } } devfileRegistryURL = strings.TrimSpace(devfileRegistryURL) - pluginRegistryURL := s.deployContext.CheCluster.Status.PluginRegistryURL - cheLogLevel := util.GetValue(s.deployContext.CheCluster.Spec.Server.CheLogLevel, deploy.DefaultCheLogLevel) - cheDebug := util.GetValue(s.deployContext.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) - cheMetrics := strconv.FormatBool(s.deployContext.CheCluster.Spec.Metrics.Enable) - cheLabels := util.MapToKeyValuePairs(deploy.GetLabels(s.deployContext.CheCluster, deploy.DefaultCheFlavor(s.deployContext.CheCluster))) - workspaceExposure := deploy.GetSingleHostExposureType(s.deployContext.CheCluster) - singleHostGatewayConfigMapLabels := labels.FormatLabels(util.GetMapValue(s.deployContext.CheCluster.Spec.Server.SingleHostGatewayConfigMapLabels, deploy.DefaultSingleHostGatewayConfigMapLabels)) - workspaceNamespaceDefault := util.GetWorkspaceNamespaceDefault(s.deployContext.CheCluster) + pluginRegistryURL := ctx.CheCluster.Status.PluginRegistryURL + cheLogLevel := util.GetValue(ctx.CheCluster.Spec.Server.CheLogLevel, deploy.DefaultCheLogLevel) + cheDebug := util.GetValue(ctx.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) + cheMetrics := strconv.FormatBool(ctx.CheCluster.Spec.Metrics.Enable) + cheLabels := util.MapToKeyValuePairs(deploy.GetLabels(ctx.CheCluster, deploy.DefaultCheFlavor(ctx.CheCluster))) + workspaceExposure := deploy.GetSingleHostExposureType(ctx.CheCluster) + singleHostGatewayConfigMapLabels := labels.FormatLabels(util.GetMapValue(ctx.CheCluster.Spec.Server.SingleHostGatewayConfigMapLabels, deploy.DefaultSingleHostGatewayConfigMapLabels)) + workspaceNamespaceDefault := util.GetWorkspaceNamespaceDefault(ctx.CheCluster) cheAPI := protocol + "://" + cheHost + "/api" var keycloakInternalURL, pluginRegistryInternalURL, devfileRegistryInternalURL, cheInternalAPI, webSocketInternalEndpoint string - if !s.deployContext.CheCluster.IsNativeUserModeEnabled() && - s.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() && - !s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider { - keycloakInternalURL = fmt.Sprintf("%s://%s.%s.svc:8080/auth", "http", deploy.IdentityProviderName, s.deployContext.CheCluster.Namespace) + if !ctx.CheCluster.IsNativeUserModeEnabled() && + ctx.CheCluster.IsInternalClusterSVCNamesEnabled() && + !ctx.CheCluster.Spec.Auth.ExternalIdentityProvider { + keycloakInternalURL = fmt.Sprintf("%s://%s.%s.svc:8080/auth", "http", deploy.IdentityProviderName, ctx.CheCluster.Namespace) } // If there is a devfile registry deployed by operator - if s.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() && !s.deployContext.CheCluster.Spec.Server.ExternalDevfileRegistry { - devfileRegistryInternalURL = fmt.Sprintf("http://%s.%s.svc:8080", deploy.DevfileRegistryName, s.deployContext.CheCluster.Namespace) + if ctx.CheCluster.IsInternalClusterSVCNamesEnabled() && !ctx.CheCluster.Spec.Server.ExternalDevfileRegistry { + devfileRegistryInternalURL = fmt.Sprintf("http://%s.%s.svc:8080", deploy.DevfileRegistryName, ctx.CheCluster.Namespace) } - if s.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() && !s.deployContext.CheCluster.Spec.Server.ExternalPluginRegistry { - pluginRegistryInternalURL = fmt.Sprintf("http://%s.%s.svc:8080/v3", deploy.PluginRegistryName, s.deployContext.CheCluster.Namespace) + if ctx.CheCluster.IsInternalClusterSVCNamesEnabled() && !ctx.CheCluster.Spec.Server.ExternalPluginRegistry { + pluginRegistryInternalURL = fmt.Sprintf("http://%s.%s.svc:8080/v3", deploy.PluginRegistryName, ctx.CheCluster.Namespace) } - if s.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() { - cheInternalAPI = fmt.Sprintf("http://%s.%s.svc:8080/api", deploy.CheServiceName, s.deployContext.CheCluster.Namespace) - webSocketInternalEndpoint = fmt.Sprintf("ws://%s.%s.svc:8080/api/websocket", deploy.CheServiceName, s.deployContext.CheCluster.Namespace) + if ctx.CheCluster.IsInternalClusterSVCNamesEnabled() { + cheInternalAPI = fmt.Sprintf("http://%s.%s.svc:8080/api", deploy.CheServiceName, ctx.CheCluster.Namespace) + webSocketInternalEndpoint = fmt.Sprintf("ws://%s.%s.svc:8080/api/websocket", deploy.CheServiceName, ctx.CheCluster.Namespace) } wsprotocol := "ws" @@ -217,9 +217,9 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { cheWorkspaceServiceAccount := "che-workspace" cheUserClusterRoleNames := "NULL" - if s.deployContext.CheCluster.IsNativeUserModeEnabled() { + if ctx.CheCluster.IsNativeUserModeEnabled() { cheWorkspaceServiceAccount = "NULL" - cheUserClusterRoleNames = fmt.Sprintf("%s-cheworkspaces-clusterrole, %s-cheworkspaces-devworkspace-clusterrole", s.deployContext.CheCluster.Namespace, s.deployContext.CheCluster.Namespace) + cheUserClusterRoleNames = fmt.Sprintf("%s-cheworkspaces-clusterrole, %s-cheworkspaces-devworkspace-clusterrole", ctx.CheCluster.Namespace, ctx.CheCluster.Namespace) } data := &CheConfigMap{ @@ -248,23 +248,23 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { WorkspaceJavaOpts: deploy.DefaultWorkspaceJavaOpts + " " + proxyJavaOpts, WorkspaceMavenOpts: deploy.DefaultWorkspaceJavaOpts + " " + proxyJavaOpts, WorkspaceProxyJavaOpts: proxyJavaOpts, - WorkspaceHttpProxy: s.deployContext.Proxy.HttpProxy, - WorkspaceHttpsProxy: s.deployContext.Proxy.HttpsProxy, + WorkspaceHttpProxy: ctx.Proxy.HttpProxy, + WorkspaceHttpsProxy: ctx.Proxy.HttpsProxy, WorkspaceNoProxy: cheWorkspaceNoProxy, PluginRegistryUrl: pluginRegistryURL, PluginRegistryInternalUrl: pluginRegistryInternalURL, DevfileRegistryUrl: devfileRegistryURL, DevfileRegistryInternalUrl: devfileRegistryInternalURL, - CheWorkspacePluginBrokerMetadataImage: deploy.DefaultCheWorkspacePluginBrokerMetadataImage(s.deployContext.CheCluster), - CheWorkspacePluginBrokerArtifactsImage: deploy.DefaultCheWorkspacePluginBrokerArtifactsImage(s.deployContext.CheCluster), - CheServerSecureExposerJwtProxyImage: deploy.DefaultCheServerSecureExposerJwtProxyImage(s.deployContext.CheCluster), + CheWorkspacePluginBrokerMetadataImage: deploy.DefaultCheWorkspacePluginBrokerMetadataImage(ctx.CheCluster), + CheWorkspacePluginBrokerArtifactsImage: deploy.DefaultCheWorkspacePluginBrokerArtifactsImage(ctx.CheCluster), + CheServerSecureExposerJwtProxyImage: deploy.DefaultCheServerSecureExposerJwtProxyImage(ctx.CheCluster), CheJGroupsKubernetesLabels: cheLabels, CheMetricsEnabled: cheMetrics, CheTrustedCABundlesConfigMap: deploytls.CheAllCACertsConfigMapName, ServerStrategy: ingressStrategy, WorkspaceExposure: workspaceExposure, SingleHostGatewayConfigMapLabels: singleHostGatewayConfigMapLabels, - CheDevWorkspacesEnabled: strconv.FormatBool(s.deployContext.CheCluster.Spec.DevWorkspace.Enable), + CheDevWorkspacesEnabled: strconv.FormatBool(ctx.CheCluster.Spec.DevWorkspace.Enable), } data.IdentityProviderUrl = identityProviderURL @@ -272,9 +272,9 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { data.KeycloakRealm = keycloakRealm data.KeycloakClientId = keycloakClientId data.DatabaseURL = "jdbc:postgresql://" + chePostgresHostName + ":" + chePostgresPort + "/" + chePostgresDb - if len(s.deployContext.CheCluster.Spec.Database.ChePostgresSecret) < 1 { - data.DbUserName = s.deployContext.CheCluster.Spec.Database.ChePostgresUser - data.DbPassword = s.deployContext.CheCluster.Spec.Database.ChePostgresPassword + if len(ctx.CheCluster.Spec.Database.ChePostgresSecret) < 1 { + data.DbUserName = ctx.CheCluster.Spec.Database.ChePostgresUser + data.DbPassword = ctx.CheCluster.Spec.Database.ChePostgresPassword } out, err := json.Marshal(data) @@ -295,26 +295,26 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { "CHE_INFRA_KUBERNETES_INGRESS_PATH__TRANSFORM": "%s(.*)", } - if s.deployContext.CheCluster.Spec.DevWorkspace.Enable { + if ctx.CheCluster.Spec.DevWorkspace.Enable { k8sCheEnv["CHE_INFRA_KUBERNETES_ENABLE__UNSUPPORTED__K8S"] = "true" } // Add TLS key and server certificate to properties since user workspaces is created in another // than Che server namespace, from where the Che TLS secret is not accessable - if s.deployContext.CheCluster.Spec.K8s.TlsSecretName != "" { + if ctx.CheCluster.Spec.K8s.TlsSecretName != "" { cheTLSSecret := &corev1.Secret{} - exists, err := deploy.GetNamespacedObject(s.deployContext, s.deployContext.CheCluster.Spec.K8s.TlsSecretName, cheTLSSecret) + exists, err := deploy.GetNamespacedObject(ctx, ctx.CheCluster.Spec.K8s.TlsSecretName, cheTLSSecret) if err != nil { return nil, err } if !exists { - return nil, fmt.Errorf("%s secret not found", s.deployContext.CheCluster.Spec.K8s.TlsSecretName) + return nil, fmt.Errorf("%s secret not found", ctx.CheCluster.Spec.K8s.TlsSecretName) } else { if _, exists := cheTLSSecret.Data["tls.key"]; !exists { - return nil, fmt.Errorf("%s secret has no 'tls.key' key in data", s.deployContext.CheCluster.Spec.K8s.TlsSecretName) + return nil, fmt.Errorf("%s secret has no 'tls.key' key in data", ctx.CheCluster.Spec.K8s.TlsSecretName) } if _, exists := cheTLSSecret.Data["tls.crt"]; !exists { - return nil, fmt.Errorf("%s secret has no 'tls.crt' key in data", s.deployContext.CheCluster.Spec.K8s.TlsSecretName) + return nil, fmt.Errorf("%s secret has no 'tls.crt' key in data", ctx.CheCluster.Spec.K8s.TlsSecretName) } k8sCheEnv["CHE_INFRA_KUBERNETES_TLS__KEY"] = string(cheTLSSecret.Data["tls.key"]) k8sCheEnv["CHE_INFRA_KUBERNETES_TLS__CERT"] = string(cheTLSSecret.Data["tls.crt"]) @@ -324,9 +324,9 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) { addMap(cheEnv, k8sCheEnv) } - addMap(cheEnv, s.deployContext.CheCluster.Spec.Server.CustomCheProperties) + addMap(cheEnv, ctx.CheCluster.Spec.Server.CustomCheProperties) - err = setBitbucketEndpoints(s.deployContext, cheEnv) + err = setBitbucketEndpoints(ctx, cheEnv) if err != nil { return nil, err } diff --git a/pkg/deploy/server/server_configmap_test.go b/pkg/deploy/server/server_configmap_test.go index d5b3d61985..0ce56e678a 100644 --- a/pkg/deploy/server/server_configmap_test.go +++ b/pkg/deploy/server/server_configmap_test.go @@ -15,6 +15,7 @@ import ( "testing" "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -69,14 +70,11 @@ func TestNewCheConfigMap(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() - if err != nil { - t.Fatalf("Error creating ConfigMap data: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) + assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) }) } @@ -225,14 +223,11 @@ func TestConfigMap(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) - - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() - if err != nil { - t.Fatalf("Error creating ConfigMap data: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) + assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) }) } @@ -339,14 +334,11 @@ func TestUpdateBitBucketEndpoints(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) - - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() - if err != nil { - t.Fatalf("Error creating ConfigMap data: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) + assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) }) } @@ -550,14 +542,11 @@ func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() - if err != nil { - t.Fatalf("Error creating ConfigMap data: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) + assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) }) } @@ -686,14 +675,11 @@ func TestShouldSetUpCorrectlyInternalPluginRegistryServiceURL(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() - if err != nil { - t.Fatalf("Error creating ConfigMap data: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) + assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) }) } @@ -763,14 +749,11 @@ func TestShouldSetUpCorrectlyInternalCheServerURL(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() - if err != nil { - t.Fatalf("Error creating ConfigMap data: %v", err) - } + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) + assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) }) } @@ -914,10 +897,10 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { util.IsOpenShift = testCase.isOpenShift util.IsOpenShift4 = testCase.isOpenShift4 - deployContext := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) + ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - server := NewServer(deployContext) - actualData, err := server.getCheConfigMapData() + server := NewServerReconciler() + actualData, err := server.getCheConfigMapData(ctx) if err != nil { t.Fatalf("Error creating ConfigMap data: %v", err) } diff --git a/pkg/deploy/server/server_deployment.go b/pkg/deploy/server/server_deployment.go index 6183f6fccc..e802111c8f 100644 --- a/pkg/deploy/server/server_deployment.go +++ b/pkg/deploy/server/server_deployment.go @@ -17,7 +17,7 @@ import ( "strings" "github.com/eclipse-che/che-operator/pkg/deploy" - identity_provider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider" + identityprovider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider" "github.com/eclipse-che/che-operator/pkg/deploy/postgres" "github.com/eclipse-che/che-operator/pkg/deploy/tls" @@ -29,18 +29,18 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { - selfSignedCASecretExists, err := tls.IsSelfSignedCASecretExists(s.deployContext) +func (s ServerReconciler) getDeploymentSpec(ctx *deploy.DeployContext) (*appsv1.Deployment, error) { + selfSignedCASecretExists, err := tls.IsSelfSignedCASecretExists(ctx) if err != nil { return nil, err } - cmResourceVersions := GetCheConfigMapVersion(s.deployContext) - cmResourceVersions += "," + tls.GetAdditionalCACertsConfigMapVersion(s.deployContext) + cmResourceVersions := GetCheConfigMapVersion(ctx) + cmResourceVersions += "," + tls.GetAdditionalCACertsConfigMapVersion(ctx) terminationGracePeriodSeconds := int64(30) - cheFlavor := deploy.DefaultCheFlavor(s.deployContext.CheCluster) - labels, labelSelector := deploy.GetLabelsAndSelector(s.deployContext.CheCluster, cheFlavor) + cheFlavor := deploy.DefaultCheFlavor(ctx.CheCluster) + labels, labelSelector := deploy.GetLabelsAndSelector(ctx.CheCluster, cheFlavor) optionalEnv := true selfSignedCertEnv := corev1.EnvVar{ Name: "CHE_SELF__SIGNED__CERT", @@ -82,7 +82,7 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { }, } } - if s.deployContext.CheCluster.Spec.Server.GitSelfSignedCert { + if ctx.CheCluster.Spec.Server.GitSelfSignedCert { gitSelfSignedCertEnv = corev1.EnvVar{ Name: "CHE_GIT_SELF__SIGNED__CERT", ValueFrom: &corev1.EnvVarSource{ @@ -114,7 +114,7 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { cheEnv = append(cheEnv, gitSelfSignedCertEnv) cheEnv = append(cheEnv, gitSelfSignedCertHostEnv) - identityProviderSecret := s.deployContext.CheCluster.Spec.Auth.IdentityProviderSecret + identityProviderSecret := ctx.CheCluster.Spec.Auth.IdentityProviderSecret if len(identityProviderSecret) > 0 { cheEnv = append(cheEnv, corev1.EnvVar{ Name: "CHE_KEYCLOAK_ADMIN__PASSWORD", @@ -141,11 +141,11 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { } else { cheEnv = append(cheEnv, corev1.EnvVar{ Name: "CHE_KEYCLOAK_ADMIN__PASSWORD", - Value: s.deployContext.CheCluster.Spec.Auth.IdentityProviderPassword, + Value: ctx.CheCluster.Spec.Auth.IdentityProviderPassword, }, corev1.EnvVar{ Name: "CHE_KEYCLOAK_ADMIN__USERNAME", - Value: s.deployContext.CheCluster.Spec.Auth.IdentityProviderAdminUserName, + Value: ctx.CheCluster.Spec.Auth.IdentityProviderAdminUserName, }) } @@ -162,15 +162,15 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { FieldPath: "metadata.namespace"}}, }) - if s.deployContext.CheCluster.IsNativeUserModeEnabled() { + if ctx.CheCluster.IsNativeUserModeEnabled() { cheEnv = append(cheEnv, corev1.EnvVar{ Name: "CHE_AUTH_NATIVEUSER", Value: "true", }) } - cheImageAndTag := GetFullCheServerImageLink(s.deployContext.CheCluster) - pullPolicy := corev1.PullPolicy(util.GetValue(string(s.deployContext.CheCluster.Spec.Server.CheImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(cheImageAndTag))) + cheImageAndTag := GetFullCheServerImageLink(ctx.CheCluster) + pullPolicy := corev1.PullPolicy(util.GetValue(string(ctx.CheCluster.Spec.Server.CheImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(cheImageAndTag))) deployment := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ @@ -179,7 +179,7 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { }, ObjectMeta: metav1.ObjectMeta{ Name: cheFlavor, - Namespace: s.deployContext.CheCluster.Namespace, + Namespace: ctx.CheCluster.Namespace, Labels: labels, }, Spec: appsv1.DeploymentSpec{ @@ -222,18 +222,18 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - s.deployContext.CheCluster.Spec.Server.ServerMemoryRequest, + ctx.CheCluster.Spec.Server.ServerMemoryRequest, deploy.DefaultServerMemoryRequest), corev1.ResourceCPU: util.GetResourceQuantity( - s.deployContext.CheCluster.Spec.Server.ServerCpuRequest, + ctx.CheCluster.Spec.Server.ServerCpuRequest, deploy.DefaultServerCpuRequest), }, Limits: corev1.ResourceList{ corev1.ResourceMemory: util.GetResourceQuantity( - s.deployContext.CheCluster.Spec.Server.ServerMemoryLimit, + ctx.CheCluster.Spec.Server.ServerMemoryLimit, deploy.DefaultServerMemoryLimit), corev1.ResourceCPU: util.GetResourceQuantity( - s.deployContext.CheCluster.Spec.Server.ServerCpuLimit, + ctx.CheCluster.Spec.Server.ServerCpuLimit, deploy.DefaultServerCpuLimit), }, }, @@ -262,23 +262,23 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { }, } - err = MountBitBucketOAuthConfig(s.deployContext, deployment) + err = MountBitBucketOAuthConfig(ctx, deployment) if err != nil { return nil, err } - err = MountGitHubOAuthConfig(s.deployContext, deployment) + err = MountGitHubOAuthConfig(ctx, deployment) if err != nil { return nil, err } - err = MountGitLabOAuthConfig(s.deployContext, deployment) + err = MountGitLabOAuthConfig(ctx, deployment) if err != nil { return nil, err } container := &deployment.Spec.Template.Spec.Containers[0] - chePostgresSecret := s.deployContext.CheCluster.Spec.Database.ChePostgresSecret + chePostgresSecret := ctx.CheCluster.Spec.Database.ChePostgresSecret if len(chePostgresSecret) > 0 { container.Env = append(container.Env, corev1.EnvVar{ @@ -305,7 +305,7 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { } // configure probes if debug isn't set - cheDebug := util.GetValue(s.deployContext.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) + cheDebug := util.GetValue(ctx.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) if cheDebug != "true" { container.ReadinessProbe = &corev1.Probe{ Handler: corev1.Handler{ @@ -347,11 +347,11 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { } if !util.IsOpenShift { - runAsUser, err := strconv.ParseInt(util.GetValue(s.deployContext.CheCluster.Spec.K8s.SecurityContextRunAsUser, deploy.DefaultSecurityContextRunAsUser), 10, 64) + runAsUser, err := strconv.ParseInt(util.GetValue(ctx.CheCluster.Spec.K8s.SecurityContextRunAsUser, deploy.DefaultSecurityContextRunAsUser), 10, 64) if err != nil { return nil, err } - fsGroup, err := strconv.ParseInt(util.GetValue(s.deployContext.CheCluster.Spec.K8s.SecurityContextFsGroup, deploy.DefaultSecurityContextFsGroup), 10, 64) + fsGroup, err := strconv.ParseInt(util.GetValue(ctx.CheCluster.Spec.K8s.SecurityContextFsGroup, deploy.DefaultSecurityContextFsGroup), 10, 64) if err != nil { return nil, err } @@ -361,17 +361,17 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { } } - if deploy.IsComponentReadinessInitContainersConfigured(s.deployContext.CheCluster) { - if !s.deployContext.CheCluster.Spec.Database.ExternalDb { - waitForPostgresInitContainer, err := postgres.GetWaitForPostgresInitContainer(s.deployContext) + if deploy.IsComponentReadinessInitContainersConfigured(ctx.CheCluster) { + if !ctx.CheCluster.Spec.Database.ExternalDb { + waitForPostgresInitContainer, err := postgres.GetWaitForPostgresInitContainer(ctx) if err != nil { return nil, err } deployment.Spec.Template.Spec.InitContainers = append(deployment.Spec.Template.Spec.InitContainers, *waitForPostgresInitContainer) } - if !s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider { - waitForKeycloakInitContainer, err := identity_provider.GetWaitForKeycloakInitContainer(s.deployContext) + if !ctx.CheCluster.Spec.Auth.ExternalIdentityProvider { + waitForKeycloakInitContainer, err := identityprovider.GetWaitForKeycloakInitContainer(ctx) if err != nil { return nil, err } diff --git a/pkg/deploy/server/server_deployment_test.go b/pkg/deploy/server/server_deployment_test.go index adfe9f8065..291bf5a500 100644 --- a/pkg/deploy/server/server_deployment_test.go +++ b/pkg/deploy/server/server_deployment_test.go @@ -86,7 +86,7 @@ func TestDeployment(t *testing.T) { testCase.initObjects = append(testCase.initObjects) cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - deployContext := &deploy.DeployContext{ + ctx := &deploy.DeployContext{ CheCluster: testCase.cheCluster, ClusterAPI: deploy.ClusterAPI{ Client: cli, @@ -94,12 +94,10 @@ func TestDeployment(t *testing.T) { }, } - server := NewServer(deployContext) - deployment, err := server.getDeploymentSpec() - if err != nil { - t.Fatalf("Error creating deployment: %v", err) - } + server := NewServerReconciler() + deployment, err := server.getDeploymentSpec(ctx) + assert.Nil(t, err) util.CompareResources(deployment, util.TestExpectedResources{ MemoryLimit: testCase.memoryLimit, @@ -181,10 +179,10 @@ func TestMountBitBucketOAuthEnvVar(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - deployContext := deploy.GetTestDeployContext(nil, testCase.initObjects) + ctx := deploy.GetTestDeployContext(nil, testCase.initObjects) - server := NewServer(deployContext) - deployment, err := server.getDeploymentSpec() + server := NewServerReconciler() + deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err, "Unexpected error occurred %v", err) container := &deployment.Spec.Template.Spec.Containers[0] @@ -279,10 +277,10 @@ func TestMountGitHubOAuthEnvVar(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - deployContext := deploy.GetTestDeployContext(nil, testCase.initObjects) + ctx := deploy.GetTestDeployContext(nil, testCase.initObjects) - server := NewServer(deployContext) - deployment, err := server.getDeploymentSpec() + server := NewServerReconciler() + deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err, "Unexpected error %v", err) container := &deployment.Spec.Template.Spec.Containers[0] @@ -373,10 +371,10 @@ func TestMountGitLabOAuthEnvVar(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - deployContext := deploy.GetTestDeployContext(nil, testCase.initObjects) + ctx := deploy.GetTestDeployContext(nil, testCase.initObjects) - server := NewServer(deployContext) - deployment, err := server.getDeploymentSpec() + server := NewServerReconciler() + deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err, "Unexpected error %v", err) container := &deployment.Spec.Template.Spec.Containers[0] diff --git a/pkg/deploy/server/server_test.go b/pkg/deploy/server/server_test.go deleted file mode 100644 index b17e30d53c..0000000000 --- a/pkg/deploy/server/server_test.go +++ /dev/null @@ -1,285 +0,0 @@ -// -// Copyright (c) 2019-2021 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 server - -import ( - "context" - "os" - - "github.com/eclipse-che/che-operator/pkg/deploy" - "github.com/eclipse-che/che-operator/pkg/util" - routev1 "github.com/openshift/api/route/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - - orgv1 "github.com/eclipse-che/che-operator/api/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "testing" -) - -func TestSyncService(t *testing.T) { - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme) - deployContext := &deploy.DeployContext{ - CheCluster: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: os.Getenv("CHE_FLAVOR"), - }, - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{ - CheDebug: "true", - }, - Metrics: orgv1.CheClusterSpecMetrics{ - Enable: true, - }, - }, - }, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - - server := NewServer(deployContext) - done, err := server.SyncCheService() - if !done { - if err != nil { - t.Fatalf("Failed to sync service, error: %v", err) - } else { - t.Fatalf("Failed to sync service") - } - } - - service := &corev1.Service{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: deploy.CheServiceName, Namespace: "eclipse-che"}, service) - if err != nil { - t.Fatalf("Failed to get service, error: %v", err) - } - - checkPort(service.Spec.Ports[0], "http", 8080, t) - checkPort(service.Spec.Ports[1], "metrics", deploy.DefaultCheMetricsPort, t) - checkPort(service.Spec.Ports[2], "debug", deploy.DefaultCheDebugPort, t) -} - -func TestSyncAll(t *testing.T) { - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: os.Getenv("CHE_FLAVOR"), - }, - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{ - TlsSupport: true, - }, - }, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - - util.IsOpenShift = true - - server := NewServer(deployContext) - done, err := server.ExposeCheServiceAndEndpoint() - if !done || err != nil { - t.Fatalf("Failed to sync Server: %v", err) - } - - done, err = server.SyncAll() - if !done || err != nil { - t.Fatalf("Failed to sync Server: %v", err) - } - - // check service - service := &corev1.Service{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: deploy.CheServiceName, Namespace: "eclipse-che"}, service) - if err != nil { - t.Fatalf("Service not found: %v", err) - } - - // check endpoint - route := &routev1.Route{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: server.component, Namespace: "eclipse-che"}, route) - if err != nil { - t.Fatalf("Route not found: %v", err) - } - - // check configmap - configMap := &corev1.ConfigMap{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: CheConfigMapName, Namespace: "eclipse-che"}, configMap) - if err != nil { - t.Fatalf("ConfigMap not found: %v", err) - } - - // check deployment - deployment := &appsv1.Deployment{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: server.component, Namespace: "eclipse-che"}, deployment) - if err != nil { - t.Fatalf("Deployment not found: %v", err) - } - - if cheCluster.Status.CheURL == "" { - t.Fatalf("CheURL is not set") - } - - if cheCluster.Status.CheClusterRunning == "" { - t.Fatalf("CheClusterRunning is not set") - } - - if cheCluster.Status.CheVersion == "" { - t.Fatalf("CheVersion is not set") - } -} - -func TestSyncLegacyConfigMap(t *testing.T) { - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: orgv1.CheClusterSpec{ - Server: orgv1.CheClusterSpecServer{ - TlsSupport: true, - }, - }, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - Proxy: &deploy.Proxy{}, - } - - legacyConfigMap := deploy.GetConfigMapSpec(deployContext, "custom", map[string]string{"a": "b"}, "test") - err := cli.Create(context.TODO(), legacyConfigMap) - if err != nil { - t.Fatalf("Failed to create config map: %v", err) - } - - server := NewServer(deployContext) - done, err := server.SyncLegacyConfigMap() - if !done || err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - err = cli.Get(context.TODO(), types.NamespacedName{Namespace: "eclipse-che", Name: "custom"}, &corev1.ConfigMap{}) - if err == nil { - t.Fatalf("Legacy configmap must be removed") - } - - if cheCluster.Spec.Server.CustomCheProperties["a"] != "b" { - t.Fatalf("CheCluster wasn't updated with legacy configmap data") - } -} - -func TestUpdateAvailabilityStatus(t *testing.T) { - cheDeployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: os.Getenv("CHE_FLAVOR"), - Namespace: "eclipse-che", - }, - Status: appsv1.DeploymentStatus{ - AvailableReplicas: 1, - Replicas: 1, - }, - } - cheCluster := &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: os.Getenv("CHE_FLAVOR"), - }, - Spec: orgv1.CheClusterSpec{}, - Status: orgv1.CheClusterStatus{}, - } - - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - corev1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, cheCluster) - deployContext := &deploy.DeployContext{ - CheCluster: cheCluster, - ClusterAPI: deploy.ClusterAPI{ - Client: cli, - NonCachingClient: cli, - Scheme: scheme.Scheme, - }, - } - - server := NewServer(deployContext) - _, err := server.UpdateAvailabilityStatus() - if err != nil { - t.Fatalf("Failed to update availability status: %v", err) - } - if cheCluster.Status.CheClusterRunning != UnavailableStatus { - t.Fatalf("Expected status: %s, actual: %s", UnavailableStatus, cheCluster.Status.CheClusterRunning) - } - - err = cli.Create(context.TODO(), cheDeployment) - if err != nil { - t.Fatalf("Deployment not found: %v", err) - } - _, err = server.UpdateAvailabilityStatus() - if err != nil { - t.Fatalf("Failed to update availability status: %v", err) - } - - if cheCluster.Status.CheClusterRunning != AvailableStatus { - t.Fatalf("Expected status: %s, actual: %s", AvailableStatus, cheCluster.Status.CheClusterRunning) - } - - cheDeployment.Status.Replicas = 2 - err = cli.Update(context.TODO(), cheDeployment) - if err != nil { - t.Fatalf("Failed to update deployment: %v", err) - } - - _, err = server.UpdateAvailabilityStatus() - if err != nil { - t.Fatalf("Failed to update availability status: %v", err) - } - if cheCluster.Status.CheClusterRunning != RollingUpdateInProgressStatus { - t.Fatalf("Expected status: %s, actual: %s", RollingUpdateInProgressStatus, cheCluster.Status.CheClusterRunning) - } -} - -func checkPort(actualPort corev1.ServicePort, expectedName string, expectedPort int32, t *testing.T) { - if actualPort.Name != expectedName || actualPort.Port != expectedPort { - t.Errorf("expected port name:`%s` port:`%d`, actual name:`%s` port:`%d`", - expectedName, expectedPort, actualPort.Name, actualPort.Port) - } -} diff --git a/pkg/deploy/test_util.go b/pkg/deploy/test_util.go index d5faff39af..d7cf1c4800 100644 --- a/pkg/deploy/test_util.go +++ b/pkg/deploy/test_util.go @@ -13,6 +13,7 @@ package deploy import ( orgv1 "github.com/eclipse-che/che-operator/api/v1" + console "github.com/openshift/api/console/v1" oauthv1 "github.com/openshift/api/oauth/v1" routev1 "github.com/openshift/api/route/v1" userv1 "github.com/openshift/api/user/v1" @@ -52,9 +53,11 @@ func GetTestDeployContext(cheCluster *orgv1.CheCluster, initObjs []runtime.Objec scheme.AddKnownTypes(operatorsv1alpha1.SchemeGroupVersion, &operatorsv1alpha1.Subscription{}) scheme.AddKnownTypes(oauthv1.SchemeGroupVersion, &oauthv1.OAuthClient{}) scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{}, &userv1.Identity{}) - scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.OAuth{}, &configv1.Proxy{}) + scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.OAuth{}, &configv1.Proxy{}, &configv1.Console{}) scheme.AddKnownTypes(routev1.GroupVersion, &routev1.Route{}) scheme.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Secret{}) + scheme.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Secret{}) + scheme.AddKnownTypes(console.SchemeGroupVersion, &console.ConsoleLink{}) initObjs = append(initObjs, cheCluster) cli := fake.NewFakeClientWithScheme(scheme, initObjs...) From 7aa29904d13712be48b7ebd61a72f5db490cabf0 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko <abazko@redhat.com> Date: Wed, 8 Dec 2021 12:26:33 +0200 Subject: [PATCH 2/6] chore: Refactoring Signed-off-by: Anatolii Bazko <abazko@redhat.com> --- pkg/deploy/consolelink/consolelink.go | 113 +++++ pkg/deploy/consolelink/consolelink_test.go | 65 +++ pkg/deploy/consolelink/init_test.go | 21 + .../identity_provider_reconciler.go | 470 ++++++++++++++++++ .../identity_provider_reconciler_test.go | 211 ++++++++ .../identity_provider_util.go | 57 +++ pkg/deploy/server/chehost_reconciler.go | 164 ++++++ pkg/deploy/server/chehost_reconciler_test.go | 107 ++++ pkg/deploy/server/default_reconciler.go | 274 ++++++++++ pkg/deploy/server/default_reconciler_test.go | 162 ++++++ pkg/deploy/server/server_reconciler.go | 192 +++++++ pkg/deploy/server/server_reconciler_test.go | 138 +++++ pkg/deploy/server/server_util.go | 30 ++ 13 files changed, 2004 insertions(+) create mode 100644 pkg/deploy/consolelink/consolelink.go create mode 100644 pkg/deploy/consolelink/consolelink_test.go create mode 100644 pkg/deploy/consolelink/init_test.go create mode 100644 pkg/deploy/identity-provider/identity_provider_reconciler.go create mode 100644 pkg/deploy/identity-provider/identity_provider_reconciler_test.go create mode 100644 pkg/deploy/identity-provider/identity_provider_util.go create mode 100644 pkg/deploy/server/chehost_reconciler.go create mode 100644 pkg/deploy/server/chehost_reconciler_test.go create mode 100644 pkg/deploy/server/default_reconciler.go create mode 100644 pkg/deploy/server/default_reconciler_test.go create mode 100644 pkg/deploy/server/server_reconciler.go create mode 100644 pkg/deploy/server/server_reconciler_test.go create mode 100644 pkg/deploy/server/server_util.go diff --git a/pkg/deploy/consolelink/consolelink.go b/pkg/deploy/consolelink/consolelink.go new file mode 100644 index 0000000000..4e0948da8a --- /dev/null +++ b/pkg/deploy/consolelink/consolelink.go @@ -0,0 +1,113 @@ +// +// Copyright (c) 2019-2021 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 consolelink + +import ( + "fmt" + "strings" + + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + consolev1 "github.com/openshift/api/console/v1" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + ConsoleLinkFinalizerName = "consolelink.finalizers.che.eclipse.org" + ConsoleLinksResourceName = "consolelinks" +) + +type ConsoleLinkReconciler struct { + deploy.Reconcilable +} + +func NewConsoleLinkReconciler() *ConsoleLinkReconciler { + return &ConsoleLinkReconciler{} +} + +func (c *ConsoleLinkReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + if !util.IsOpenShift4 || !util.HasK8SResourceObject(ctx.ClusterAPI.DiscoveryClient, ConsoleLinksResourceName) { + // console link is supported only on OpenShift >= 4.2 + logrus.Debug("Console link won't be created. Consolelinks is not supported by OpenShift cluster.") + return reconcile.Result{}, true, nil + } + + if !ctx.CheCluster.Spec.Server.TlsSupport { + // console link is supported only with https + logrus.Debug("Console link won't be created. HTTP protocol is not supported.") + return reconcile.Result{}, true, nil + } + + done, err := c.createConsoleLink(ctx) + if !done { + return reconcile.Result{Requeue: true}, false, err + } + + return reconcile.Result{}, true, nil +} + +func (c *ConsoleLinkReconciler) Finalize(ctx *deploy.DeployContext) error { + return deploy.DeleteObjectWithFinalizer(ctx, client.ObjectKey{Name: deploy.DefaultConsoleLinkName()}, &consolev1.ConsoleLink{}, ConsoleLinkFinalizerName) +} + +func (c *ConsoleLinkReconciler) createConsoleLink(ctx *deploy.DeployContext) (bool, error) { + consoleLinkSpec := c.getConsoleLinkSpec(ctx) + _, err := deploy.CreateIfNotExists(ctx, consoleLinkSpec) + if err != nil { + return false, err + } + + consoleLink := &consolev1.ConsoleLink{} + exists, err := deploy.Get(ctx, client.ObjectKey{Name: deploy.DefaultConsoleLinkName()}, consoleLink) + if !exists || err != nil { + return false, err + } + + // consolelink is for this specific instance of Eclipse Che + if strings.Index(consoleLink.Spec.Link.Href, ctx.CheCluster.Spec.Server.CheHost) != -1 { + err = deploy.AppendFinalizer(ctx, ConsoleLinkFinalizerName) + return err == nil, err + } + + return true, nil +} + +func (c *ConsoleLinkReconciler) getConsoleLinkSpec(ctx *deploy.DeployContext) *consolev1.ConsoleLink { + cheHost := ctx.CheCluster.Spec.Server.CheHost + consoleLink := &consolev1.ConsoleLink{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConsoleLink", + APIVersion: consolev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: deploy.DefaultConsoleLinkName(), + Annotations: map[string]string{ + deploy.CheEclipseOrgNamespace: ctx.CheCluster.Namespace, + }, + }, + Spec: consolev1.ConsoleLinkSpec{ + Link: consolev1.Link{ + Href: "https://" + cheHost, + Text: deploy.DefaultConsoleLinkDisplayName()}, + Location: consolev1.ApplicationMenu, + ApplicationMenu: &consolev1.ApplicationMenuSpec{ + Section: deploy.DefaultConsoleLinkSection(), + ImageURL: fmt.Sprintf("https://%s%s", cheHost, deploy.DefaultConsoleLinkImage()), + }, + }, + } + + return consoleLink +} diff --git a/pkg/deploy/consolelink/consolelink_test.go b/pkg/deploy/consolelink/consolelink_test.go new file mode 100644 index 0000000000..f56779bb40 --- /dev/null +++ b/pkg/deploy/consolelink/consolelink_test.go @@ -0,0 +1,65 @@ +// +// Copyright (c) 2019-2021 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 consolelink + +import ( + orgv1 "github.com/eclipse-che/che-operator/api/v1" + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + console "github.com/openshift/api/console/v1" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + fakeDiscovery "k8s.io/client-go/discovery/fake" + + "testing" +) + +func TestReconcileConsoleLink(t *testing.T) { + cheCluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + TlsSupport: true, + }, + }, + } + + util.IsOpenShift4 = true + ctx := deploy.GetTestDeployContext(cheCluster, []runtime.Object{}) + ctx.ClusterAPI.DiscoveryClient.(*fakeDiscovery.FakeDiscovery).Fake.Resources = []*metav1.APIResourceList{ + { + APIResources: []metav1.APIResource{ + {Name: ConsoleLinksResourceName}, + }, + }, + } + + consolelink := NewConsoleLinkReconciler() + _, done, err := consolelink.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: deploy.DefaultConsoleLinkName()}, &console.ConsoleLink{})) + assert.True(t, util.ContainsString(ctx.CheCluster.Finalizers, ConsoleLinkFinalizerName)) + + // Initialize DeletionTimestamp => checluster is being deleted + err = consolelink.Finalize(ctx) + assert.Nil(t, err) + + assert.False(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: deploy.DefaultConsoleLinkName()}, &console.ConsoleLink{})) + assert.False(t, util.ContainsString(ctx.CheCluster.Finalizers, ConsoleLinkFinalizerName)) +} diff --git a/pkg/deploy/consolelink/init_test.go b/pkg/deploy/consolelink/init_test.go new file mode 100644 index 0000000000..8390ba322a --- /dev/null +++ b/pkg/deploy/consolelink/init_test.go @@ -0,0 +1,21 @@ +// +// Copyright (c) 2019-2021 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 consolelink + +import "github.com/eclipse-che/che-operator/pkg/deploy" + +func init() { + err := deploy.InitTestDefaultsFromDeployment("../../../config/manager/manager.yaml") + if err != nil { + panic(err) + } +} diff --git a/pkg/deploy/identity-provider/identity_provider_reconciler.go b/pkg/deploy/identity-provider/identity_provider_reconciler.go new file mode 100644 index 0000000000..50012e6899 --- /dev/null +++ b/pkg/deploy/identity-provider/identity_provider_reconciler.go @@ -0,0 +1,470 @@ +// +// Copyright (c) 2019-2021 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 identityprovider + +import ( + "context" + "errors" + "strings" + + "github.com/eclipse-che/che-operator/pkg/deploy/gateway" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + orgv1 "github.com/eclipse-che/che-operator/api/v1" + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/deploy/expose" + "github.com/eclipse-che/che-operator/pkg/util" + "github.com/google/go-cmp/cmp/cmpopts" + oauth "github.com/openshift/api/oauth/v1" + "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" +) + +const ( + OAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org" +) + +var ( + oAuthClientDiffOpts = cmpopts.IgnoreFields(oauth.OAuthClient{}, "TypeMeta", "ObjectMeta") + syncItems = []func(*deploy.DeployContext) (bool, error){ + syncService, + syncExposure, + SyncKeycloakDeploymentToCluster, + syncKeycloakResources, + syncOpenShiftIdentityProvider, + SyncGitHubOAuth, + } + + keycloakUpdated = false + keycloakCheHost = "" +) + +type IdentityProviderReconciler struct { + deploy.Reconcilable +} + +func NewIdentityProviderReconciler() *IdentityProviderReconciler { + return &IdentityProviderReconciler{} +} + +func (ip *IdentityProviderReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + if ctx.CheCluster.Spec.Auth.ExternalIdentityProvider { + keycloakURL := ctx.CheCluster.Spec.Auth.IdentityProviderURL + if ctx.CheCluster.Status.KeycloakURL != keycloakURL { + ctx.CheCluster.Status.KeycloakURL = keycloakURL + if err := deploy.UpdateCheCRStatus(ctx, "status: Keycloak URL", keycloakURL); err != nil { + return reconcile.Result{}, false, err + } + } + + return reconcile.Result{}, true, nil + } + + if ctx.CheCluster.IsNativeUserModeEnabled() { + done, err := syncNativeIdentityProviderItems(ctx) + if !done { + return reconcile.Result{}, false, err + } + return reconcile.Result{}, true, nil + } + + for _, syncItem := range syncItems { + done, err := syncItem(ctx) + if !util.IsTestMode() { + if !done { + return reconcile.Result{}, false, err + } + } + } + + return reconcile.Result{}, true, nil +} + +func (ip *IdentityProviderReconciler) Finalize(ctx *deploy.DeployContext) error { + oAuthClientName := ctx.CheCluster.Spec.Auth.OAuthClientName + if oAuthClientName != "" { + return deploy.DeleteObjectWithFinalizer(ctx, types.NamespacedName{Name: oAuthClientName}, &oauth.OAuthClient{}, OAuthFinalizerName) + } + return nil +} + +func syncService(deployContext *deploy.DeployContext) (bool, error) { + return deploy.SyncServiceToCluster( + deployContext, + deploy.IdentityProviderName, + []string{"http"}, + []int32{8080}, + deploy.IdentityProviderName) +} + +func syncExposure(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + + protocol := (map[bool]string{ + true: "https", + false: "http"})[cr.Spec.Server.TlsSupport] + endpoint, done, err := expose.Expose( + deployContext, + deploy.IdentityProviderName, + cr.Spec.Auth.IdentityProviderRoute, + cr.Spec.Auth.IdentityProviderIngress, + createGatewayConfig(deployContext.CheCluster)) + if !done { + return false, err + } + + keycloakURL := protocol + "://" + endpoint + if cr.Spec.Auth.IdentityProviderURL != keycloakURL { + cr.Spec.Auth.IdentityProviderURL = keycloakURL + if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak URL", keycloakURL); err != nil { + return false, err + } + + cr.Status.KeycloakURL = keycloakURL + if err := deploy.UpdateCheCRStatus(deployContext, "Keycloak URL", keycloakURL); err != nil { + return false, err + } + } + + return true, nil +} + +func syncKeycloakResources(deployContext *deploy.DeployContext) (bool, error) { + if !util.IsTestMode() { + cr := deployContext.CheCluster + if !cr.Status.KeycloakProvisoned { + if err := ProvisionKeycloakResources(deployContext); err != nil { + return false, err + } + + for { + cr.Status.KeycloakProvisoned = true + if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with Keycloak", "true"); err != nil && + apierrors.IsConflict(err) { + + deploy.ReloadCheClusterCR(deployContext) + continue + } + break + } + } + + // Updates keycloak if chehost has been changed + if !keycloakUpdated || keycloakCheHost != deployContext.CheCluster.Spec.Server.CheHost { + if _, err := util.K8sclient.ExecIntoPod( + deployContext.CheCluster, + deploy.IdentityProviderName, + GetKeycloakUpdateCommand, + "Update redirect URI-s and webOrigins"); err != nil { + return false, err + } else { + keycloakUpdated = true + keycloakCheHost = deployContext.CheCluster.Spec.Server.CheHost + } + } + } + + return true, nil +} + +func syncOpenShiftIdentityProvider(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + if util.IsOpenShift && cr.IsOpenShiftOAuthEnabled() { + return SyncOpenShiftIdentityProviderItems(deployContext) + } + return true, nil +} + +func syncNativeIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + + if err := resolveOpenshiftOAuthClientName(deployContext); err != nil { + return false, err + } + if err := resolveOpenshiftOAuthClientSecret(deployContext); err != nil { + return false, err + } + + if util.IsOpenShift { + redirectURIs := []string{"https://" + cr.Spec.Server.CheHost + "/oauth/callback"} + oAuthClient := getOAuthClientSpec(cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret, redirectURIs) + done, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) + if !done { + return false, err + } + + err = deploy.AppendFinalizer(deployContext, OAuthFinalizerName) + if err != nil { + return false, err + } + } + + return true, nil +} + +func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + + if err := resolveOpenshiftOAuthClientName(deployContext); err != nil { + return false, err + } + if err := resolveOpenshiftOAuthClientSecret(deployContext); err != nil { + return false, err + } + + keycloakURL := cr.Spec.Auth.IdentityProviderURL + cheFlavor := deploy.DefaultCheFlavor(cr) + keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) + oAuthClient := getKeycloakOAuthClientSpec(cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret, keycloakURL, keycloakRealm, util.IsOpenShift4) + provisioned, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) + if !provisioned { + return false, err + } + + if !util.IsTestMode() { + if !cr.Status.OpenShiftoAuthProvisioned { + // note that this uses the instance.Spec.Auth.IdentityProviderRealm and instance.Spec.Auth.IdentityProviderClientId. + // because we're not doing much of a change detection on those fields, we can't react on them changing here. + _, err := util.K8sclient.ExecIntoPod( + cr, + deploy.IdentityProviderName, + func(cr *orgv1.CheCluster) (string, error) { + return GetOpenShiftIdentityProviderProvisionCommand(cr, cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret) + }, + "Create OpenShift identity provider") + if err != nil { + return false, err + } + + for { + cr.Status.OpenShiftoAuthProvisioned = true + if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "true"); err != nil && + apierrors.IsConflict(err) { + + deploy.ReloadCheClusterCR(deployContext) + continue + } + break + } + } + } + return true, nil +} + +func resolveOpenshiftOAuthClientName(deployContext *deploy.DeployContext) error { + cr := deployContext.CheCluster + oAuthClientName := cr.Spec.Auth.OAuthClientName + if len(oAuthClientName) < 1 { + oAuthClientName = cr.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6)) + cr.Spec.Auth.OAuthClientName = oAuthClientName + if err := deploy.UpdateCheCRSpec(deployContext, "oAuthClient name", oAuthClientName); err != nil { + return err + } + } + return nil +} + +func resolveOpenshiftOAuthClientSecret(deployContext *deploy.DeployContext) error { + cr := deployContext.CheCluster + oauthSecret := cr.Spec.Auth.OAuthSecret + if len(oauthSecret) < 1 { + oauthSecret = util.GeneratePasswd(12) + cr.Spec.Auth.OAuthSecret = oauthSecret + if err := deploy.UpdateCheCRSpec(deployContext, "oAuth secret name", oauthSecret); err != nil { + return err + } + } + return nil +} + +// SyncGitHubOAuth provisions GitHub OAuth if secret with annotation +// `che.eclipse.org/github-oauth-credentials=true` or `che.eclipse.org/oauth-scm-configuration=github` +// is mounted into a container +func SyncGitHubOAuth(deployContext *deploy.DeployContext) (bool, error) { + // get legacy secret + legacySecrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.IdentityProviderName + "-secret", + }, map[string]string{ + deploy.CheEclipseOrgGithubOAuthCredentials: "true", + }) + if err != nil { + return false, err + } + + secrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.OAuthScmConfiguration, + }, map[string]string{ + deploy.CheEclipseOrgOAuthScmServer: "github", + }) + + if err != nil { + return false, err + } else if len(secrets)+len(legacySecrets) > 1 { + return false, errors.New("More than 1 GitHub OAuth configuration secrets found") + } + + isGitHubOAuthCredentialsExists := len(secrets) == 1 || len(legacySecrets) == 1 + cr := deployContext.CheCluster + + if isGitHubOAuthCredentialsExists { + if !cr.Status.GitHubOAuthProvisioned { + if !util.IsTestMode() { + _, err := util.K8sclient.ExecIntoPod( + cr, + deploy.IdentityProviderName, + func(cr *orgv1.CheCluster) (string, error) { + return GetGitHubIdentityProviderCreateCommand(deployContext) + }, + "Create GitHub OAuth") + if err != nil { + return false, err + } + } + + cr.Status.GitHubOAuthProvisioned = true + if err := deploy.UpdateCheCRStatus(deployContext, "status: GitHub OAuth provisioned", "true"); err != nil { + return false, err + } + } + } else { + if cr.Status.GitHubOAuthProvisioned { + if !util.IsTestMode() { + _, err := util.K8sclient.ExecIntoPod( + cr, + deploy.IdentityProviderName, + func(cr *orgv1.CheCluster) (string, error) { + return GetIdentityProviderDeleteCommand(cr, "github") + }, + "Delete GitHub OAuth") + if err != nil { + return false, err + } + } + + cr.Status.GitHubOAuthProvisioned = false + if err := deploy.UpdateCheCRStatus(deployContext, "status: GitHub OAuth provisioned", "false"); err != nil { + return false, err + } + } + } + + return true, nil +} + +func deleteIdentityProvider(ctx *deploy.DeployContext) error { + if !ctx.CheCluster.IsOpenShiftOAuthEnabled() && ctx.CheCluster.Status.OpenShiftoAuthProvisioned == true { + keycloakDeployment := &appsv1.Deployment{} + if err := ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: deploy.IdentityProviderName, Namespace: ctx.CheCluster.Namespace}, keycloakDeployment); err != nil { + logrus.Errorf("Deployment %s not found: %s", keycloakDeployment.Name, err.Error()) + } + + providerName := "openshift-v3" + if util.IsOpenShift4 { + providerName = "openshift-v4" + } + + _, err := util.K8sclient.ExecIntoPod( + ctx.CheCluster, + keycloakDeployment.Name, + func(cr *orgv1.CheCluster) (string, error) { + return GetIdentityProviderDeleteCommand(ctx.CheCluster, providerName) + }, + "delete OpenShift identity provider") + if err == nil { + oAuthClient := &oauth.OAuthClient{} + oAuthClientName := ctx.CheCluster.Spec.Auth.OAuthClientName + err := deploy.DeleteObjectWithFinalizer(ctx, types.NamespacedName{Name: oAuthClientName}, &oauth.OAuthClient{}, OAuthFinalizerName) + if err != nil { + logrus.Errorf("Failed to delete %s %s: %s", oAuthClient.Kind, oAuthClient.Name, err.Error()) + } + + for { + ctx.CheCluster.Status.OpenShiftoAuthProvisioned = false + if err := deploy.UpdateCheCRStatus(ctx, "OpenShiftoAuthProvisioned", "false"); err != nil { + if apierrors.IsConflict(err) { + deploy.ReloadCheClusterCR(ctx) + continue + } + } + break + } + + for { + ctx.CheCluster.Spec.Auth.OAuthSecret = "" + ctx.CheCluster.Spec.Auth.OAuthClientName = "" + updateFields := map[string]string{ + "oAuthSecret": "", + "oAuthClientName": "", + } + + if err := deploy.UpdateCheCRSpecByFields(ctx, updateFields); err != nil { + if apierrors.IsConflict(err) { + deploy.ReloadCheClusterCR(ctx) + continue + } + } + break + } + + return nil + } + return err + } + + return nil +} + +// Delete OpenShift identity provider if OpenShift oAuth is false in spec +// but OpenShiftoAuthProvisioned is true in CR status, e.g. when oAuth has been turned on and then turned off +// deleted, err := identityprovider.ReconcileIdentityProvider(deployContext) +// if deleted { +// // ignore error +// deploy.DeleteFinalizer(deployContext, deploy.OAuthFinalizerName) +// for { +// checluster.Status.OpenShiftoAuthProvisioned = false +// if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "false"); err != nil && +// errors.IsConflict(err) { +// _ = deploy.ReloadCheClusterCR(deployContext) +// continue +// } +// break +// } +// for { +// checluster.Spec.Auth.OAuthSecret = "" +// checluster.Spec.Auth.OAuthClientName = "" +// if err := deploy.UpdateCheCRStatus(deployContext, "clean oAuth secret name and client name", ""); err != nil && +// errors.IsConflict(err) { +// _ = deploy.ReloadCheClusterCR(deployContext) +// continue +// } +// break +// } +// } + +func createGatewayConfig(cheCluster *orgv1.CheCluster) *gateway.TraefikConfig { + cfg := gateway.CreateCommonTraefikConfig( + deploy.IdentityProviderName, + "PathPrefix(`/auth`)", + 10, + "http://"+deploy.IdentityProviderName+":8080", + []string{}) + + if util.IsOpenShift && cheCluster.IsNativeUserModeEnabled() { + cfg.AddAuthHeaderRewrite(deploy.IdentityProviderName) + } + + return cfg +} diff --git a/pkg/deploy/identity-provider/identity_provider_reconciler_test.go b/pkg/deploy/identity-provider/identity_provider_reconciler_test.go new file mode 100644 index 0000000000..1106850e87 --- /dev/null +++ b/pkg/deploy/identity-provider/identity_provider_reconciler_test.go @@ -0,0 +1,211 @@ +// +// Copyright (c) 2019-2021 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 identityprovider + +import ( + "os" + "reflect" + + "github.com/eclipse-che/che-operator/pkg/deploy" + + "github.com/google/go-cmp/cmp" + + orgv1 "github.com/eclipse-che/che-operator/api/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "testing" +) + +func TestSyncGitHubOAuth(t *testing.T) { + type testCase struct { + name string + initCR *orgv1.CheCluster + expectedCR *orgv1.CheCluster + initObjects []runtime.Object + } + + testCases := []testCase{ + { + name: "Should provision GitHub OAuth with legacy secret", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "0", + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "1", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: true, + }, + }, + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-credentials", + Namespace: "eclipse-che", + Labels: map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: "keycloak-secret", + }, + Annotations: map[string]string{ + deploy.CheEclipseOrgGithubOAuthCredentials: "true", + }, + }, + Data: map[string][]byte{ + "key": []byte("key-data"), + }, + }, + }, + }, + { + name: "Should provision GitHub OAuth", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "0", + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "1", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: true, + }, + }, + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-oauth-config", + Namespace: "eclipse-che", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "che.eclipse.org", + "app.kubernetes.io/component": "oauth-scm-configuration", + }, + Annotations: map[string]string{ + "che.eclipse.org/oauth-scm-server": "github", + }, + }, + }, + }, + }, + { + name: "Should not provision GitHub OAuth", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "0", + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "0", + }, + }, + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-credentials", + Namespace: "eclipse-che", + Labels: map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: "keycloak-secret", + }, + }, + Data: map[string][]byte{ + "key": []byte("key-data"), + }, + }, + }, + }, + { + name: "Should delete GitHub OAuth", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "0", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: true, + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Namespace: "eclipse-che", + ResourceVersion: "1", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: false, + }, + }, + initObjects: []runtime.Object{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + testCase.initObjects = append(testCase.initObjects, testCase.initCR) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + + deployContext := &deploy.DeployContext{ + CheCluster: testCase.initCR, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + Scheme: scheme.Scheme, + }, + } + + _, err := SyncGitHubOAuth(deployContext) + if err != nil { + t.Fatalf("Error mounting secret: %v", err) + } + + if !reflect.DeepEqual(testCase.expectedCR, testCase.initCR) { + t.Errorf("Expected CR and CR returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedCR, testCase.initCR)) + } + }) + } +} diff --git a/pkg/deploy/identity-provider/identity_provider_util.go b/pkg/deploy/identity-provider/identity_provider_util.go new file mode 100644 index 0000000000..a3c09cf531 --- /dev/null +++ b/pkg/deploy/identity-provider/identity_provider_util.go @@ -0,0 +1,57 @@ +// +// Copyright (c) 2019-2021 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 identityprovider + +import ( + "strings" + + oauth "github.com/openshift/api/oauth/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func getKeycloakOAuthClientSpec(name string, oauthSecret string, keycloakURL string, keycloakRealm string, isOpenShift4 bool) *oauth.OAuthClient { + providerName := "openshift-v3" + if isOpenShift4 { + providerName = "openshift-v4" + } + + redirectURLSuffix := "/realms/" + keycloakRealm + "/broker/" + providerName + "/endpoint" + redirectURIs := []string{ + keycloakURL + redirectURLSuffix, + } + + keycloakURL = strings.NewReplacer("https://", "", "http://", "").Replace(keycloakURL) + if !strings.Contains(keycloakURL, "://") { + redirectURIs = []string{ + "http://" + keycloakURL + redirectURLSuffix, + "https://" + keycloakURL + redirectURLSuffix, + } + } + return getOAuthClientSpec(name, oauthSecret, redirectURIs) +} + +func getOAuthClientSpec(name string, oauthSecret string, redirectURIs []string) *oauth.OAuthClient { + return &oauth.OAuthClient{ + TypeMeta: metav1.TypeMeta{ + Kind: "OAuthClient", + APIVersion: oauth.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{"app": "che"}, + }, + + Secret: oauthSecret, + RedirectURIs: redirectURIs, + GrantMethod: oauth.GrantHandlerPrompt, + } +} diff --git a/pkg/deploy/server/chehost_reconciler.go b/pkg/deploy/server/chehost_reconciler.go new file mode 100644 index 0000000000..ac403695c2 --- /dev/null +++ b/pkg/deploy/server/chehost_reconciler.go @@ -0,0 +1,164 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + routev1 "github.com/openshift/api/route/v1" + networking "k8s.io/api/networking/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type CheHostReconciler struct { + deploy.Reconcilable +} + +func NewCheHostReconciler() *CheHostReconciler { + return &CheHostReconciler{} +} + +func (s *CheHostReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + done, err := s.detectDefaultCheHost(ctx) + if !done { + return reconcile.Result{}, false, err + } + + done, err = s.syncCheService(ctx) + if !done { + return reconcile.Result{}, false, err + } + + done, err = s.exposeCheEndpoint(ctx) + if !done { + return reconcile.Result{}, false, err + } + + return reconcile.Result{}, true, nil +} + +func (s *CheHostReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil +} + +func (s *CheHostReconciler) detectDefaultCheHost(ctx *deploy.DeployContext) (bool, error) { + // only for OpenShift + if !util.IsOpenShift || ctx.DefaultCheHost != "" { + return true, nil + } + + done, err := deploy.SyncRouteToCluster( + ctx, + getComponentName(ctx), + "", + "/", + getServerExposingServiceName(ctx.CheCluster), + 8080, + ctx.CheCluster.Spec.Server.CheServerRoute, + getComponentName(ctx)) + if !done { + return false, err + } + + route := &routev1.Route{} + exists, err := deploy.GetNamespacedObject(ctx, getComponentName(ctx), route) + if !exists { + return false, err + } + + ctx.DefaultCheHost = route.Spec.Host + return true, nil +} + +func (s *CheHostReconciler) syncCheService(ctx *deploy.DeployContext) (bool, error) { + portName := []string{"http"} + portNumber := []int32{8080} + + if ctx.CheCluster.Spec.Metrics.Enable { + portName = append(portName, "metrics") + portNumber = append(portNumber, deploy.DefaultCheMetricsPort) + } + + if ctx.CheCluster.Spec.Server.CheDebug == "true" { + portName = append(portName, "debug") + portNumber = append(portNumber, deploy.DefaultCheDebugPort) + } + + spec := deploy.GetServiceSpec(ctx, deploy.CheServiceName, portName, portNumber, getComponentName(ctx)) + return deploy.Sync(ctx, spec, deploy.ServiceDefaultDiffOpts) +} + +func (s CheHostReconciler) exposeCheEndpoint(ctx *deploy.DeployContext) (bool, error) { + cheHost := "" + exposedServiceName := getServerExposingServiceName(ctx.CheCluster) + + if !util.IsOpenShift { + _, done, err := deploy.SyncIngressToCluster( + ctx, + getComponentName(ctx), + ctx.CheCluster.Spec.Server.CheHost, + "", + exposedServiceName, + 8080, + ctx.CheCluster.Spec.Server.CheServerIngress, + getComponentName(ctx)) + if !done { + return false, err + } + + ingress := &networking.Ingress{} + exists, err := deploy.GetNamespacedObject(ctx, getComponentName(ctx), ingress) + if !exists { + return false, err + } + + cheHost = ingress.Spec.Rules[0].Host + } else { + customHost := ctx.CheCluster.Spec.Server.CheHost + if ctx.DefaultCheHost == customHost { + // let OpenShift set a hostname by itself since it requires a routes/custom-host permissions + customHost = "" + } + + done, err := deploy.SyncRouteToCluster( + ctx, + getComponentName(ctx), + customHost, + "/", + exposedServiceName, + 8080, + ctx.CheCluster.Spec.Server.CheServerRoute, + getComponentName(ctx)) + if !done { + return false, err + } + + route := &routev1.Route{} + exists, err := deploy.GetNamespacedObject(ctx, getComponentName(ctx), route) + if !exists { + return false, err + } + + if customHost == "" { + ctx.DefaultCheHost = route.Spec.Host + } + cheHost = route.Spec.Host + } + + if ctx.CheCluster.Spec.Server.CheHost != cheHost { + ctx.CheCluster.Spec.Server.CheHost = cheHost + err := deploy.UpdateCheCRSpec(ctx, "CheHost URL", cheHost) + return err == nil, err + } + + return true, nil +} diff --git a/pkg/deploy/server/chehost_reconciler_test.go b/pkg/deploy/server/chehost_reconciler_test.go new file mode 100644 index 0000000000..56eeb11ab3 --- /dev/null +++ b/pkg/deploy/server/chehost_reconciler_test.go @@ -0,0 +1,107 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + "context" + "os" + + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + routev1 "github.com/openshift/api/route/v1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + + orgv1 "github.com/eclipse-che/che-operator/api/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "testing" +) + +func TestSyncService(t *testing.T) { + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + corev1.SchemeBuilder.AddToScheme(scheme.Scheme) + cli := fake.NewFakeClientWithScheme(scheme.Scheme) + ctx := &deploy.DeployContext{ + CheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: os.Getenv("CHE_FLAVOR"), + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + CheDebug: "true", + }, + Metrics: orgv1.CheClusterSpecMetrics{ + Enable: true, + }, + }, + }, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + NonCachingClient: cli, + Scheme: scheme.Scheme, + }, + } + + server := NewCheHostReconciler() + done, err := server.syncCheService(ctx) + assert.True(t, done) + assert.Nil(t, err) + + service := &corev1.Service{} + done, err = deploy.Get(ctx, types.NamespacedName{Name: deploy.CheServiceName, Namespace: "eclipse-che"}, service) + assert.True(t, done) + assert.Nil(t, err) + + assert.Equal(t, service.Spec.Ports[0].Name, "http") + assert.Equal(t, service.Spec.Ports[0].Port, int32(8080)) + assert.Equal(t, service.Spec.Ports[1].Name, "metrics") + assert.Equal(t, service.Spec.Ports[1].Port, deploy.DefaultCheMetricsPort) + assert.Equal(t, service.Spec.Ports[2].Name, "debug") + assert.Equal(t, service.Spec.Ports[2].Port, deploy.DefaultCheDebugPort) +} + +func TestConfiguringLabelsForRoutes(t *testing.T) { + util.IsOpenShift = true + + cheCluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: os.Getenv("CHE_FLAVOR"), + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + CheServerRoute: orgv1.RouteCustomSettings{ + Labels: "route=one", + }, + }, + }, + Status: orgv1.CheClusterStatus{}, + } + + ctx := deploy.GetTestDeployContext(cheCluster, []runtime.Object{}) + + server := NewCheHostReconciler() + done, err := server.exposeCheEndpoint(ctx) + assert.True(t, done) + assert.Nil(t, err) + + route := &routev1.Route{} + err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: getComponentName(ctx), Namespace: "eclipse-che"}, route) + assert.Nil(t, err) + assert.Equal(t, route.ObjectMeta.Labels["route"], "one") +} diff --git a/pkg/deploy/server/default_reconciler.go b/pkg/deploy/server/default_reconciler.go new file mode 100644 index 0000000000..356a27cdc4 --- /dev/null +++ b/pkg/deploy/server/default_reconciler.go @@ -0,0 +1,274 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + "strconv" + + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type DefaultValuesReconciler struct { + deploy.Reconcilable +} + +func NewDefaultValuesReconciler() *DefaultValuesReconciler { + return &DefaultValuesReconciler{} +} + +func (p *DefaultValuesReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + cheFlavor := deploy.DefaultCheFlavor(ctx.CheCluster) + cheNamespace := ctx.CheCluster.Namespace + if len(ctx.CheCluster.Spec.Server.CheFlavor) < 1 { + ctx.CheCluster.Spec.Server.CheFlavor = cheFlavor + if err := deploy.UpdateCheCRSpec(ctx, "installation flavor", cheFlavor); err != nil { + return reconcile.Result{}, false, err + } + } + + if len(ctx.CheCluster.Spec.Database.ChePostgresSecret) < 1 { + if len(ctx.CheCluster.Spec.Database.ChePostgresUser) < 1 || len(ctx.CheCluster.Spec.Database.ChePostgresPassword) < 1 { + chePostgresSecret := deploy.DefaultChePostgresSecret() + _, err := deploy.SyncSecretToCluster(ctx, chePostgresSecret, cheNamespace, map[string][]byte{"user": []byte(deploy.DefaultChePostgresUser), "password": []byte(util.GeneratePasswd(12))}) + if err != nil { + return reconcile.Result{}, false, err + } + ctx.CheCluster.Spec.Database.ChePostgresSecret = chePostgresSecret + if err := deploy.UpdateCheCRSpec(ctx, "Postgres Secret", chePostgresSecret); err != nil { + return reconcile.Result{}, false, err + } + } else { + if len(ctx.CheCluster.Spec.Database.ChePostgresUser) < 1 { + ctx.CheCluster.Spec.Database.ChePostgresUser = deploy.DefaultChePostgresUser + if err := deploy.UpdateCheCRSpec(ctx, "Postgres User", ctx.CheCluster.Spec.Database.ChePostgresUser); err != nil { + return reconcile.Result{}, false, err + } + } + if len(ctx.CheCluster.Spec.Database.ChePostgresPassword) < 1 { + ctx.CheCluster.Spec.Database.ChePostgresPassword = util.GeneratePasswd(12) + if err := deploy.UpdateCheCRSpec(ctx, "auto-generated CheCluster DB password", "password-hidden"); err != nil { + return reconcile.Result{}, false, err + } + } + } + } + if len(ctx.CheCluster.Spec.Auth.IdentityProviderPostgresSecret) < 1 { + keycloakPostgresPassword := util.GeneratePasswd(12) + keycloakDeployment := &appsv1.Deployment{} + exists, err := deploy.GetNamespacedObject(ctx, deploy.IdentityProviderName, keycloakDeployment) + if err != nil { + logrus.Error(err) + } + if exists { + keycloakPostgresPassword = util.GetDeploymentEnv(keycloakDeployment, "DB_PASSWORD") + } + + if len(ctx.CheCluster.Spec.Auth.IdentityProviderPostgresPassword) < 1 { + identityPostgresSecret := deploy.DefaultCheIdentityPostgresSecret() + _, err := deploy.SyncSecretToCluster(ctx, identityPostgresSecret, cheNamespace, map[string][]byte{"password": []byte(keycloakPostgresPassword)}) + if err != nil { + return reconcile.Result{}, false, err + } + ctx.CheCluster.Spec.Auth.IdentityProviderPostgresSecret = identityPostgresSecret + if err := deploy.UpdateCheCRSpec(ctx, "Identity Provider Postgres Secret", identityPostgresSecret); err != nil { + return reconcile.Result{}, false, err + } + } + } + + if len(ctx.CheCluster.Spec.Auth.IdentityProviderSecret) < 1 { + keycloakAdminUserName := util.GetValue(ctx.CheCluster.Spec.Auth.IdentityProviderAdminUserName, "admin") + keycloakAdminPassword := util.GetValue(ctx.CheCluster.Spec.Auth.IdentityProviderPassword, util.GeneratePasswd(12)) + + keycloakDeployment := &appsv1.Deployment{} + exists, _ := deploy.GetNamespacedObject(ctx, deploy.IdentityProviderName, keycloakDeployment) + if exists { + keycloakAdminUserName = util.GetDeploymentEnv(keycloakDeployment, "SSO_ADMIN_USERNAME") + keycloakAdminPassword = util.GetDeploymentEnv(keycloakDeployment, "SSO_ADMIN_PASSWORD") + } + + if len(ctx.CheCluster.Spec.Auth.IdentityProviderAdminUserName) < 1 || len(ctx.CheCluster.Spec.Auth.IdentityProviderPassword) < 1 { + identityProviderSecret := deploy.DefaultCheIdentitySecret() + _, err := deploy.SyncSecretToCluster(ctx, identityProviderSecret, cheNamespace, map[string][]byte{"user": []byte(keycloakAdminUserName), "password": []byte(keycloakAdminPassword)}) + if err != nil { + return reconcile.Result{}, false, err + } + ctx.CheCluster.Spec.Auth.IdentityProviderSecret = identityProviderSecret + if err := deploy.UpdateCheCRSpec(ctx, "Identity Provider Secret", identityProviderSecret); err != nil { + return reconcile.Result{}, false, err + } + } else { + if len(ctx.CheCluster.Spec.Auth.IdentityProviderPassword) < 1 { + ctx.CheCluster.Spec.Auth.IdentityProviderPassword = keycloakAdminPassword + if err := deploy.UpdateCheCRSpec(ctx, "Keycloak admin password", "password hidden"); err != nil { + return reconcile.Result{}, false, err + } + } + if len(ctx.CheCluster.Spec.Auth.IdentityProviderAdminUserName) < 1 { + ctx.CheCluster.Spec.Auth.IdentityProviderAdminUserName = keycloakAdminUserName + if err := deploy.UpdateCheCRSpec(ctx, "Keycloak admin username", keycloakAdminUserName); err != nil { + return reconcile.Result{}, false, err + } + } + } + } + + chePostgresDb := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresDb, "dbche") + if len(ctx.CheCluster.Spec.Database.ChePostgresDb) < 1 { + ctx.CheCluster.Spec.Database.ChePostgresDb = chePostgresDb + if err := deploy.UpdateCheCRSpec(ctx, "Postgres DB", chePostgresDb); err != nil { + return reconcile.Result{}, false, err + } + } + chePostgresHostName := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresHostName, deploy.DefaultChePostgresHostName) + if len(ctx.CheCluster.Spec.Database.ChePostgresHostName) < 1 { + ctx.CheCluster.Spec.Database.ChePostgresHostName = chePostgresHostName + if err := deploy.UpdateCheCRSpec(ctx, "Postgres hostname", chePostgresHostName); err != nil { + return reconcile.Result{}, false, err + } + } + chePostgresPort := util.GetValue(ctx.CheCluster.Spec.Database.ChePostgresPort, deploy.DefaultChePostgresPort) + if len(ctx.CheCluster.Spec.Database.ChePostgresPort) < 1 { + ctx.CheCluster.Spec.Database.ChePostgresPort = chePostgresPort + if err := deploy.UpdateCheCRSpec(ctx, "Postgres port", chePostgresPort); err != nil { + return reconcile.Result{}, false, err + } + } + + if !ctx.CheCluster.IsNativeUserModeEnabled() { + keycloakRealm := util.GetValue(ctx.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor) + if len(ctx.CheCluster.Spec.Auth.IdentityProviderRealm) < 1 { + ctx.CheCluster.Spec.Auth.IdentityProviderRealm = keycloakRealm + if err := deploy.UpdateCheCRSpec(ctx, "Keycloak realm", keycloakRealm); err != nil { + return reconcile.Result{}, false, err + } + } + keycloakClientId := util.GetValue(ctx.CheCluster.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") + if len(ctx.CheCluster.Spec.Auth.IdentityProviderClientId) < 1 { + ctx.CheCluster.Spec.Auth.IdentityProviderClientId = keycloakClientId + + if err := deploy.UpdateCheCRSpec(ctx, "Keycloak client ID", keycloakClientId); err != nil { + return reconcile.Result{}, false, err + } + } + } + + cheLogLevel := util.GetValue(ctx.CheCluster.Spec.Server.CheLogLevel, deploy.DefaultCheLogLevel) + if len(ctx.CheCluster.Spec.Server.CheLogLevel) < 1 { + ctx.CheCluster.Spec.Server.CheLogLevel = cheLogLevel + if err := deploy.UpdateCheCRSpec(ctx, "log level", cheLogLevel); err != nil { + return reconcile.Result{}, false, err + } + } + cheDebug := util.GetValue(ctx.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) + if len(ctx.CheCluster.Spec.Server.CheDebug) < 1 { + ctx.CheCluster.Spec.Server.CheDebug = cheDebug + if err := deploy.UpdateCheCRSpec(ctx, "debug", cheDebug); err != nil { + return reconcile.Result{}, false, err + } + } + pvcStrategy := util.GetValue(ctx.CheCluster.Spec.Storage.PvcStrategy, deploy.DefaultPvcStrategy) + if len(ctx.CheCluster.Spec.Storage.PvcStrategy) < 1 { + ctx.CheCluster.Spec.Storage.PvcStrategy = pvcStrategy + if err := deploy.UpdateCheCRSpec(ctx, "pvc strategy", pvcStrategy); err != nil { + return reconcile.Result{}, false, err + } + } + pvcClaimSize := util.GetValue(ctx.CheCluster.Spec.Storage.PvcClaimSize, deploy.DefaultPvcClaimSize) + if len(ctx.CheCluster.Spec.Storage.PvcClaimSize) < 1 { + ctx.CheCluster.Spec.Storage.PvcClaimSize = pvcClaimSize + if err := deploy.UpdateCheCRSpec(ctx, "pvc claim size", pvcClaimSize); err != nil { + return reconcile.Result{}, false, err + } + } + + // This is only to correctly manage defaults during the transition + // from Upstream 7.0.0 GA to the next + // version that should fixed bug https://github.com/eclipse/che/issues/13714 + // Or for the transition from CRW 1.2 to 2.0 + + if ctx.CheCluster.Spec.Storage.PvcJobsImage == deploy.OldDefaultPvcJobsUpstreamImageToDetect || + (deploy.MigratingToCRW2_0(ctx.CheCluster) && ctx.CheCluster.Spec.Storage.PvcJobsImage != "") { + ctx.CheCluster.Spec.Storage.PvcJobsImage = "" + if err := deploy.UpdateCheCRSpec(ctx, "pvc jobs image", ctx.CheCluster.Spec.Storage.PvcJobsImage); err != nil { + return reconcile.Result{}, false, err + } + } + + if ctx.CheCluster.Spec.Database.PostgresImage == deploy.OldDefaultPostgresUpstreamImageToDetect || + (deploy.MigratingToCRW2_0(ctx.CheCluster) && ctx.CheCluster.Spec.Database.PostgresImage != "") { + ctx.CheCluster.Spec.Database.PostgresImage = "" + if err := deploy.UpdateCheCRSpec(ctx, "postgres image", ctx.CheCluster.Spec.Database.PostgresImage); err != nil { + return reconcile.Result{}, false, err + } + } + + if ctx.CheCluster.Spec.Auth.IdentityProviderImage == deploy.OldDefaultKeycloakUpstreamImageToDetect || + (deploy.MigratingToCRW2_0(ctx.CheCluster) && ctx.CheCluster.Spec.Auth.IdentityProviderImage != "") { + ctx.CheCluster.Spec.Auth.IdentityProviderImage = "" + if err := deploy.UpdateCheCRSpec(ctx, "keycloak image", ctx.CheCluster.Spec.Auth.IdentityProviderImage); err != nil { + return reconcile.Result{}, false, err + } + } + + if deploy.MigratingToCRW2_0(ctx.CheCluster) && + !ctx.CheCluster.Spec.Server.ExternalPluginRegistry && + ctx.CheCluster.Spec.Server.PluginRegistryUrl == deploy.OldCrwPluginRegistryUrl { + ctx.CheCluster.Spec.Server.PluginRegistryUrl = "" + if err := deploy.UpdateCheCRSpec(ctx, "plugin registry url", ctx.CheCluster.Spec.Server.PluginRegistryUrl); err != nil { + return reconcile.Result{}, false, err + } + } + + if deploy.MigratingToCRW2_0(ctx.CheCluster) && + ctx.CheCluster.Spec.Server.CheImage == deploy.OldDefaultCodeReadyServerImageRepo { + ctx.CheCluster.Spec.Server.CheImage = "" + if err := deploy.UpdateCheCRSpec(ctx, "che image repo", ctx.CheCluster.Spec.Server.CheImage); err != nil { + return reconcile.Result{}, false, err + } + } + + if deploy.MigratingToCRW2_0(ctx.CheCluster) && + ctx.CheCluster.Spec.Server.CheImageTag == deploy.OldDefaultCodeReadyServerImageTag { + ctx.CheCluster.Spec.Server.CheImageTag = "" + if err := deploy.UpdateCheCRSpec(ctx, "che image tag", ctx.CheCluster.Spec.Server.CheImageTag); err != nil { + return reconcile.Result{}, false, err + } + } + + if ctx.CheCluster.Spec.Server.ServerExposureStrategy == "" && ctx.CheCluster.Spec.K8s.IngressStrategy == "" { + strategy := util.GetServerExposureStrategy(ctx.CheCluster) + ctx.CheCluster.Spec.Server.ServerExposureStrategy = strategy + if err := deploy.UpdateCheCRSpec(ctx, "serverExposureStrategy", strategy); err != nil { + return reconcile.Result{}, false, err + } + } + + if ctx.CheCluster.Spec.DevWorkspace.Enable && ctx.CheCluster.Spec.Auth.NativeUserMode == nil { + newNativeUserModeValue := util.NewBoolPointer(true) + ctx.CheCluster.Spec.Auth.NativeUserMode = newNativeUserModeValue + if err := deploy.UpdateCheCRSpec(ctx, "nativeUserMode", strconv.FormatBool(*newNativeUserModeValue)); err != nil { + return reconcile.Result{}, false, err + } + } + + return reconcile.Result{}, true, nil +} + +func (p *DefaultValuesReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil +} diff --git a/pkg/deploy/server/default_reconciler_test.go b/pkg/deploy/server/default_reconciler_test.go new file mode 100644 index 0000000000..12235e5082 --- /dev/null +++ b/pkg/deploy/server/default_reconciler_test.go @@ -0,0 +1,162 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + "os" + + "github.com/eclipse-che/che-operator/pkg/deploy" + devworkspace "github.com/eclipse-che/che-operator/pkg/deploy/dev-workspace" + "github.com/eclipse-che/che-operator/pkg/util" + "github.com/stretchr/testify/assert" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + orgv1 "github.com/eclipse-che/che-operator/api/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" + + "testing" +) + +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) { + checluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eclipse-che", + Namespace: "eclipse-che", + }, + } + + checluster.Spec.DevWorkspace.Enable = testCase.devWorkspaceEnabled + ctx := deploy.GetTestDeployContext(checluster, []runtime.Object{}) + + defaults := NewDefaultValuesReconciler() + _, done, err := defaults.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + assert.Equal(t, testCase.expectedCr.Spec.Server.ServerExposureStrategy, ctx.CheCluster.Spec.Server.ServerExposureStrategy) + }) + } +} + +func TestNativeUserModeEnabled(t *testing.T) { + type testCase struct { + name string + initObjects []runtime.Object + isOpenshift bool + devworkspaceEnabled bool + initialNativeUserValue *bool + expectedNativeUserValue *bool + } + + testCases := []testCase{ + { + name: "che-operator should use nativeUserMode when devworkspaces on openshift and no initial value in CR for nativeUserMode", + isOpenshift: true, + devworkspaceEnabled: true, + initialNativeUserValue: nil, + expectedNativeUserValue: pointer.BoolPtr(true), + }, + { + name: "che-operator should use nativeUserMode value from initial CR", + isOpenshift: true, + devworkspaceEnabled: true, + initialNativeUserValue: pointer.BoolPtr(false), + expectedNativeUserValue: pointer.BoolPtr(false), + }, + { + name: "che-operator should use nativeUserMode value from initial CR", + isOpenshift: true, + devworkspaceEnabled: true, + initialNativeUserValue: pointer.BoolPtr(true), + expectedNativeUserValue: pointer.BoolPtr(true), + }, + { + name: "che-operator should use nativeUserMode when devworkspaces on kubernetes and no initial value in CR for nativeUserMode", + isOpenshift: false, + devworkspaceEnabled: true, + initialNativeUserValue: nil, + expectedNativeUserValue: pointer.BoolPtr(true), + }, + { + name: "che-operator not modify nativeUserMode when devworkspace not enabled", + isOpenshift: true, + devworkspaceEnabled: false, + initialNativeUserValue: nil, + expectedNativeUserValue: nil, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true))) + + checluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eclipse-che", + Namespace: "eclipse-che", + }, + } + + // reread templates (workaround after setting IsOpenShift value) + util.IsOpenShift = testCase.isOpenshift + devworkspace.DevWorkspaceTemplates = devworkspace.DevWorkspaceTemplatesPath() + devworkspace.DevWorkspaceIssuerFile = devworkspace.DevWorkspaceTemplates + "/devworkspace-controller-selfsigned-issuer.Issuer.yaml" + devworkspace.DevWorkspaceCertificateFile = devworkspace.DevWorkspaceTemplates + "/devworkspace-controller-serving-cert.Certificate.yaml" + + checluster.Spec.DevWorkspace.Enable = testCase.devworkspaceEnabled + checluster.Spec.Auth.NativeUserMode = testCase.initialNativeUserValue + ctx := deploy.GetTestDeployContext(checluster, []runtime.Object{}) + + defaults := NewDefaultValuesReconciler() + _, done, err := defaults.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + assert.Equal(t, testCase.expectedNativeUserValue, ctx.CheCluster.Spec.Auth.NativeUserMode) + }) + } +} diff --git a/pkg/deploy/server/server_reconciler.go b/pkg/deploy/server/server_reconciler.go new file mode 100644 index 0000000000..47e225128d --- /dev/null +++ b/pkg/deploy/server/server_reconciler.go @@ -0,0 +1,192 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + "context" + + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + AvailableStatus = "Available" + UnavailableStatus = "Unavailable" + RollingUpdateInProgressStatus = "Available: Rolling update in progress" +) + +type ServerReconciler struct { + deploy.Reconcilable +} + +func NewServerReconciler() *ServerReconciler { + return &ServerReconciler{} +} + +func (s *ServerReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { + done, err := s.syncLegacyConfigMap(ctx) + if !done { + return reconcile.Result{}, false, err + } + + done, err = s.syncCheConfigMap(ctx) + if !done { + return reconcile.Result{}, false, err + } + + // ensure configmap is created + // the version of the object is used in the deployment + exists, err := deploy.GetNamespacedObject(ctx, CheConfigMapName, &corev1.ConfigMap{}) + if !exists { + return reconcile.Result{}, false, err + } + + done, err = s.syncDeployment(ctx) + if !done { + return reconcile.Result{}, false, err + } + + done, err = s.updateAvailabilityStatus(ctx) + if !done { + return reconcile.Result{}, false, err + } + + done, err = s.updateCheURL(ctx) + if !done { + return reconcile.Result{}, false, err + } + + done, err = s.updateCheVersion(ctx) + if !done { + return reconcile.Result{}, false, err + } + + return reconcile.Result{}, true, nil +} + +func (s *ServerReconciler) Finalize(ctx *deploy.DeployContext) error { + return nil +} + +func (s ServerReconciler) updateCheURL(ctx *deploy.DeployContext) (bool, error) { + var cheUrl = util.GetCheURL(ctx.CheCluster) + if ctx.CheCluster.Status.CheURL != cheUrl { + ctx.CheCluster.Status.CheURL = cheUrl + err := deploy.UpdateCheCRStatus(ctx, getComponentName(ctx)+" server URL", cheUrl) + return err == nil, err + } + + return true, nil +} + +func (s *ServerReconciler) syncCheConfigMap(ctx *deploy.DeployContext) (bool, error) { + data, err := s.getCheConfigMapData(ctx) + if err != nil { + return false, err + } + + return deploy.SyncConfigMapDataToCluster(ctx, CheConfigMapName, data, getComponentName(ctx)) +} + +func (s ServerReconciler) syncLegacyConfigMap(ctx *deploy.DeployContext) (bool, error) { + // Get custom ConfigMap + // if it exists, add the data into CustomCheProperties + customConfigMap := &corev1.ConfigMap{} + exists, err := deploy.GetNamespacedObject(ctx, "custom", customConfigMap) + if err != nil { + return false, err + } else if exists { + logrus.Info("Found legacy custom ConfigMap. Adding those values to CheCluster.Spec.Server.CustomCheProperties") + + if ctx.CheCluster.Spec.Server.CustomCheProperties == nil { + ctx.CheCluster.Spec.Server.CustomCheProperties = make(map[string]string) + } + for k, v := range customConfigMap.Data { + ctx.CheCluster.Spec.Server.CustomCheProperties[k] = v + } + + err := ctx.ClusterAPI.Client.Update(context.TODO(), ctx.CheCluster) + if err != nil { + return false, err + } + + return deploy.DeleteNamespacedObject(ctx, "custom", &corev1.ConfigMap{}) + } + + return true, nil +} + +func (s *ServerReconciler) updateAvailabilityStatus(ctx *deploy.DeployContext) (bool, error) { + cheDeployment := &appsv1.Deployment{} + exists, err := deploy.GetNamespacedObject(ctx, getComponentName(ctx), cheDeployment) + if err != nil { + return false, err + } + + if exists { + if cheDeployment.Status.AvailableReplicas < 1 { + if ctx.CheCluster.Status.CheClusterRunning != UnavailableStatus { + ctx.CheCluster.Status.CheClusterRunning = UnavailableStatus + err := deploy.UpdateCheCRStatus(ctx, "status: Che API", UnavailableStatus) + return err == nil, err + } + } else if cheDeployment.Status.Replicas != 1 { + if ctx.CheCluster.Status.CheClusterRunning != RollingUpdateInProgressStatus { + ctx.CheCluster.Status.CheClusterRunning = RollingUpdateInProgressStatus + err := deploy.UpdateCheCRStatus(ctx, "status: Che API", RollingUpdateInProgressStatus) + return err == nil, err + } + } else { + if ctx.CheCluster.Status.CheClusterRunning != AvailableStatus { + cheFlavor := deploy.DefaultCheFlavor(ctx.CheCluster) + name := "Eclipse Che" + if cheFlavor == "codeready" { + name = "CodeReady Workspaces" + } + + logrus.Infof(name+" is now available at: %s", util.GetCheURL(ctx.CheCluster)) + ctx.CheCluster.Status.CheClusterRunning = AvailableStatus + err := deploy.UpdateCheCRStatus(ctx, "status: Che API", AvailableStatus) + return err == nil, err + } + } + } else { + ctx.CheCluster.Status.CheClusterRunning = UnavailableStatus + err := deploy.UpdateCheCRStatus(ctx, "status: Che API", UnavailableStatus) + return err == nil, err + } + + return true, nil +} + +func (s *ServerReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, error) { + spec, err := s.getDeploymentSpec(ctx) + if err != nil { + return false, err + } + + return deploy.SyncDeploymentSpecToCluster(ctx, spec, deploy.DefaultDeploymentDiffOpts) +} + +func (s ServerReconciler) updateCheVersion(ctx *deploy.DeployContext) (bool, error) { + cheVersion := deploy.DefaultCheVersion() + if ctx.CheCluster.Status.CheVersion != cheVersion { + ctx.CheCluster.Status.CheVersion = cheVersion + err := deploy.UpdateCheCRStatus(ctx, "version", cheVersion) + return err == nil, err + } + return true, nil +} diff --git a/pkg/deploy/server/server_reconciler_test.go b/pkg/deploy/server/server_reconciler_test.go new file mode 100644 index 0000000000..336ada4aac --- /dev/null +++ b/pkg/deploy/server/server_reconciler_test.go @@ -0,0 +1,138 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + "context" + "os" + + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/util" + routev1 "github.com/openshift/api/route/v1" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + + orgv1 "github.com/eclipse-che/che-operator/api/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + + "testing" +) + +func TestReconcile(t *testing.T) { + cheCluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: os.Getenv("CHE_FLAVOR"), + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + TlsSupport: true, + }, + }, + } + + util.IsOpenShift = true + ctx := deploy.GetTestDeployContext(cheCluster, []runtime.Object{}) + + chehost := NewCheHostReconciler() + done, err := chehost.exposeCheEndpoint(ctx) + assert.True(t, done) + assert.Nil(t, err) + + server := NewServerReconciler() + _, done, err = server.Reconcile(ctx) + assert.True(t, done) + assert.Nil(t, err) + + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: getComponentName(ctx), Namespace: "eclipse-che"}, &routev1.Route{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: CheConfigMapName, Namespace: "eclipse-che"}, &corev1.ConfigMap{})) + assert.True(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{})) + assert.NotEmpty(t, cheCluster.Status.CheURL) + assert.NotEmpty(t, cheCluster.Status.CheClusterRunning) + assert.NotEmpty(t, cheCluster.Status.CheVersion) +} + +func TestSyncLegacyConfigMap(t *testing.T) { + cheCluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + TlsSupport: true, + }, + }, + } + ctx := deploy.GetTestDeployContext(cheCluster, []runtime.Object{}) + + legacyConfigMap := deploy.GetConfigMapSpec(ctx, "custom", map[string]string{"a": "b"}, "test") + err := ctx.ClusterAPI.Client.Create(context.TODO(), legacyConfigMap) + assert.Nil(t, err) + + server := NewServerReconciler() + done, err := server.syncLegacyConfigMap(ctx) + assert.True(t, done) + assert.Nil(t, err) + + assert.False(t, util.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "custom", Namespace: "eclipse-che"}, &corev1.ConfigMap{})) + assert.Equal(t, cheCluster.Spec.Server.CustomCheProperties["a"], "b") +} + +func TestUpdateAvailabilityStatus(t *testing.T) { + cheDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: os.Getenv("CHE_FLAVOR"), + Namespace: "eclipse-che", + }, + Status: appsv1.DeploymentStatus{ + AvailableReplicas: 1, + Replicas: 1, + }, + } + cheCluster := &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: os.Getenv("CHE_FLAVOR"), + }, + Spec: orgv1.CheClusterSpec{}, + Status: orgv1.CheClusterStatus{}, + } + + ctx := deploy.GetTestDeployContext(cheCluster, []runtime.Object{}) + + server := NewServerReconciler() + done, err := server.updateAvailabilityStatus(ctx) + assert.True(t, done) + assert.Nil(t, err) + assert.Equal(t, cheCluster.Status.CheClusterRunning, UnavailableStatus) + + err = ctx.ClusterAPI.Client.Create(context.TODO(), cheDeployment) + assert.Nil(t, err) + + done, err = server.updateAvailabilityStatus(ctx) + assert.True(t, done) + assert.Nil(t, err) + assert.Equal(t, cheCluster.Status.CheClusterRunning, AvailableStatus) + + cheDeployment.Status.Replicas = 2 + err = ctx.ClusterAPI.Client.Update(context.TODO(), cheDeployment) + assert.Nil(t, err) + + done, err = server.updateAvailabilityStatus(ctx) + assert.True(t, done) + assert.Nil(t, err) + assert.Equal(t, cheCluster.Status.CheClusterRunning, RollingUpdateInProgressStatus) +} diff --git a/pkg/deploy/server/server_util.go b/pkg/deploy/server/server_util.go new file mode 100644 index 0000000000..c687b2fc6f --- /dev/null +++ b/pkg/deploy/server/server_util.go @@ -0,0 +1,30 @@ +// +// Copyright (c) 2019-2021 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 server + +import ( + orgv1 "github.com/eclipse-che/che-operator/api/v1" + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/deploy/gateway" + "github.com/eclipse-che/che-operator/pkg/util" +) + +func getComponentName(ctx *deploy.DeployContext) string { + return deploy.DefaultCheFlavor(ctx.CheCluster) +} + +func getServerExposingServiceName(cr *orgv1.CheCluster) string { + if util.GetServerExposureStrategy(cr) == "single-host" && deploy.GetSingleHostExposureType(cr) == deploy.GatewaySingleHostExposureType { + return gateway.GatewayServiceName + } + return deploy.CheServiceName +} From 8e98d1a963efcf51510057b5fffa75ad42be3300 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko <abazko@redhat.com> Date: Thu, 9 Dec 2021 10:41:11 +0200 Subject: [PATCH 3/6] Fixes Signed-off-by: Anatolii Bazko <abazko@redhat.com> --- controllers/che/checluster_controller.go | 13 ++++------ pkg/deploy/data_types.go | 1 - pkg/deploy/server/chehost_reconciler.go | 30 +++++++++++------------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/controllers/che/checluster_controller.go b/controllers/che/checluster_controller.go index 27fb97da51..3600c629f0 100644 --- a/controllers/che/checluster_controller.go +++ b/controllers/che/checluster_controller.go @@ -253,6 +253,7 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) if err != nil { if errors.IsNotFound(err) { + r.Log.Info("CheCluster Custom Resource not found.") // Request object not found, could have been deleted after reconcile request. // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. // Return and don't requeue @@ -321,12 +322,8 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } -func (r *CheClusterReconciler) GetCR(request ctrl.Request) (instance *orgv1.CheCluster, err error) { - instance = &orgv1.CheCluster{} - err = r.client.Get(context.TODO(), request.NamespacedName, instance) - if err != nil { - r.Log.Error(err, "Failed to get %s CR: %s", "Cluster name", instance.Name) - return nil, err - } - return instance, nil +func (r *CheClusterReconciler) GetCR(request ctrl.Request) (*orgv1.CheCluster, error) { + checluster := &orgv1.CheCluster{} + err := r.client.Get(context.TODO(), request.NamespacedName, checluster) + return checluster, err } diff --git a/pkg/deploy/data_types.go b/pkg/deploy/data_types.go index 7d8ace3150..f4ef11ea9b 100644 --- a/pkg/deploy/data_types.go +++ b/pkg/deploy/data_types.go @@ -29,7 +29,6 @@ type DeployContext struct { CheCluster *orgv1.CheCluster ClusterAPI ClusterAPI Proxy *Proxy - DefaultCheHost string IsSelfSignedCertificate bool } diff --git a/pkg/deploy/server/chehost_reconciler.go b/pkg/deploy/server/chehost_reconciler.go index ac403695c2..8fc78cc0dd 100644 --- a/pkg/deploy/server/chehost_reconciler.go +++ b/pkg/deploy/server/chehost_reconciler.go @@ -21,6 +21,7 @@ import ( type CheHostReconciler struct { deploy.Reconcilable + defaultCheHost string } func NewCheHostReconciler() *CheHostReconciler { @@ -28,12 +29,15 @@ func NewCheHostReconciler() *CheHostReconciler { } func (s *CheHostReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { - done, err := s.detectDefaultCheHost(ctx) - if !done { - return reconcile.Result{}, false, err + if util.IsOpenShift && s.defaultCheHost == "" { + done, defaultCheHost, err := s.getDefaultCheHost(ctx) + if !done { + return reconcile.Result{}, false, err + } + s.defaultCheHost = defaultCheHost } - done, err = s.syncCheService(ctx) + done, err := s.syncCheService(ctx) if !done { return reconcile.Result{}, false, err } @@ -50,12 +54,7 @@ func (s *CheHostReconciler) Finalize(ctx *deploy.DeployContext) error { return nil } -func (s *CheHostReconciler) detectDefaultCheHost(ctx *deploy.DeployContext) (bool, error) { - // only for OpenShift - if !util.IsOpenShift || ctx.DefaultCheHost != "" { - return true, nil - } - +func (s *CheHostReconciler) getDefaultCheHost(ctx *deploy.DeployContext) (bool, string, error) { done, err := deploy.SyncRouteToCluster( ctx, getComponentName(ctx), @@ -66,17 +65,16 @@ func (s *CheHostReconciler) detectDefaultCheHost(ctx *deploy.DeployContext) (boo ctx.CheCluster.Spec.Server.CheServerRoute, getComponentName(ctx)) if !done { - return false, err + return false, "", err } route := &routev1.Route{} exists, err := deploy.GetNamespacedObject(ctx, getComponentName(ctx), route) if !exists { - return false, err + return false, "", err } - ctx.DefaultCheHost = route.Spec.Host - return true, nil + return true, route.Spec.Host, nil } func (s *CheHostReconciler) syncCheService(ctx *deploy.DeployContext) (bool, error) { @@ -124,7 +122,7 @@ func (s CheHostReconciler) exposeCheEndpoint(ctx *deploy.DeployContext) (bool, e cheHost = ingress.Spec.Rules[0].Host } else { customHost := ctx.CheCluster.Spec.Server.CheHost - if ctx.DefaultCheHost == customHost { + if s.defaultCheHost == customHost { // let OpenShift set a hostname by itself since it requires a routes/custom-host permissions customHost = "" } @@ -149,7 +147,7 @@ func (s CheHostReconciler) exposeCheEndpoint(ctx *deploy.DeployContext) (bool, e } if customHost == "" { - ctx.DefaultCheHost = route.Spec.Host + s.defaultCheHost = route.Spec.Host } cheHost = route.Spec.Host } From c240bd7c5b8561c4fc4aa24f57cc43dd7bbcdfb4 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko <abazko@redhat.com> Date: Thu, 9 Dec 2021 10:44:53 +0200 Subject: [PATCH 4/6] Fixew Signed-off-by: Anatolii Bazko <abazko@redhat.com> --- controllers/che/checluster_controller.go | 2 +- .../identity_provider_reconciler.go | 30 ++----------------- ...nciler.go => default_values_reconciler.go} | 0 ...t.go => default_values_reconciler_test.go} | 0 pkg/deploy/server/server_configmap.go | 2 +- pkg/deploy/server/server_configmap_test.go | 14 ++++----- pkg/deploy/server/server_deployment.go | 2 +- pkg/deploy/server/server_deployment_test.go | 8 ++--- pkg/deploy/server/server_reconciler.go | 22 +++++++------- pkg/deploy/server/server_reconciler_test.go | 6 ++-- 10 files changed, 30 insertions(+), 56 deletions(-) rename pkg/deploy/server/{default_reconciler.go => default_values_reconciler.go} (100%) rename pkg/deploy/server/{default_reconciler_test.go => default_values_reconciler_test.go} (100%) diff --git a/controllers/che/checluster_controller.go b/controllers/che/checluster_controller.go index 3600c629f0..a5b27ee7f4 100644 --- a/controllers/che/checluster_controller.go +++ b/controllers/che/checluster_controller.go @@ -113,7 +113,7 @@ func NewReconciler( reconcileManager.RegisterReconciler(pluginregistry.NewPluginRegistryReconciler()) reconcileManager.RegisterReconciler(dashboard.NewDashboardReconciler()) reconcileManager.RegisterReconciler(gateway.NewGatewayReconciler()) - reconcileManager.RegisterReconciler(server.NewServerReconciler()) + reconcileManager.RegisterReconciler(server.NewCheServerReconciler()) reconcileManager.RegisterReconciler(consolelink.NewConsoleLinkReconciler()) return &CheClusterReconciler{ diff --git a/pkg/deploy/identity-provider/identity_provider_reconciler.go b/pkg/deploy/identity-provider/identity_provider_reconciler.go index 50012e6899..de20e57774 100644 --- a/pkg/deploy/identity-provider/identity_provider_reconciler.go +++ b/pkg/deploy/identity-provider/identity_provider_reconciler.go @@ -95,8 +95,9 @@ func (ip *IdentityProviderReconciler) Finalize(ctx *deploy.DeployContext) error oAuthClientName := ctx.CheCluster.Spec.Auth.OAuthClientName if oAuthClientName != "" { return deploy.DeleteObjectWithFinalizer(ctx, types.NamespacedName{Name: oAuthClientName}, &oauth.OAuthClient{}, OAuthFinalizerName) + } else { + return deploy.DeleteFinalizer(ctx, OAuthFinalizerName) } - return nil } func syncService(deployContext *deploy.DeployContext) (bool, error) { @@ -427,33 +428,6 @@ func deleteIdentityProvider(ctx *deploy.DeployContext) error { return nil } -// Delete OpenShift identity provider if OpenShift oAuth is false in spec -// but OpenShiftoAuthProvisioned is true in CR status, e.g. when oAuth has been turned on and then turned off -// deleted, err := identityprovider.ReconcileIdentityProvider(deployContext) -// if deleted { -// // ignore error -// deploy.DeleteFinalizer(deployContext, deploy.OAuthFinalizerName) -// for { -// checluster.Status.OpenShiftoAuthProvisioned = false -// if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "false"); err != nil && -// errors.IsConflict(err) { -// _ = deploy.ReloadCheClusterCR(deployContext) -// continue -// } -// break -// } -// for { -// checluster.Spec.Auth.OAuthSecret = "" -// checluster.Spec.Auth.OAuthClientName = "" -// if err := deploy.UpdateCheCRStatus(deployContext, "clean oAuth secret name and client name", ""); err != nil && -// errors.IsConflict(err) { -// _ = deploy.ReloadCheClusterCR(deployContext) -// continue -// } -// break -// } -// } - func createGatewayConfig(cheCluster *orgv1.CheCluster) *gateway.TraefikConfig { cfg := gateway.CreateCommonTraefikConfig( deploy.IdentityProviderName, diff --git a/pkg/deploy/server/default_reconciler.go b/pkg/deploy/server/default_values_reconciler.go similarity index 100% rename from pkg/deploy/server/default_reconciler.go rename to pkg/deploy/server/default_values_reconciler.go diff --git a/pkg/deploy/server/default_reconciler_test.go b/pkg/deploy/server/default_values_reconciler_test.go similarity index 100% rename from pkg/deploy/server/default_reconciler_test.go rename to pkg/deploy/server/default_values_reconciler_test.go diff --git a/pkg/deploy/server/server_configmap.go b/pkg/deploy/server/server_configmap.go index cc46e4dab8..f2eec6055c 100644 --- a/pkg/deploy/server/server_configmap.go +++ b/pkg/deploy/server/server_configmap.go @@ -91,7 +91,7 @@ type CheConfigMap struct { // GetCheConfigMapData gets env values from CR spec and returns a map with key:value // which is used in CheCluster ConfigMap to configure CheCluster master behavior -func (s *ServerReconciler) getCheConfigMapData(ctx *deploy.DeployContext) (cheEnv map[string]string, err error) { +func (s *CheServerReconciler) getCheConfigMapData(ctx *deploy.DeployContext) (cheEnv map[string]string, err error) { cheHost := ctx.CheCluster.Spec.Server.CheHost identityProviderURL := ctx.CheCluster.Spec.Auth.IdentityProviderURL diff --git a/pkg/deploy/server/server_configmap_test.go b/pkg/deploy/server/server_configmap_test.go index 0ce56e678a..038ca90a29 100644 --- a/pkg/deploy/server/server_configmap_test.go +++ b/pkg/deploy/server/server_configmap_test.go @@ -72,7 +72,7 @@ func TestNewCheConfigMap(t *testing.T) { util.IsOpenShift4 = testCase.isOpenShift4 ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) @@ -225,7 +225,7 @@ func TestConfigMap(t *testing.T) { util.IsOpenShift4 = testCase.isOpenShift4 ctx := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) @@ -336,7 +336,7 @@ func TestUpdateBitBucketEndpoints(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { ctx := deploy.GetTestDeployContext(testCase.cheCluster, testCase.initObjects) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) @@ -544,7 +544,7 @@ func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) { util.IsOpenShift4 = testCase.isOpenShift4 ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) @@ -677,7 +677,7 @@ func TestShouldSetUpCorrectlyInternalPluginRegistryServiceURL(t *testing.T) { util.IsOpenShift4 = testCase.isOpenShift4 ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) @@ -751,7 +751,7 @@ func TestShouldSetUpCorrectlyInternalCheServerURL(t *testing.T) { util.IsOpenShift4 = testCase.isOpenShift4 ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) assert.Nil(t, err) util.ValidateContainData(actualData, testCase.expectedData, t) @@ -899,7 +899,7 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) { util.IsOpenShift4 = testCase.isOpenShift4 ctx := deploy.GetTestDeployContext(testCase.cheCluster, []runtime.Object{}) - server := NewServerReconciler() + server := NewCheServerReconciler() actualData, err := server.getCheConfigMapData(ctx) if err != nil { t.Fatalf("Error creating ConfigMap data: %v", err) diff --git a/pkg/deploy/server/server_deployment.go b/pkg/deploy/server/server_deployment.go index e802111c8f..fb76fa8ec8 100644 --- a/pkg/deploy/server/server_deployment.go +++ b/pkg/deploy/server/server_deployment.go @@ -29,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -func (s ServerReconciler) getDeploymentSpec(ctx *deploy.DeployContext) (*appsv1.Deployment, error) { +func (s CheServerReconciler) getDeploymentSpec(ctx *deploy.DeployContext) (*appsv1.Deployment, error) { selfSignedCASecretExists, err := tls.IsSelfSignedCASecretExists(ctx) if err != nil { return nil, err diff --git a/pkg/deploy/server/server_deployment_test.go b/pkg/deploy/server/server_deployment_test.go index 291bf5a500..a3098c7219 100644 --- a/pkg/deploy/server/server_deployment_test.go +++ b/pkg/deploy/server/server_deployment_test.go @@ -94,7 +94,7 @@ func TestDeployment(t *testing.T) { }, } - server := NewServerReconciler() + server := NewCheServerReconciler() deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err) @@ -181,7 +181,7 @@ func TestMountBitBucketOAuthEnvVar(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { ctx := deploy.GetTestDeployContext(nil, testCase.initObjects) - server := NewServerReconciler() + server := NewCheServerReconciler() deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err, "Unexpected error occurred %v", err) @@ -279,7 +279,7 @@ func TestMountGitHubOAuthEnvVar(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { ctx := deploy.GetTestDeployContext(nil, testCase.initObjects) - server := NewServerReconciler() + server := NewCheServerReconciler() deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err, "Unexpected error %v", err) @@ -373,7 +373,7 @@ func TestMountGitLabOAuthEnvVar(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { ctx := deploy.GetTestDeployContext(nil, testCase.initObjects) - server := NewServerReconciler() + server := NewCheServerReconciler() deployment, err := server.getDeploymentSpec(ctx) assert.Nil(t, err, "Unexpected error %v", err) diff --git a/pkg/deploy/server/server_reconciler.go b/pkg/deploy/server/server_reconciler.go index 47e225128d..5d2f6e7a7c 100644 --- a/pkg/deploy/server/server_reconciler.go +++ b/pkg/deploy/server/server_reconciler.go @@ -28,15 +28,15 @@ const ( RollingUpdateInProgressStatus = "Available: Rolling update in progress" ) -type ServerReconciler struct { +type CheServerReconciler struct { deploy.Reconcilable } -func NewServerReconciler() *ServerReconciler { - return &ServerReconciler{} +func NewCheServerReconciler() *CheServerReconciler { + return &CheServerReconciler{} } -func (s *ServerReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { +func (s *CheServerReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) { done, err := s.syncLegacyConfigMap(ctx) if !done { return reconcile.Result{}, false, err @@ -77,11 +77,11 @@ func (s *ServerReconciler) Reconcile(ctx *deploy.DeployContext) (reconcile.Resul return reconcile.Result{}, true, nil } -func (s *ServerReconciler) Finalize(ctx *deploy.DeployContext) error { +func (s *CheServerReconciler) Finalize(ctx *deploy.DeployContext) error { return nil } -func (s ServerReconciler) updateCheURL(ctx *deploy.DeployContext) (bool, error) { +func (s CheServerReconciler) updateCheURL(ctx *deploy.DeployContext) (bool, error) { var cheUrl = util.GetCheURL(ctx.CheCluster) if ctx.CheCluster.Status.CheURL != cheUrl { ctx.CheCluster.Status.CheURL = cheUrl @@ -92,7 +92,7 @@ func (s ServerReconciler) updateCheURL(ctx *deploy.DeployContext) (bool, error) return true, nil } -func (s *ServerReconciler) syncCheConfigMap(ctx *deploy.DeployContext) (bool, error) { +func (s *CheServerReconciler) syncCheConfigMap(ctx *deploy.DeployContext) (bool, error) { data, err := s.getCheConfigMapData(ctx) if err != nil { return false, err @@ -101,7 +101,7 @@ func (s *ServerReconciler) syncCheConfigMap(ctx *deploy.DeployContext) (bool, er return deploy.SyncConfigMapDataToCluster(ctx, CheConfigMapName, data, getComponentName(ctx)) } -func (s ServerReconciler) syncLegacyConfigMap(ctx *deploy.DeployContext) (bool, error) { +func (s CheServerReconciler) syncLegacyConfigMap(ctx *deploy.DeployContext) (bool, error) { // Get custom ConfigMap // if it exists, add the data into CustomCheProperties customConfigMap := &corev1.ConfigMap{} @@ -129,7 +129,7 @@ func (s ServerReconciler) syncLegacyConfigMap(ctx *deploy.DeployContext) (bool, return true, nil } -func (s *ServerReconciler) updateAvailabilityStatus(ctx *deploy.DeployContext) (bool, error) { +func (s *CheServerReconciler) updateAvailabilityStatus(ctx *deploy.DeployContext) (bool, error) { cheDeployment := &appsv1.Deployment{} exists, err := deploy.GetNamespacedObject(ctx, getComponentName(ctx), cheDeployment) if err != nil { @@ -172,7 +172,7 @@ func (s *ServerReconciler) updateAvailabilityStatus(ctx *deploy.DeployContext) ( return true, nil } -func (s *ServerReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, error) { +func (s *CheServerReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, error) { spec, err := s.getDeploymentSpec(ctx) if err != nil { return false, err @@ -181,7 +181,7 @@ func (s *ServerReconciler) syncDeployment(ctx *deploy.DeployContext) (bool, erro return deploy.SyncDeploymentSpecToCluster(ctx, spec, deploy.DefaultDeploymentDiffOpts) } -func (s ServerReconciler) updateCheVersion(ctx *deploy.DeployContext) (bool, error) { +func (s CheServerReconciler) updateCheVersion(ctx *deploy.DeployContext) (bool, error) { cheVersion := deploy.DefaultCheVersion() if ctx.CheCluster.Status.CheVersion != cheVersion { ctx.CheCluster.Status.CheVersion = cheVersion diff --git a/pkg/deploy/server/server_reconciler_test.go b/pkg/deploy/server/server_reconciler_test.go index 336ada4aac..47fe47a020 100644 --- a/pkg/deploy/server/server_reconciler_test.go +++ b/pkg/deploy/server/server_reconciler_test.go @@ -51,7 +51,7 @@ func TestReconcile(t *testing.T) { assert.True(t, done) assert.Nil(t, err) - server := NewServerReconciler() + server := NewCheServerReconciler() _, done, err = server.Reconcile(ctx) assert.True(t, done) assert.Nil(t, err) @@ -82,7 +82,7 @@ func TestSyncLegacyConfigMap(t *testing.T) { err := ctx.ClusterAPI.Client.Create(context.TODO(), legacyConfigMap) assert.Nil(t, err) - server := NewServerReconciler() + server := NewCheServerReconciler() done, err := server.syncLegacyConfigMap(ctx) assert.True(t, done) assert.Nil(t, err) @@ -113,7 +113,7 @@ func TestUpdateAvailabilityStatus(t *testing.T) { ctx := deploy.GetTestDeployContext(cheCluster, []runtime.Object{}) - server := NewServerReconciler() + server := NewCheServerReconciler() done, err := server.updateAvailabilityStatus(ctx) assert.True(t, done) assert.Nil(t, err) From cdd139162565648de45a899fcc4eab53b7935743 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko <abazko@redhat.com> Date: Thu, 9 Dec 2021 11:18:48 +0200 Subject: [PATCH 5/6] Fixes Signed-off-by: Anatolii Bazko <abazko@redhat.com> --- pkg/deploy/reconcile_manager.go | 5 +---- pkg/deploy/sync.go | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/deploy/reconcile_manager.go b/pkg/deploy/reconcile_manager.go index 2215dbd06b..dd7c7f0889 100644 --- a/pkg/deploy/reconcile_manager.go +++ b/pkg/deploy/reconcile_manager.go @@ -12,9 +12,6 @@ package deploy import ( - "reflect" - "runtime" - "github.com/sirupsen/logrus" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -71,7 +68,7 @@ func (manager *ReconcileManager) FinalizeAll(ctx *DeployContext) { for _, reconciler := range manager.reconcilers { err := reconciler.Finalize(ctx) if err != nil { - reconcilerName := runtime.FuncForPC(reflect.ValueOf(reconciler).Pointer()).Name() + reconcilerName := GetObjectType(reconciler) logrus.Errorf("Finalization failed for reconciler: `%s`, cause: %v", reconcilerName, err) } } diff --git a/pkg/deploy/sync.go b/pkg/deploy/sync.go index c06ee15809..e4b4d711b7 100644 --- a/pkg/deploy/sync.go +++ b/pkg/deploy/sync.go @@ -337,9 +337,9 @@ func getClientForObject(objectNamespace string, deployContext *DeployContext) cl return deployContext.ClusterAPI.NonCachingClient } -func GetObjectType(objectMeta metav1.Object) string { - objType := reflect.TypeOf(objectMeta).String() - if reflect.TypeOf(objectMeta).Kind().String() == "ptr" { +func GetObjectType(obj interface{}) string { + objType := reflect.TypeOf(obj).String() + if reflect.TypeOf(obj).Kind().String() == "ptr" { objType = objType[1:] } From 9c7bec0445f436d2fa17a7386d856ba7918255ce Mon Sep 17 00:00:00 2001 From: Anatolii Bazko <abazko@redhat.com> Date: Thu, 9 Dec 2021 11:51:53 +0200 Subject: [PATCH 6/6] Fixes Signed-off-by: Anatolii Bazko <abazko@redhat.com> --- controllers/che/checluster_controller.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controllers/che/checluster_controller.go b/controllers/che/checluster_controller.go index a5b27ee7f4..c2dd5718ed 100644 --- a/controllers/che/checluster_controller.go +++ b/controllers/che/checluster_controller.go @@ -114,7 +114,10 @@ func NewReconciler( reconcileManager.RegisterReconciler(dashboard.NewDashboardReconciler()) reconcileManager.RegisterReconciler(gateway.NewGatewayReconciler()) reconcileManager.RegisterReconciler(server.NewCheServerReconciler()) - reconcileManager.RegisterReconciler(consolelink.NewConsoleLinkReconciler()) + + if util.IsOpenShift4 { + reconcileManager.RegisterReconciler(consolelink.NewConsoleLinkReconciler()) + } return &CheClusterReconciler{ Scheme: scheme,