// // Copyright (c) 2019-2023 Red Hat, Inc. // This program and the accompanying materials are made // available under the terms of the Eclipse Public License 2.0 // which is available at https://www.eclipse.org/legal/epl-2.0/ // // SPDX-License-Identifier: EPL-2.0 // // Contributors: // Red Hat, Inc. - initial API and implementation // package che import ( "context" editorsdefinitions "github.com/eclipse-che/che-operator/pkg/deploy/editors-definitions" "github.com/eclipse-che/che-operator/pkg/common/test" containerbuild "github.com/eclipse-che/che-operator/pkg/deploy/container-build" "github.com/devfile/devworkspace-operator/pkg/infrastructure" "github.com/eclipse-che/che-operator/pkg/common/chetypes" "github.com/eclipse-che/che-operator/pkg/common/utils" "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" devworkspaceconfig "github.com/eclipse-che/che-operator/pkg/deploy/dev-workspace-config" "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" "github.com/eclipse-che/che-operator/pkg/deploy/pluginregistry" "github.com/eclipse-che/che-operator/pkg/deploy/postgres" "github.com/eclipse-che/che-operator/pkg/deploy/rbac" "github.com/eclipse-che/che-operator/pkg/deploy/server" "github.com/eclipse-che/che-operator/pkg/deploy/tls" "github.com/go-logr/logr" routev1 "github.com/openshift/api/route/v1" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "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/source" chev2 "github.com/eclipse-che/che-operator/api/v2" networking "k8s.io/api/networking/v1" ) // CheClusterReconciler reconciles a CheCluster object type CheClusterReconciler struct { Log logr.Logger Scheme *k8sruntime.Scheme // This client, initialized using mgr.Client() above, is a split client // that reads objects from the cache and writes to the apiserver client client.Client // This client, is a simple client // that reads objects without using the cache, // to simply read objects thta we don't intend // to further watch nonCachedClient client.Client // A discovery client to check for the existence of certain APIs registered // in the API Server discoveryClient discovery.DiscoveryInterface reconcileManager *deploy.ReconcileManager // the namespace to which to limit the reconciliation. If empty, all namespaces are considered namespace string } // NewReconciler returns a new CheClusterReconciler func NewReconciler( k8sclient client.Client, noncachedClient client.Client, discoveryClient discovery.DiscoveryInterface, scheme *k8sruntime.Scheme, namespace string) *CheClusterReconciler { reconcileManager := deploy.NewReconcileManager() // order does matter if !test.IsTestMode() { reconcileManager.RegisterReconciler(migration.NewMigrator()) reconcileManager.RegisterReconciler(migration.NewCheClusterDefaultsCleaner()) reconcileManager.RegisterReconciler(NewCheClusterValidator()) } reconcileManager.RegisterReconciler(imagepuller.NewImagePuller()) reconcileManager.RegisterReconciler(tls.NewCertificatesReconciler()) reconcileManager.RegisterReconciler(tls.NewTlsSecretReconciler()) reconcileManager.RegisterReconciler(devworkspaceconfig.NewDevWorkspaceConfigReconciler()) reconcileManager.RegisterReconciler(rbac.NewGatewayPermissionsReconciler()) // 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()) if infrastructure.IsOpenShift() { reconcileManager.RegisterReconciler(identityprovider.NewIdentityProviderReconciler()) } reconcileManager.RegisterReconciler(devfileregistry.NewDevfileRegistryReconciler()) reconcileManager.RegisterReconciler(pluginregistry.NewPluginRegistryReconciler()) reconcileManager.RegisterReconciler(editorsdefinitions.NewEditorsDefinitionsReconciler()) reconcileManager.RegisterReconciler(dashboard.NewDashboardReconciler()) reconcileManager.RegisterReconciler(gateway.NewGatewayReconciler()) reconcileManager.RegisterReconciler(server.NewCheServerReconciler()) if infrastructure.IsOpenShift() { reconcileManager.RegisterReconciler(containerbuild.NewContainerBuildReconciler()) reconcileManager.RegisterReconciler(consolelink.NewConsoleLinkReconciler()) } return &CheClusterReconciler{ Scheme: scheme, Log: ctrl.Log.WithName("controllers").WithName("CheCluster"), client: k8sclient, nonCachedClient: noncachedClient, discoveryClient: discoveryClient, namespace: namespace, reconcileManager: reconcileManager, } } // SetupWithManager sets up the controller with the Manager. func (r *CheClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { onAllExceptGenericEventsPredicate := predicate.Funcs{ UpdateFunc: func(evt event.UpdateEvent) bool { return true }, CreateFunc: func(evt event.CreateEvent) bool { return true }, DeleteFunc: func(evt event.DeleteEvent) bool { return true }, GenericFunc: func(evt event.GenericEvent) bool { return false }, } var toTrustedBundleConfigMapRequestMapper handler.MapFunc = func(obj client.Object) []ctrl.Request { isTrusted, reconcileRequest := IsTrustedBundleConfigMap(r.client, r.namespace, obj) if isTrusted { return []ctrl.Request{reconcileRequest} } return []ctrl.Request{} } var toEclipseCheRelatedObjRequestMapper handler.MapFunc = func(obj client.Object) []ctrl.Request { isEclipseCheRelatedObj, reconcileRequest := IsEclipseCheRelatedObj(r.client, r.namespace, obj) if isEclipseCheRelatedObj { return []ctrl.Request{reconcileRequest} } return []ctrl.Request{} } controllerBuilder := ctrl.NewControllerManagedBy(mgr). // Watch for changes to primary resource CheCluster Watches(&source.Kind{Type: &chev2.CheCluster{}}, &handler.EnqueueRequestForObject{}). // Watch for changes to secondary resources and requeue the owner CheCluster Watches(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &rbacv1.Role{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }). Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(toTrustedBundleConfigMapRequestMapper), builder.WithPredicates(onAllExceptGenericEventsPredicate), ). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(toEclipseCheRelatedObjRequestMapper), builder.WithPredicates(onAllExceptGenericEventsPredicate), ). Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(toEclipseCheRelatedObjRequestMapper), builder.WithPredicates(onAllExceptGenericEventsPredicate), ) if infrastructure.IsOpenShift() { controllerBuilder = controllerBuilder.Watches(&source.Kind{Type: &routev1.Route{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }) } else { controllerBuilder = controllerBuilder.Watches(&source.Kind{Type: &networking.Ingress{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &chev2.CheCluster{}, }) } if r.namespace != "" { controllerBuilder = controllerBuilder.WithEventFilter(utils.InNamespaceEventFilter(r.namespace)) } return controllerBuilder. For(&chev2.CheCluster{}). Complete(r) } // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.5/pkg/reconcile func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("checluster", req.NamespacedName) clusterAPI := chetypes.ClusterAPI{ Client: r.client, NonCachingClient: r.nonCachedClient, DiscoveryClient: r.discoveryClient, Scheme: r.Scheme, } // Fetch the CheCluster instance checluster, err := deploy.FindCheClusterCRInNamespace(r.client, req.NamespacedName.Namespace) if checluster == nil { r.Log.Info("CheCluster Custom Resource not found.") return ctrl.Result{}, nil } else if err != nil { // Error reading the object - requeue the request. return ctrl.Result{}, err } deployContext := &chetypes.DeployContext{ ClusterAPI: clusterAPI, CheCluster: checluster, } // Read proxy configuration proxy, err := GetProxyConfiguration(deployContext) if err != nil { r.Log.Error(err, "Error on reading proxy configuration") return ctrl.Result{}, err } deployContext.Proxy = proxy // Detect whether self-signed certificate is used isSelfSignedCertificate, err := tls.IsSelfSignedCertificateUsed(deployContext) if err != nil { r.Log.Error(err, "Failed to detect if self-signed certificate used.") return ctrl.Result{}, err } deployContext.IsSelfSignedCertificate = isSelfSignedCertificate if deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() { result, done, err := r.reconcileManager.ReconcileAll(deployContext) if !done { return result, err } else { if err := deploy.SetStatusDetails(deployContext, "", ""); err != nil { return ctrl.Result{}, err } logrus.Info("Successfully reconciled.") return ctrl.Result{}, nil } } else { deployContext.CheCluster.Status.ChePhase = chev2.ClusterPhasePendingDeletion _ = deploy.UpdateCheCRStatus(deployContext, "ChePhase", chev2.ClusterPhasePendingDeletion) done := r.reconcileManager.FinalizeAll(deployContext) return ctrl.Result{Requeue: !done}, nil } }