che-operator/pkg/controller/che/che_controller.go

874 lines
35 KiB
Go

//
// Copyright (c) 2012-2019 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
// Red Hat, Inc. - initial API and implementation
//
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/eclipse/che-operator/pkg/util"
oauth "github.com/openshift/api/oauth/v1"
routev1 "github.com/openshift/api/route/v1"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/source"
"time"
)
var log = logf.Log.WithName("controller_che")
var (
k8sclient = GetK8Client()
)
// Add creates a new CheCluster Controller and adds it to the Manager. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
return add(mgr, newReconciler(mgr))
}
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcileChe{client: mgr.GetClient(), scheme: mgr.GetScheme()}
}
// add adds a new Controller to mgr with r as the reconcile.Reconciler
func add(mgr manager.Manager, r reconcile.Reconciler) error {
isOpenShift, _, err := util.DetectOpenShift()
if err != nil {
logrus.Errorf("An error occurred when detecting current infra: %s", err)
}
// Create a new controller
c, err := controller.New("che-controller", mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}
// register OpenShift routes in the scheme
if isOpenShift {
if err := routev1.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Errorf("Failed to add OpenShift route to scheme: %s", err)
}
if err := oauth.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Errorf("Failed to add oAuth to scheme: %s", err)
}
}
// register RBAC in the scheme
if err := rbac.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Errorf("Failed to add RBAC to scheme: %s", err)
}
// Watch for changes to primary resource CheCluster
err = c.Watch(&source.Kind{Type: &orgv1.CheCluster{}}, &handler.EnqueueRequestForObject{})
if err != nil {
return err
}
// Watch for changes to secondary resources and requeue the owner CheCluster
if err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
}); err != nil {
return err
}
if err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
}); err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &rbac.Role{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &rbac.RoleBinding{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
if isOpenShift {
err = c.Watch(&source.Kind{Type: &routev1.Route{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
} else {
err = c.Watch(&source.Kind{Type: &v1beta1.Ingress{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
}
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
return nil
}
var _ reconcile.Reconciler = &ReconcileChe{}
var oAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org"
// ReconcileChe reconciles a CheCluster object
type ReconcileChe struct {
// 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
scheme *runtime.Scheme
tests bool
}
// Reconcile reads that state of the cluster for a CheCluster object and makes changes based on the state read
// and what is in the CheCluster.Spec. The Controller will requeue the Request to be processed again if the returned error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, error) {
// Fetch the CheCluster instance
tests := r.tests
instance, err := r.GetCR(request)
if err != nil {
if errors.IsNotFound(err) {
// 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
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}
isOpenShift, isOpenShift4, err := util.DetectOpenShift()
if err != nil {
logrus.Errorf("An error occurred when detecting current infra: %s", err)
}
if isOpenShift {
// delete oAuthClient before CR is deleted
doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth
if doInstallOpenShiftoAuthProvider {
if err := r.ReconcileFinalizer(instance); err != nil {
return reconcile.Result{}, err
}
}
}
if isOpenShift {
// create a secret with router tls cert when on OpenShift infra and router is configured with a self signed certificate
if instance.Spec.Server.SelfSignedCert ||
// To use Openshift v4 OAuth, the OAuth endpoints are served from a namespace
// and NOT from the Openshift API Master URL (as in v3)
// So we also need the self-signed certificate to access them (same as the Che server)
(isOpenShift4 && instance.Spec.Auth.OpenShiftOauth && ! instance.Spec.Server.TlsSupport) {
if err := r.CreateTLSSecret(instance, "", "self-signed-certificate"); err != nil {
return reconcile.Result{}, err
}
}
if instance.Spec.Auth.OpenShiftOauth {
// create a secret with OpenShift API crt to be added to keystore that RH SSO will consume
baseURL, err := util.GetClusterPublicHostname(isOpenShift4)
if err != nil {
logrus.Errorf("Failed to get OpenShift cluster public hostname. A secret with API crt will not be created and consumed by RH-SSO/Keycloak")
} else {
if err := r.CreateTLSSecret(instance, baseURL, "openshift-api-crt"); err != nil {
return reconcile.Result{}, err
}
}
}
}
if !tests {
deployment := &appsv1.Deployment{}
name := "che"
cheFlavor := instance.Spec.Server.CheFlavor
if cheFlavor == "codeready" {
name = cheFlavor
}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, deployment)
if err != nil && instance.Status.CheClusterRunning != UnavailableStatus {
if err := r.SetCheUnavailableStatus(instance, request); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
// create service accounts:
// che is the one which token is used to create workspace objects
// che-workspace is SA used by plugins like exec and terminal with limited privileges
cheServiceAccount := deploy.NewServiceAccount(instance, "che")
if err := r.CreateServiceAccount(instance, cheServiceAccount); err != nil {
return reconcile.Result{}, err
}
workspaceServiceAccount := deploy.NewServiceAccount(instance, "che-workspace")
if err := r.CreateServiceAccount(instance, workspaceServiceAccount); err != nil {
return reconcile.Result{}, err
}
// create exec and view roles for CheCluster server and workspaces
execRole := deploy.NewRole(instance, "exec", []string{"pods/exec"}, []string{"*"})
if err := r.CreateNewRole(instance, execRole); err != nil {
return reconcile.Result{}, err
}
viewRole := deploy.NewRole(instance, "view", []string{"pods"}, []string{"list"})
if err := r.CreateNewRole(instance, viewRole); err != nil {
return reconcile.Result{}, err
}
// create RoleBindings for created (and existing ClusterRole) roles and service accounts
cheRoleBinding := deploy.NewRoleBinding(instance, "che", cheServiceAccount.Name, "edit", "ClusterRole")
if err := r.CreateNewRoleBinding(instance, cheRoleBinding); err != nil {
return reconcile.Result{}, err
}
execRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-exec", workspaceServiceAccount.Name, execRole.Name, "Role")
if err = r.CreateNewRoleBinding(instance, execRoleBinding); err != nil {
return reconcile.Result{}, err
}
viewRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-view", workspaceServiceAccount.Name, viewRole.Name, "Role")
if err := r.CreateNewRoleBinding(instance, viewRoleBinding); err != nil {
return reconcile.Result{}, err
}
// If the user specified an additional cluster role to use for the Che workspace, create a role binding for it
// Use a role binding instead of a cluster role binding to keep the additional access scoped to the workspace's namespace
workspaceClusterRole := instance.Spec.Server.CheWorkspaceClusterRole
if workspaceClusterRole != "" {
customRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-custom", workspaceServiceAccount.Name, workspaceClusterRole, "ClusterRole")
if err = r.CreateNewRoleBinding(instance, customRoleBinding); err != nil {
return reconcile.Result{}, err
}
}
if err := r.GenerateAndSaveFields(instance, request); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
chePostgresPassword := instance.Spec.Database.ChePostgresPassword
keycloakPostgresPassword := instance.Spec.Auth.KeycloakPostgresPassword
keycloakAdminPassword := instance.Spec.Auth.KeycloakAdminPassword
// Create Postgres resources and provisioning unless an external DB is used
externalDB := instance.Spec.Database.ExternalDB
if !externalDB {
// Create a new postgres service
postgresLabels := deploy.GetLabels(instance, "postgres")
postgresService := deploy.NewService(instance, "postgres", []string{"postgres"}, []int32{5432}, postgresLabels)
if err := r.CreateService(instance, postgresService); err != nil {
return reconcile.Result{}, err
}
// Create a new Postgres PVC object
pvc := deploy.NewPvc(instance, "postgres-data", "1Gi", postgresLabels)
if err := r.CreatePVC(instance, pvc); err != nil {
return reconcile.Result{}, err
}
if !tests {
err = r.client.Get(context.TODO(), types.NamespacedName{Name: pvc.Name, Namespace: instance.Namespace}, pvc)
if pvc.Status.Phase != "Bound" {
k8sclient.GetPostgresStatus(pvc, instance.Namespace)
}
}
// Create a new Postgres deployment
postgresDeployment := deploy.NewPostgresDeployment(instance, chePostgresPassword, isOpenShift)
if err := r.CreateNewDeployment(instance, postgresDeployment); err != nil {
return reconcile.Result{}, err
}
time.Sleep(time.Duration(1) * time.Second)
pgDeployment, err := r.GetEffectiveDeployment(instance, postgresDeployment.Name)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", postgresDeployment.Name, err)
return reconcile.Result{}, err
}
if !tests {
if pgDeployment.Status.AvailableReplicas != 1 {
scaled := k8sclient.GetDeploymentStatus("postgres", instance.Namespace)
if !scaled {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
pgCommand := deploy.GetPostgresProvisionCommand(instance)
dbStatus := instance.Status.DbProvisoned
// provision Db and users for Che and Keycloak servers
if !dbStatus {
podToExec, err := k8sclient.GetDeploymentPod(pgDeployment.Name, instance.Namespace)
if err != nil {
return reconcile.Result{}, err
}
provisioned := ExecIntoPod(podToExec, pgCommand, "create Keycloak DB, user, privileges", instance.Namespace)
if provisioned {
for {
instance.Status.DbProvisoned = true
if err := r.UpdateCheCRStatus(instance, "status: provisioned with DB and user", "true"); err != nil {
instance, _ = r.GetCR(request)
} else {
break
}
}
} else {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
}
}
cheFlavor := util.GetValue(instance.Spec.Server.CheFlavor, deploy.DefaultCheFlavor)
ingressStrategy := util.GetValue(instance.Spec.K8SOnly.IngressStrategy, deploy.DefaultIngressStrategy)
ingressDomain := instance.Spec.K8SOnly.IngressDomain
tlsSupport := instance.Spec.Server.TlsSupport
protocol := "http"
if tlsSupport {
protocol = "https"
}
addRegistryRoute := func (registryType string) (string, error) {
registryName := registryType + "-registry"
host := ""
if !isOpenShift {
ingress := deploy.NewIngress(instance, registryName, registryName, 8080)
if err := r.CreateNewIngress(instance, ingress); err != nil {
return "", err
}
host = ingressDomain
if ingressStrategy == "multi-host" {
host = registryName + "-" + instance.Namespace + "." + ingressDomain
}
} else {
route := deploy.NewRoute(instance, registryName, registryName, 8080)
if tlsSupport {
route = deploy.NewTlsRoute(instance, registryName, registryName, 8080)
}
if err := r.CreateNewRoute(instance, route); err != nil {
return "", err
}
host = route.Spec.Host
if len(host) < 1 {
cheRoute := r.GetEffectiveRoute(instance, route.Name)
host = cheRoute.Spec.Host
}
}
return protocol + "://" + host, nil
}
addRegistryDeployment := func (
registryType string,
registryImage string,
registryImagePullPolicy corev1.PullPolicy,
registryMemoryLimit string,
registryMemoryRequest string,
probePath string,
) (*reconcile.Result, error) {
registryName := registryType + "-registry"
// Create a new registry service
registryLabels := deploy.GetLabels(instance, registryName)
registryService := deploy.NewService(instance, registryName, []string{"http"}, []int32{8080}, registryLabels)
if err := r.CreateService(instance,registryService); err != nil {
return &reconcile.Result{}, err
}
// Create a new registry deployment
registryDeployment := deploy.NewRegistryDeployment(
instance,
registryType,
registryImage,
registryImagePullPolicy,
registryMemoryLimit,
registryMemoryRequest,
probePath,
)
if err := r.CreateNewDeployment(instance, registryDeployment); err != nil {
return &reconcile.Result{}, err
}
time.Sleep(time.Duration(1) * time.Second)
effectiveDeployment, err := r.GetEffectiveDeployment(instance, registryDeployment.Name)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", registryDeployment.Name, err)
return &reconcile.Result{}, err
}
if !tests {
if effectiveDeployment.Status.AvailableReplicas != 1 {
scaled := k8sclient.GetDeploymentStatus(registryName, instance.Namespace)
if !scaled {
return &reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
if effectiveDeployment.Spec.Template.Spec.Containers[0].Image != registryImage {
newDeployment := deploy.NewRegistryDeployment(
instance,
registryType,
registryImage,
registryImagePullPolicy,
registryMemoryLimit,
registryMemoryRequest,
probePath,
)
logrus.Infof("Updating %s registry deployment with an image %s", registryType, registryImage)
if err := controllerutil.SetControllerReference(instance, newDeployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
if err := r.client.Update(context.TODO(), newDeployment); err != nil {
logrus.Errorf("Failed to update %s registry deployment: %s", registryType, err)
}
}
}
return nil, nil
}
// Create Plugin registry resources unless an external registry is used
externalPluginRegistry := instance.Spec.Server.ExternalPluginRegistry
if !externalPluginRegistry {
pluginRegistryURL, err := addRegistryRoute("plugin")
if err != nil {
return reconcile.Result{}, err
}
if cheFlavor != "codeready" {
pluginRegistryURL += "/v3"
}
instance.Status.PluginRegistryURL = pluginRegistryURL
if err := r.UpdateCheCRStatus(instance, "status: Plugin Registry URL", pluginRegistryURL); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
result, err := addRegistryDeployment(
"plugin",
util.GetValue(instance.Spec.Server.PluginRegistryImage, deploy.DefaultPluginRegistryImage),
corev1.PullPolicy(util.GetValue(string(instance.Spec.Server.PluginRegistryImagePullPolicy), deploy.DefaultPluginRegistryPullPolicy)),
util.GetValue(string(instance.Spec.Server.PluginRegistryMemoryLimit), deploy.DefaultPluginRegistryMemoryLimit),
util.GetValue(string(instance.Spec.Server.PluginRegistryMemoryRequest), deploy.DefaultPluginRegistryMemoryRequest),
"/v3/plugins/",
)
if err != nil || result != nil {
return *result, err
}
}
// Create devfile registry resources unless an external registry is used
externalDevfileRegistry := instance.Spec.Server.ExternalDevfileRegistry
if !externalDevfileRegistry {
devfileRegistryURL, err := addRegistryRoute("devfile")
if err != nil {
return reconcile.Result{}, err
}
instance.Status.DevfileRegistryURL = devfileRegistryURL
if err := r.UpdateCheCRStatus(instance, "status: Devfile Registry URL", devfileRegistryURL); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
result, err := addRegistryDeployment(
"devfile",
util.GetValue(instance.Spec.Server.DevfileRegistryImage, deploy.DefaultDevfileRegistryImage),
corev1.PullPolicy(util.GetValue(string(instance.Spec.Server.DevfileRegistryImagePullPolicy), deploy.DefaultDevfileRegistryPullPolicy)),
util.GetValue(string(instance.Spec.Server.DevfileRegistryMemoryLimit), deploy.DefaultDevfileRegistryMemoryLimit),
util.GetValue(string(instance.Spec.Server.DevfileRegistryMemoryRequest), deploy.DefaultDevfileRegistryMemoryRequest),
"/devfiles/",
)
if err != nil || result != nil {
return *result, err
}
}
// create Che service and route
cheLabels := deploy.GetLabels(instance, util.GetValue(instance.Spec.Server.CheFlavor, deploy.DefaultCheFlavor))
cheService := deploy.NewService(instance, "che-host", []string{"http", "metrics"}, []int32{8080, 8087}, cheLabels)
if err := r.CreateService(instance, cheService); err != nil {
return reconcile.Result{}, err
}
if !isOpenShift {
cheIngress := deploy.NewIngress(instance, cheFlavor, "che-host", 8080)
if err := r.CreateNewIngress(instance, cheIngress); err != nil {
return reconcile.Result{}, err
}
cheHost := ingressDomain
if ingressStrategy == "multi-host" {
cheHost = cheFlavor + "-" + instance.Namespace + "." + ingressDomain
}
if len(instance.Spec.Server.CheHost) == 0 {
instance.Spec.Server.CheHost = cheHost
if err := r.UpdateCheCRSpec(instance, "CheHost URL", cheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
} else {
cheRoute := deploy.NewRoute(instance, cheFlavor, "che-host", 8080)
if tlsSupport {
cheRoute = deploy.NewTlsRoute(instance, cheFlavor, "che-host", 8080)
}
if err := r.CreateNewRoute(instance, cheRoute); err != nil {
return reconcile.Result{}, err
}
if len(instance.Spec.Server.CheHost) == 0 {
instance.Spec.Server.CheHost = cheRoute.Spec.Host
if len(cheRoute.Spec.Host) < 1 {
cheRoute := r.GetEffectiveRoute(instance, cheRoute.Name)
instance.Spec.Server.CheHost = cheRoute.Spec.Host
}
if err := r.UpdateCheCRSpec(instance, "CheHost URL", instance.Spec.Server.CheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
// create and provision Keycloak related objects
ExternalKeycloak := instance.Spec.Auth.ExternalKeycloak
if !ExternalKeycloak {
keycloakLabels := deploy.GetLabels(instance, "keycloak")
keycloakService := deploy.NewService(instance, "keycloak", []string{"http"}, []int32{8080}, keycloakLabels)
if err := r.CreateService(instance, keycloakService); err != nil {
return reconcile.Result{}, err
}
// create Keycloak ingresses when on k8s
if !isOpenShift {
keycloakIngress := deploy.NewIngress(instance, "keycloak", "keycloak", 8080)
if err := r.CreateNewIngress(instance, keycloakIngress); err != nil {
return reconcile.Result{}, err
}
keycloakURL := protocol + "://" + ingressDomain
if ingressStrategy == "multi-host" {
keycloakURL = protocol + "://keycloak-" + instance.Namespace + "." + ingressDomain
}
if len(instance.Spec.Auth.KeycloakURL) == 0 {
instance.Spec.Auth.KeycloakURL = keycloakURL
if err := r.UpdateCheCRSpec(instance, "Keycloak URL", instance.Spec.Auth.KeycloakURL); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
} else {
// create Keycloak route
keycloakRoute := deploy.NewRoute(instance, "keycloak", "keycloak", 8080)
if tlsSupport {
keycloakRoute = deploy.NewTlsRoute(instance, "keycloak", "keycloak", 8080)
}
if err = r.CreateNewRoute(instance, keycloakRoute); err != nil {
return reconcile.Result{}, err
}
keycloakURL := keycloakRoute.Spec.Host
if len(instance.Spec.Auth.KeycloakURL) == 0 {
instance.Spec.Auth.KeycloakURL = protocol + "://" + keycloakURL
if len(keycloakURL) < 1 {
keycloakURL := r.GetEffectiveRoute(instance, keycloakRoute.Name).Spec.Host
instance.Spec.Auth.KeycloakURL = protocol + "://" + keycloakURL
}
if err := r.UpdateCheCRSpec(instance, "Keycloak URL", instance.Spec.Auth.KeycloakURL); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Status.KeycloakURL = protocol + "://" + keycloakURL
if err := r.UpdateCheCRStatus(instance, "status: Keycloak URL", instance.Spec.Auth.KeycloakURL); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
keycloakDeployment := deploy.NewKeycloakDeployment(instance, keycloakPostgresPassword, keycloakAdminPassword, cheFlavor,
r.GetEffectiveSecretResourceVersion(instance, "self-signed-certificate"),
r.GetEffectiveSecretResourceVersion(instance, "openshift-api-crt"))
if err := r.CreateNewDeployment(instance, keycloakDeployment); err != nil {
return reconcile.Result{}, err
}
time.Sleep(time.Duration(1) * time.Second)
deployment, err := r.GetEffectiveDeployment(instance, keycloakDeployment.Name)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", keycloakDeployment.Name, err)
return reconcile.Result{}, err
}
if !tests {
if deployment.Status.AvailableReplicas != 1 {
scaled := k8sclient.GetDeploymentStatus(keycloakDeployment.Name, instance.Namespace)
if !scaled {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
cheCertSecretVersion := r.GetEffectiveSecretResourceVersion(instance, "self-signed-certificate")
openshiftApiCertSecretVersion := r.GetEffectiveSecretResourceVersion(instance, "openshift-api-crt")
if deployment.Spec.Template.Spec.Containers[0].Image != instance.Spec.Auth.KeycloakImage ||
cheCertSecretVersion != deployment.Annotations["che.self-signed-certificate.version"] ||
openshiftApiCertSecretVersion != deployment.Annotations["che.openshift-api-crt.version"] {
keycloakDeployment := deploy.NewKeycloakDeployment(instance, keycloakPostgresPassword, keycloakAdminPassword, cheFlavor, cheCertSecretVersion, openshiftApiCertSecretVersion)
logrus.Infof("Updating Keycloak deployment with an image %s", instance.Spec.Auth.KeycloakImage)
if err := controllerutil.SetControllerReference(instance, keycloakDeployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
if err := r.client.Update(context.TODO(), keycloakDeployment); err != nil {
logrus.Errorf("Failed to update Keycloak deployment: %s", err)
}
}
keycloakRealmClientStatus := instance.Status.KeycloakProvisoned
if !keycloakRealmClientStatus {
if err := r.CreateKyecloakResources(instance, request, keycloakDeployment.Name); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
}
if isOpenShift {
doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth
if doInstallOpenShiftoAuthProvider {
openShiftIdentityProviderStatus := instance.Status.OpenShiftoAuthProvisioned
if !openShiftIdentityProviderStatus {
if err := r.CreateIdentityProviderItems(instance, request, cheFlavor, keycloakDeployment.Name, isOpenShift4); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
}
}
}
// create Che ConfigMap which is synced with CR and is not supposed to be manually edited
// controller will reconcile this CM with CR spec
cheHost := instance.Spec.Server.CheHost
cheEnv := deploy.GetConfigMapData(instance)
cheConfigMap := deploy.NewCheConfigMap(instance, cheEnv)
if err := r.CreateNewConfigMap(instance, cheConfigMap); err != nil {
return reconcile.Result{}, err
}
// create a custom ConfigMap that won't be synced with CR spec
// to be able to override envs and not clutter CR spec with fields which are too numerous
customCM := &corev1.ConfigMap{
Data: deploy.GetCustomConfigMapData(),
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap"},
ObjectMeta: metav1.ObjectMeta{
Name: "custom",
Namespace: instance.Namespace,
Labels: cheLabels}}
if err := r.CreateNewConfigMap(instance, customCM); err != nil {
return reconcile.Result{}, err
}
// configMap resource version will be an env in Che deployment to easily update it when a ConfigMap changes
// which will automatically trigger Che rolling update
cmResourceVersion := cheConfigMap.ResourceVersion
// create Che deployment
cheImageRepo := util.GetValue(instance.Spec.Server.CheImage, deploy.DefaultCheServerImageRepo)
cheImageTag := util.GetValue(instance.Spec.Server.CheImageTag, deploy.DefaultCheServerImageTag)
if cheFlavor == "codeready" {
cheImageRepo = util.GetValue(instance.Spec.Server.CheImage, deploy.DefaultCodeReadyServerImageRepo)
cheImageTag = util.GetValue(instance.Spec.Server.CheImageTag, deploy.DefaultCodeReadyServerImageTag)
}
cheDeployment, err := deploy.NewCheDeployment(instance, cheImageRepo, cheImageTag, cmResourceVersion, isOpenShift)
if err != nil {
return reconcile.Result{}, err
}
if err = r.CreateNewDeployment(instance, cheDeployment); err != nil {
return reconcile.Result{}, err
}
// sometimes Get cannot find deployment right away
time.Sleep(time.Duration(1) * time.Second)
deployment, err := r.GetEffectiveDeployment(instance, cheDeployment.Name)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", cheDeployment.Name, err)
return reconcile.Result{}, err
}
if !tests {
if deployment.Status.AvailableReplicas != 1 {
instance, _ := r.GetCR(request)
if err := r.SetCheUnavailableStatus(instance, request); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
scaled := k8sclient.GetDeploymentStatus(cheDeployment.Name, instance.Namespace)
if !scaled {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheDeployment.Name, Namespace: instance.Namespace}, deployment)
if deployment.Status.AvailableReplicas == 1 {
if err := r.SetCheAvailableStatus(instance, request, protocol, cheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
if instance.Status.CheVersion != cheImageTag {
instance.Status.CheVersion = cheImageTag
if err := r.UpdateCheCRStatus(instance, "version", cheImageTag); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
}
if deployment.Status.Replicas > 1 {
logrus.Infof("Deployment %s is in the rolling update state", cheDeployment.Name)
if err := r.SetCheRollingUpdateStatus(instance, request); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
k8sclient.GetDeploymentRollingUpdateStatus(cheDeployment.Name, instance.Namespace)
deployment, _ := r.GetEffectiveDeployment(instance, cheDeployment.Name)
if deployment.Status.Replicas == 1 {
if err := r.SetCheAvailableStatus(instance, request, protocol, cheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
}
if deployment.Spec.Template.Spec.Containers[0].Image != cheDeployment.Spec.Template.Spec.Containers[0].Image {
if err := controllerutil.SetControllerReference(instance, deployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
logrus.Infof("Updating %s %s with image %s:%s", cheDeployment.Name, cheDeployment.Kind, cheImageRepo, cheImageTag)
instance.Status.CheVersion = cheImageTag
if err := r.UpdateCheCRStatus(instance, "version", cheImageTag); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
if err := r.client.Update(context.TODO(), cheDeployment); err != nil {
logrus.Errorf("Failed to update %s %s: %s", deployment.Kind, deployment.Name, err)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
// reconcile routes/ingresses before reconciling Che deployment
activeConfigMap := &corev1.ConfigMap{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: instance.Namespace}, activeConfigMap); err != nil {
logrus.Errorf("ConfigMap %s not found: %s", activeConfigMap.Name, err)
}
if !tlsSupport && activeConfigMap.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] == "true" {
routesUpdated, err := r.ReconcileTLSObjects(instance, request, cheFlavor, tlsSupport, isOpenShift)
if err != nil {
logrus.Errorf("An error occurred when updating routes %s", err)
}
if routesUpdated {
logrus.Info("Routes have been updated with TLS config")
}
}
if tlsSupport && activeConfigMap.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] == "false" {
routesUpdated, err := r.ReconcileTLSObjects(instance, request, cheFlavor, tlsSupport, isOpenShift)
if err != nil {
logrus.Errorf("An error occurred when updating routes %s", err)
}
if routesUpdated {
logrus.Info("Routes have been updated with TLS config")
}
}
// Reconcile Che ConfigMap to align with CR spec
cmUpdated, err := r.UpdateConfigMap(instance)
if err != nil {
return reconcile.Result{}, 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 := r.ReconcileIdentityProvider(instance, isOpenShift4)
if deleted {
if err := r.DeleteFinalizer(instance); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Status.OpenShiftoAuthProvisioned = false
if err := r.UpdateCheCRStatus(instance, "provisioned with OpenShift oAuth", "false"); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Spec.Auth.OauthSecret = ""
if err := r.UpdateCheCRSpec(instance, "delete oAuth secret name", ""); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Spec.Auth.OauthClientName = ""
if err := r.UpdateCheCRSpec(instance, "delete oAuth client name", ""); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
if cmUpdated {
// sometimes an old cm resource version is returned ie get happens too fast - before server updates CM
time.Sleep(time.Duration(1) * time.Second)
cm := r.GetEffectiveConfigMap(instance, cheConfigMap.Name)
cmResourceVersion := cm.ResourceVersion
cheDeployment, err := deploy.NewCheDeployment(instance, cheImageRepo, cheImageTag, cmResourceVersion, isOpenShift)
if err != nil {
logrus.Errorf("An error occurred: %s", err)
}
if err := controllerutil.SetControllerReference(instance, cheDeployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
if err := r.client.Update(context.TODO(), cheDeployment); err != nil {
return reconcile.Result{}, err
}
}
deployment, _ = r.GetEffectiveDeployment(instance, cheDeployment.Name)
actualMemRequest := deployment.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory]
actualMemLimit := deployment.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory]
limitStr := actualMemLimit.String()
requestStr := actualMemRequest.String()
desiredRequest := util.GetValue(instance.Spec.Server.ServerMemoryRequest, deploy.DefaultServerMemoryRequest)
desiredLimit := util.GetValue(instance.Spec.Server.ServerMemoryLimit, deploy.DefaultServerMemoryLimit)
if desiredRequest != requestStr || desiredLimit != limitStr {
cheDeployment, err := deploy.NewCheDeployment(instance, cheImageRepo, cheImageTag, cmResourceVersion, isOpenShift)
if err != nil {
logrus.Errorf("An error occurred: %s", err)
}
if err := controllerutil.SetControllerReference(instance, cheDeployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
logrus.Infof("Updating deployment %s with new memory settings. Request: %s, limit: %s", cheDeployment.Name, desiredRequest, desiredLimit)
if err := r.client.Update(context.TODO(), cheDeployment); err != nil {
logrus.Errorf("Failed to update deployment: %s", err)
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}