From 1b09c61d49265fea1c3789bc00687746410adf69 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Thu, 11 Jun 2026 10:02:49 +0200 Subject: [PATCH 01/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- .../usernamespace/usernamespace_controller.go | 4 +- .../workspaces_config_controller_test.go | 5 +- pkg/common/diffs/diffs.go | 22 ++-- pkg/common/diffs/diffs_test.go | 11 +- .../devworkspace/dev_workspace_config.go | 17 ++- .../devworkspace/dev_workspace_config_test.go | 101 +++++++++++++++++- .../editors_definitions.go | 2 +- pkg/deploy/expose/expose.go | 2 +- pkg/deploy/pluginregistry/pluginregistry.go | 2 +- pkg/deploy/server/server_configmap.go | 2 +- pkg/deploy/tls/certificates.go | 53 ++++----- 11 files changed, 162 insertions(+), 59 deletions(-) diff --git a/controllers/usernamespace/usernamespace_controller.go b/controllers/usernamespace/usernamespace_controller.go index d142e7583c..ff54217d7f 100644 --- a/controllers/usernamespace/usernamespace_controller.go +++ b/controllers/usernamespace/usernamespace_controller.go @@ -434,7 +434,7 @@ func (r *CheUserNamespaceReconciler) reconcileUserSettings( Data: data, } - _, err := deploy.Sync(deployContext, cm, diffs.ConfigMapEnsureLabels) + _, err := deploy.Sync(deployContext, cm, diffs.ConfigMapEnsureMetadata(cm)) return err } @@ -488,7 +488,7 @@ func (r *CheUserNamespaceReconciler) reconcileGitTlsCertificate(ctx context.Cont target.Data["host"] = gitCert.Data[constants.GitSelfSignedCertsConfigMapGitHostKey] } - _, err := deploy.Sync(deployContext, &target, diffs.ConfigMapEnsureLabels) + _, err := deploy.Sync(deployContext, &target, diffs.ConfigMapEnsureMetadata(&target)) return err } diff --git a/controllers/workspaceconfig/workspaces_config_controller_test.go b/controllers/workspaceconfig/workspaces_config_controller_test.go index e188406838..86865e3550 100644 --- a/controllers/workspaceconfig/workspaces_config_controller_test.go +++ b/controllers/workspaceconfig/workspaces_config_controller_test.go @@ -18,8 +18,6 @@ import ( "testing" "github.com/eclipse-che/che-operator/controllers/namespacecache" - "github.com/eclipse-che/che-operator/pkg/common/diffs" - "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/api/errors" rbacv1 "k8s.io/api/rbac/v1" @@ -141,7 +139,8 @@ func TestUpdate(t *testing.T) { err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test", Namespace: "eclipse-che"}, srcCm) assert.NoError(t, err) - assert.True(t, cmp.Equal(dstCm, srcCm, diffs.ConfigMap([]string{constants.KubernetesPartOfLabelKey, constants.KubernetesComponentLabelKey}, nil))) + assert.Equal(t, srcCm.Labels[constants.KubernetesPartOfLabelKey], dstCm.Labels[constants.KubernetesPartOfLabelKey]) + assert.Equal(t, srcCm.Labels[constants.KubernetesComponentLabelKey], dstCm.Labels[constants.KubernetesComponentLabelKey]) // update source and destination config maps diff --git a/pkg/common/diffs/diffs.go b/pkg/common/diffs/diffs.go index ffc9d348e4..1601cc28ca 100644 --- a/pkg/common/diffs/diffs.go +++ b/pkg/common/diffs/diffs.go @@ -14,6 +14,7 @@ package diffs import ( "maps" + "slices" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -22,6 +23,7 @@ import ( corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" ) var Role = cmp.Options{ @@ -44,17 +46,19 @@ var SecurityContextConstraints = cmp.Options{ cmpopts.IgnoreFields(securityv1.SecurityContextConstraints{}, "TypeMeta", "ObjectMeta", "Priority"), } -var ConfigMapEnsureLabels = cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return maps.Equal(x.Labels, y.Labels) - }), +func ConfigMapEnsureMetadata(obj client.Object) cmp.Options { + return cmp.Options{ + cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), + ensureMetadata(obj), + } } -func ConfigMap(labels []string, annotations []string) cmp.Options { +func ensureMetadata(obj client.Object) cmp.Options { return cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - objectMetaComparator(labels, annotations), + doEnsureMetadata( + slices.Collect(maps.Keys(obj.GetLabels())), + slices.Collect(maps.Keys(obj.GetAnnotations())), + ), } } @@ -62,7 +66,7 @@ var ServiceMonitor = cmp.Options{ cmpopts.IgnoreFields(monitoringv1.ServiceMonitor{}, "TypeMeta", "ObjectMeta"), } -func objectMetaComparator(labels []string, annotations []string) cmp.Option { +func doEnsureMetadata(labels []string, annotations []string) cmp.Option { return cmp.Comparer(func(x, y metav1.ObjectMeta) bool { for _, label := range labels { if x.Labels[label] != y.Labels[label] { diff --git a/pkg/common/diffs/diffs_test.go b/pkg/common/diffs/diffs_test.go index e35d35e3c9..38400fda1f 100644 --- a/pkg/common/diffs/diffs_test.go +++ b/pkg/common/diffs/diffs_test.go @@ -34,7 +34,7 @@ func TestConfigMap(t *testing.T) { { srcCm: &corev1.ConfigMap{}, dstCm: &corev1.ConfigMap{}, - diffs: ConfigMapEnsureLabels, + diffs: ConfigMapEnsureMetadata(&corev1.ConfigMap{}), isEqual: true, }, { @@ -44,8 +44,13 @@ func TestConfigMap(t *testing.T) { Annotations: map[string]string{}, }, }, - dstCm: &corev1.ConfigMap{}, - diffs: ConfigMapEnsureLabels, + dstCm: &corev1.ConfigMap{}, + diffs: ConfigMapEnsureMetadata(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + Annotations: map[string]string{}, + }, + }), isEqual: true, }, } diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index ca464d575e..3a308d7a2f 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -26,6 +26,7 @@ import ( "github.com/eclipse-che/che-operator/pkg/common/reconciler" "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/tls" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -123,14 +124,17 @@ func updateWorkspaceConfig(ctx *chetypes.DeployContext, operatorConfig *controll updateInitContainers(devEnvironments, operatorConfig.Workspace) + if operatorConfig.Routing == nil { + operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} + } + + updateTLSCertificateConfigmapRef(ctx.CheCluster, operatorConfig.Routing) + // If the CheCluster has a configured proxy, or if the Che Operator has detected a proxy configuration, // we need to disable automatic proxy handling in the DevWorkspace Operator as its implementation collides // with ours -- they set environment variables the deployment spec explicitly, which overrides the proxy-settings // automount configmap. if ctx.Proxy.HttpProxy != "" || ctx.Proxy.HttpsProxy != "" { - if operatorConfig.Routing == nil { - operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} - } disableDWOProxy(operatorConfig.Routing) } @@ -301,6 +305,13 @@ func updateInitContainers(devEnvironments *chev2.CheClusterDevEnvironments, work workspaceConfig.InitContainers = devEnvironments.InitContainers } +func updateTLSCertificateConfigmapRef(cheCluster *chev2.CheCluster, routing *controllerv1alpha1.RoutingConfig) { + routing.TLSCertificateConfigmapRef = &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: cheCluster.Namespace, + } +} + func disableDWOProxy(routingConfig *controllerv1alpha1.RoutingConfig) { // Since we create proxy configmaps to mount proxy settings, we want to disable // proxy handling in DWO; otherwise the env vars added by DWO will override the env diff --git a/pkg/deploy/devworkspace/dev_workspace_config_test.go b/pkg/deploy/devworkspace/dev_workspace_config_test.go index 84e6f1d12d..4df88cd98a 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_test.go @@ -26,6 +26,7 @@ import ( chev2 "github.com/eclipse-che/che-operator/api/v2" "github.com/eclipse-che/che-operator/pkg/common/constants" "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/eclipse-che/che-operator/pkg/deploy/tls" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" @@ -730,7 +731,10 @@ func TestReconcileDevWorkspaceConfigStorage(t *testing.T) { err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) assert.NoError(t, err) - diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount")}) + diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount"), + cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + }) assert.Empty(t, diff) }) } @@ -1000,7 +1004,10 @@ func TestReconcileDevWorkspaceConfigForContainerCapabilities(t *testing.T) { assert.NoError(t, err) diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, - cmp.Options{cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "ProjectCloneConfig", "DeploymentStrategy", "DefaultStorageSize", "StorageClassName")}) + cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "ProjectCloneConfig", "DeploymentStrategy", "DefaultStorageSize", "StorageClassName"), + cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + }) assert.Empty(t, diff) }) } @@ -1156,7 +1163,10 @@ func TestReconcileDevWorkspaceConfigProgressTimeout(t *testing.T) { assert.NoError(t, err) diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, - cmp.Options{cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DefaultStorageSize", "StorageClassName", "ProjectCloneConfig", "DeploymentStrategy")}) + cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DefaultStorageSize", "StorageClassName", "ProjectCloneConfig", "DeploymentStrategy"), + cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + }) assert.Empty(t, diff) }) } @@ -2412,7 +2422,10 @@ func TestReconcileDevWorkspaceConfigPersistUserHome(t *testing.T) { dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) assert.NoError(t, err) - diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DeploymentStrategy", "ContainerSecurityContext")}) + diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DeploymentStrategy", "ContainerSecurityContext"), + cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + }) assert.Empty(t, diff) }) } @@ -3326,3 +3339,83 @@ func TestReconcileDevWorkspaceConfigForInitContainers(t *testing.T) { }) } } + +func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + + assert.NoError(t, err) + assert.NotNil(t, dwoc.Config.Routing) + assert.Equal(t, testCase.expectedOperatorConfig.Routing, dwoc.Config.Routing) + }) + } +} diff --git a/pkg/deploy/editors-definitions/editors_definitions.go b/pkg/deploy/editors-definitions/editors_definitions.go index 34579504b5..983a1c7f3f 100644 --- a/pkg/deploy/editors-definitions/editors_definitions.go +++ b/pkg/deploy/editors-definitions/editors_definitions.go @@ -162,5 +162,5 @@ func syncEditorDefinitions(ctx *chetypes.DeployContext, editorDefinitions map[st cm.Data[fileName] = string(content) } - return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureLabels) + return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureMetadata(cm)) } diff --git a/pkg/deploy/expose/expose.go b/pkg/deploy/expose/expose.go index 46295ed1c4..0b253773c0 100644 --- a/pkg/deploy/expose/expose.go +++ b/pkg/deploy/expose/expose.go @@ -76,7 +76,7 @@ func exposeWithGateway(deployContext *chetypes.DeployContext, if err != nil { return "", false, err } - done, err = deploy.Sync(deployContext, cfg, diffs.ConfigMapEnsureLabels) + done, err = deploy.Sync(deployContext, cfg, diffs.ConfigMapEnsureMetadata(cfg)) if !done { if err != nil { logrus.Error(err) diff --git a/pkg/deploy/pluginregistry/pluginregistry.go b/pkg/deploy/pluginregistry/pluginregistry.go index a5152c1a20..9ff6ba79e1 100644 --- a/pkg/deploy/pluginregistry/pluginregistry.go +++ b/pkg/deploy/pluginregistry/pluginregistry.go @@ -117,7 +117,7 @@ func (p *PluginRegistryReconciler) syncConfigMap(ctx *chetypes.DeployContext) (b Data: data, } - return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureLabels) + return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureMetadata(cm)) } func (p *PluginRegistryReconciler) ExposeEndpoint(ctx *chetypes.DeployContext) (string, bool, error) { diff --git a/pkg/deploy/server/server_configmap.go b/pkg/deploy/server/server_configmap.go index 741305d60b..ee78672991 100644 --- a/pkg/deploy/server/server_configmap.go +++ b/pkg/deploy/server/server_configmap.go @@ -83,7 +83,7 @@ func (s *CheServerReconciler) syncConfigMap(ctx *chetypes.DeployContext) (bool, err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), cm, - &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureLabels}, + &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureMetadata(cm)}, ) return err == nil, err diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 291d419d61..17b2ffed9c 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -164,11 +164,7 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } } - return deploy.Sync( - ctx, - openShiftCaBundleCM, - diffs.ConfigMap(append(deploy.DefaultsLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle), nil), - ) + return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapEnsureMetadata(openShiftCaBundleCM)) } else { // Add annotation to allow OpenShift network operator inject certificates // https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/networking/configuring-a-custom-pki#certificate-injection-using-operators_configuring-a-custom-pki @@ -176,11 +172,7 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes // Ignore Data field to allow OpenShift network operator inject certificates into CM // and avoid endless reconciliation loop - return deploy.Sync( - ctx, - openShiftCaBundleCM, - diffs.ConfigMap(append(deploy.DefaultsLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle), nil), - ) + return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapEnsureMetadata(openShiftCaBundleCM)) } } @@ -204,7 +196,7 @@ func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetype Data: map[string]string{kubernetesCABundleCertsFile: string(data)}, } - return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMapEnsureLabels) + return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMapEnsureMetadata(kubernetesCaBundleCM)) } // syncGitTrustedCertificates adds labels to git trusted certificates ConfigMap @@ -242,7 +234,7 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy return deploy.Sync( ctx, gitTrustedCertsCM, - diffs.ConfigMap([]string{constants.KubernetesPartOfLabelKey, constants.KubernetesComponentLabelKey}, nil), + diffs.ConfigMapEnsureMetadata(gitTrustedCertsCM), ) } @@ -278,7 +270,7 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy Data: map[string]string{"ca.crt": string(selfSignedCertSecret.Data["ca.crt"])}, } - return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMapEnsureLabels) + return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMapEnsureMetadata(selfSignedCertCM)) } return true, nil @@ -317,7 +309,7 @@ func (c *CertificatesReconciler) syncKubernetesRootCertificates(ctx *chetypes.De client, ctx, kubeRootCertsCM, - diffs.ConfigMap([]string{constants.KubernetesPartOfLabelKey, constants.KubernetesComponentLabelKey}, nil), + diffs.ConfigMapEnsureMetadata(kubeRootCertsCM), ) } @@ -341,7 +333,7 @@ func (c *CertificatesReconciler) syncOIDCIssuerCertificate(ctx *chetypes.DeployC return false, err } - err := ctx.ClusterAPI.ClientWrapper.Sync(context.TODO(), cm, &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureLabels}) + err := ctx.ClusterAPI.ClientWrapper.Sync(context.TODO(), cm, &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureMetadata(cm)}) return err == nil, err } @@ -378,6 +370,12 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex } } + // Mark ConfigMap as workspace config (will be mounted in all users' containers) + labels := deploy.GetLabels(constants.WorkspacesConfig) + + // Mark as `controller.devfile.io/watch-configmap=true` to allow DWO read custom certificates + labels[dwconstants.DevWorkspaceWatchConfigMapLabel] = "true" + // Sync a new ConfigMap with all trusted CA certificates mergedCABundlesCM := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ @@ -385,10 +383,9 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: CheMergedCABundleCertsCMName, - Namespace: ctx.CheCluster.Namespace, - // Mark ConfigMap as workspace config (will be mounted in all users' containers) - Labels: deploy.GetLabels(constants.WorkspacesConfig), + Name: CheMergedCABundleCertsCMName, + Namespace: ctx.CheCluster.Namespace, + Labels: labels, Annotations: map[string]string{}, }, Data: map[string]string{}, @@ -409,18 +406,12 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex } mergedCABundlesCM.Annotations[dwconstants.DevWorkspaceMountAccessModeAnnotation] = "0444" - return deploy.Sync( - ctx, - mergedCABundlesCM, - diffs.ConfigMap( - deploy.DefaultsLabelKeys, - []string{ - dwconstants.DevWorkspaceMountAsAnnotation, - dwconstants.DevWorkspaceMountPathAnnotation, - dwconstants.DevWorkspaceMountAccessModeAnnotation, - }, - ), - ) + if err := controllerutil.SetControllerReference(ctx.CheCluster, mergedCABundlesCM, ctx.ClusterAPI.Scheme); err != nil { + return false, err + } + + err = ctx.ClusterAPI.ClientWrapper.Sync(context.TODO(), mergedCABundlesCM, &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureMetadata(mergedCABundlesCM)}) + return err == nil, err } func readKubernetesCaBundle() ([]byte, error) { From 22cd9a2191ffd6a6b94a45b5d85a76005da40e0c Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Thu, 11 Jun 2026 14:32:06 +0200 Subject: [PATCH 02/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- .../usernamespace/usernamespace_controller.go | 4 +- pkg/common/diffs/diffs.go | 24 +++--- pkg/common/diffs/diffs_test.go | 11 +-- .../devworkspace/dev_workspace_config.go | 20 ++++- .../devworkspace/dev_workspace_config_test.go | 74 ++++++++++++++++--- .../editors_definitions.go | 2 +- pkg/deploy/expose/expose.go | 2 +- pkg/deploy/pluginregistry/pluginregistry.go | 2 +- pkg/deploy/server/server_configmap.go | 2 +- pkg/deploy/tls/certificates.go | 34 +++++++-- 10 files changed, 126 insertions(+), 49 deletions(-) diff --git a/controllers/usernamespace/usernamespace_controller.go b/controllers/usernamespace/usernamespace_controller.go index ff54217d7f..d142e7583c 100644 --- a/controllers/usernamespace/usernamespace_controller.go +++ b/controllers/usernamespace/usernamespace_controller.go @@ -434,7 +434,7 @@ func (r *CheUserNamespaceReconciler) reconcileUserSettings( Data: data, } - _, err := deploy.Sync(deployContext, cm, diffs.ConfigMapEnsureMetadata(cm)) + _, err := deploy.Sync(deployContext, cm, diffs.ConfigMapEnsureLabels) return err } @@ -488,7 +488,7 @@ func (r *CheUserNamespaceReconciler) reconcileGitTlsCertificate(ctx context.Cont target.Data["host"] = gitCert.Data[constants.GitSelfSignedCertsConfigMapGitHostKey] } - _, err := deploy.Sync(deployContext, &target, diffs.ConfigMapEnsureMetadata(&target)) + _, err := deploy.Sync(deployContext, &target, diffs.ConfigMapEnsureLabels) return err } diff --git a/pkg/common/diffs/diffs.go b/pkg/common/diffs/diffs.go index 1601cc28ca..c9b04be73b 100644 --- a/pkg/common/diffs/diffs.go +++ b/pkg/common/diffs/diffs.go @@ -46,19 +46,17 @@ var SecurityContextConstraints = cmp.Options{ cmpopts.IgnoreFields(securityv1.SecurityContextConstraints{}, "TypeMeta", "ObjectMeta", "Priority"), } -func ConfigMapEnsureMetadata(obj client.Object) cmp.Options { - return cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - ensureMetadata(obj), - } +var ConfigMapEnsureLabels = cmp.Options{ + cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), + cmp.Comparer(func(x, y metav1.ObjectMeta) bool { + return maps.Equal(x.Labels, y.Labels) + }), } -func ensureMetadata(obj client.Object) cmp.Options { +func ConfigMapWithMetadata(labelKeys []string, annotationKeys []string) cmp.Options { return cmp.Options{ - doEnsureMetadata( - slices.Collect(maps.Keys(obj.GetLabels())), - slices.Collect(maps.Keys(obj.GetAnnotations())), - ), + cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), + cmpMetadata(labelKeys, annotationKeys), } } @@ -66,7 +64,11 @@ var ServiceMonitor = cmp.Options{ cmpopts.IgnoreFields(monitoringv1.ServiceMonitor{}, "TypeMeta", "ObjectMeta"), } -func doEnsureMetadata(labels []string, annotations []string) cmp.Option { +func GetLabelsAndAnnotations(obj client.Object) ([]string, []string) { + return slices.Collect(maps.Keys(obj.GetLabels())), slices.Collect(maps.Keys(obj.GetAnnotations())) +} + +func cmpMetadata(labels []string, annotations []string) cmp.Option { return cmp.Comparer(func(x, y metav1.ObjectMeta) bool { for _, label := range labels { if x.Labels[label] != y.Labels[label] { diff --git a/pkg/common/diffs/diffs_test.go b/pkg/common/diffs/diffs_test.go index 38400fda1f..7e95ce4ec5 100644 --- a/pkg/common/diffs/diffs_test.go +++ b/pkg/common/diffs/diffs_test.go @@ -34,7 +34,7 @@ func TestConfigMap(t *testing.T) { { srcCm: &corev1.ConfigMap{}, dstCm: &corev1.ConfigMap{}, - diffs: ConfigMapEnsureMetadata(&corev1.ConfigMap{}), + diffs: ConfigMapWithMetadata(nil, nil), isEqual: true, }, { @@ -44,13 +44,8 @@ func TestConfigMap(t *testing.T) { Annotations: map[string]string{}, }, }, - dstCm: &corev1.ConfigMap{}, - diffs: ConfigMapEnsureMetadata(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{}, - }, - }), + dstCm: &corev1.ConfigMap{}, + diffs: ConfigMapWithMetadata([]string{}, []string{}), isEqual: true, }, } diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index 3a308d7a2f..52678b8b50 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -13,6 +13,7 @@ package devworkspace import ( + "context" "encoding/json" "fmt" "maps" @@ -31,6 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -124,17 +126,27 @@ func updateWorkspaceConfig(ctx *chetypes.DeployContext, operatorConfig *controll updateInitContainers(devEnvironments, operatorConfig.Workspace) - if operatorConfig.Routing == nil { - operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} - } + cm := &corev1.ConfigMap{} + exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound(context.TODO(), types.NamespacedName{Name: tls.CheMergedCABundleCertsCMName, Namespace: ctx.CheCluster.Namespace}, cm) + if err != nil { + return fmt.Errorf("failed to get ConfigMap %s: %w", tls.CheMergedCABundleCertsCMName, err) + } else if exists && len(cm.Data) > 0 { + if operatorConfig.Routing == nil { + operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} + } - updateTLSCertificateConfigmapRef(ctx.CheCluster, operatorConfig.Routing) + updateTLSCertificateConfigmapRef(ctx.CheCluster, operatorConfig.Routing) + } // If the CheCluster has a configured proxy, or if the Che Operator has detected a proxy configuration, // we need to disable automatic proxy handling in the DevWorkspace Operator as its implementation collides // with ours -- they set environment variables the deployment spec explicitly, which overrides the proxy-settings // automount configmap. if ctx.Proxy.HttpProxy != "" || ctx.Proxy.HttpsProxy != "" { + if operatorConfig.Routing == nil { + operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} + } + disableDWOProxy(operatorConfig.Routing) } diff --git a/pkg/deploy/devworkspace/dev_workspace_config_test.go b/pkg/deploy/devworkspace/dev_workspace_config_test.go index 4df88cd98a..983eb83ca0 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_test.go @@ -3345,29 +3345,38 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { name string cheCluster *chev2.CheCluster existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + expectedRoutingConfig *controllerv1alpha1.RoutingConfig } var testCases = []testCase{ { - name: "Create DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef", + name: "Create DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef when CA bundle ConfigMap exists with data", cheCluster: &chev2.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", Name: "eclipse-che", }, }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ Name: tls.CheMergedCABundleCertsCMName, Namespace: "eclipse-che", }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", }, }, }, { - name: "Update existing DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef", + name: "Update existing DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef when CA bundle ConfigMap exists with data", cheCluster: &chev2.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", @@ -3375,6 +3384,15 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, }, existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, &controllerv1alpha1.DevWorkspaceOperatorConfig{ ObjectMeta: metav1.ObjectMeta{ Name: devWorkspaceConfigName, @@ -3391,15 +3409,41 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, }, }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + { + name: "Do not set TLSCertificateConfigmapRef when CA bundle ConfigMap does not exist", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + expectedRoutingConfig: nil, + }, + { + name: "Do not set TLSCertificateConfigmapRef when CA bundle ConfigMap exists but has no data", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ Name: tls.CheMergedCABundleCertsCMName, Namespace: "eclipse-che", }, }, }, + expectedRoutingConfig: nil, }, } @@ -3414,8 +3458,14 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) assert.NoError(t, err) - assert.NotNil(t, dwoc.Config.Routing) - assert.Equal(t, testCase.expectedOperatorConfig.Routing, dwoc.Config.Routing) + if testCase.expectedRoutingConfig == nil { + if dwoc.Config.Routing != nil { + assert.Nil(t, dwoc.Config.Routing.TLSCertificateConfigmapRef) + } + } else { + assert.NotNil(t, dwoc.Config.Routing) + assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) + } }) } } diff --git a/pkg/deploy/editors-definitions/editors_definitions.go b/pkg/deploy/editors-definitions/editors_definitions.go index 983a1c7f3f..34579504b5 100644 --- a/pkg/deploy/editors-definitions/editors_definitions.go +++ b/pkg/deploy/editors-definitions/editors_definitions.go @@ -162,5 +162,5 @@ func syncEditorDefinitions(ctx *chetypes.DeployContext, editorDefinitions map[st cm.Data[fileName] = string(content) } - return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureMetadata(cm)) + return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureLabels) } diff --git a/pkg/deploy/expose/expose.go b/pkg/deploy/expose/expose.go index 0b253773c0..46295ed1c4 100644 --- a/pkg/deploy/expose/expose.go +++ b/pkg/deploy/expose/expose.go @@ -76,7 +76,7 @@ func exposeWithGateway(deployContext *chetypes.DeployContext, if err != nil { return "", false, err } - done, err = deploy.Sync(deployContext, cfg, diffs.ConfigMapEnsureMetadata(cfg)) + done, err = deploy.Sync(deployContext, cfg, diffs.ConfigMapEnsureLabels) if !done { if err != nil { logrus.Error(err) diff --git a/pkg/deploy/pluginregistry/pluginregistry.go b/pkg/deploy/pluginregistry/pluginregistry.go index 9ff6ba79e1..a5152c1a20 100644 --- a/pkg/deploy/pluginregistry/pluginregistry.go +++ b/pkg/deploy/pluginregistry/pluginregistry.go @@ -117,7 +117,7 @@ func (p *PluginRegistryReconciler) syncConfigMap(ctx *chetypes.DeployContext) (b Data: data, } - return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureMetadata(cm)) + return deploy.Sync(ctx, cm, diffs.ConfigMapEnsureLabels) } func (p *PluginRegistryReconciler) ExposeEndpoint(ctx *chetypes.DeployContext) (string, bool, error) { diff --git a/pkg/deploy/server/server_configmap.go b/pkg/deploy/server/server_configmap.go index ee78672991..741305d60b 100644 --- a/pkg/deploy/server/server_configmap.go +++ b/pkg/deploy/server/server_configmap.go @@ -83,7 +83,7 @@ func (s *CheServerReconciler) syncConfigMap(ctx *chetypes.DeployContext) (bool, err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), cm, - &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureMetadata(cm)}, + &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureLabels}, ) return err == nil, err diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 17b2ffed9c..07d562b064 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -164,7 +164,10 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } } - return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapEnsureMetadata(openShiftCaBundleCM)) + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(openShiftCaBundleCM) + labelKeys = append(labelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + + return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) } else { // Add annotation to allow OpenShift network operator inject certificates // https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/networking/configuring-a-custom-pki#certificate-injection-using-operators_configuring-a-custom-pki @@ -172,7 +175,8 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes // Ignore Data field to allow OpenShift network operator inject certificates into CM // and avoid endless reconciliation loop - return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapEnsureMetadata(openShiftCaBundleCM)) + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(openShiftCaBundleCM) + return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) } } @@ -196,7 +200,8 @@ func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetype Data: map[string]string{kubernetesCABundleCertsFile: string(data)}, } - return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMapEnsureMetadata(kubernetesCaBundleCM)) + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(kubernetesCaBundleCM) + return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) } // syncGitTrustedCertificates adds labels to git trusted certificates ConfigMap @@ -231,10 +236,11 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy gitTrustedCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg gitTrustedCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(gitTrustedCertsCM) return deploy.Sync( ctx, gitTrustedCertsCM, - diffs.ConfigMapEnsureMetadata(gitTrustedCertsCM), + diffs.ConfigMapWithMetadata(labelKeys, annotationKeys), ) } @@ -270,7 +276,8 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy Data: map[string]string{"ca.crt": string(selfSignedCertSecret.Data["ca.crt"])}, } - return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMapEnsureMetadata(selfSignedCertCM)) + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(selfSignedCertCM) + return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) } return true, nil @@ -305,11 +312,12 @@ func (c *CertificatesReconciler) syncKubernetesRootCertificates(ctx *chetypes.De kubeRootCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg kubeRootCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(kubeRootCertsCM) return deploy.SyncForClient( client, ctx, kubeRootCertsCM, - diffs.ConfigMapEnsureMetadata(kubeRootCertsCM), + diffs.ConfigMapWithMetadata(labelKeys, annotationKeys), ) } @@ -333,7 +341,12 @@ func (c *CertificatesReconciler) syncOIDCIssuerCertificate(ctx *chetypes.DeployC return false, err } - err := ctx.ClusterAPI.ClientWrapper.Sync(context.TODO(), cm, &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureMetadata(cm)}) + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(cm) + err := ctx.ClusterAPI.ClientWrapper.Sync( + context.TODO(), + cm, + &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)}, + ) return err == nil, err } @@ -410,7 +423,12 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex return false, err } - err = ctx.ClusterAPI.ClientWrapper.Sync(context.TODO(), mergedCABundlesCM, &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapEnsureMetadata(mergedCABundlesCM)}) + labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(mergedCABundlesCM) + err = ctx.ClusterAPI.ClientWrapper.Sync( + context.TODO(), + mergedCABundlesCM, + &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)}, + ) return err == nil, err } From 7f92c818b36c1649d958e9f7c0bf3c1d8d0a8079 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Thu, 11 Jun 2026 15:01:54 +0200 Subject: [PATCH 03/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/common/diffs/diffs_test.go | 4 +-- .../devworkspace/dev_workspace_config.go | 11 +++++--- .../devworkspace/dev_workspace_config_test.go | 27 ++++++++++--------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/pkg/common/diffs/diffs_test.go b/pkg/common/diffs/diffs_test.go index 7e95ce4ec5..e35d35e3c9 100644 --- a/pkg/common/diffs/diffs_test.go +++ b/pkg/common/diffs/diffs_test.go @@ -34,7 +34,7 @@ func TestConfigMap(t *testing.T) { { srcCm: &corev1.ConfigMap{}, dstCm: &corev1.ConfigMap{}, - diffs: ConfigMapWithMetadata(nil, nil), + diffs: ConfigMapEnsureLabels, isEqual: true, }, { @@ -45,7 +45,7 @@ func TestConfigMap(t *testing.T) { }, }, dstCm: &corev1.ConfigMap{}, - diffs: ConfigMapWithMetadata([]string{}, []string{}), + diffs: ConfigMapEnsureLabels, isEqual: true, }, } diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index 52678b8b50..ede09a2fe4 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -127,14 +127,20 @@ func updateWorkspaceConfig(ctx *chetypes.DeployContext, operatorConfig *controll updateInitContainers(devEnvironments, operatorConfig.Workspace) cm := &corev1.ConfigMap{} - exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound(context.TODO(), types.NamespacedName{Name: tls.CheMergedCABundleCertsCMName, Namespace: ctx.CheCluster.Namespace}, cm) + exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound( + context.TODO(), + types.NamespacedName{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: ctx.CheCluster.Namespace, + }, + cm, + ) if err != nil { return fmt.Errorf("failed to get ConfigMap %s: %w", tls.CheMergedCABundleCertsCMName, err) } else if exists && len(cm.Data) > 0 { if operatorConfig.Routing == nil { operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} } - updateTLSCertificateConfigmapRef(ctx.CheCluster, operatorConfig.Routing) } @@ -146,7 +152,6 @@ func updateWorkspaceConfig(ctx *chetypes.DeployContext, operatorConfig *controll if operatorConfig.Routing == nil { operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} } - disableDWOProxy(operatorConfig.Routing) } diff --git a/pkg/deploy/devworkspace/dev_workspace_config_test.go b/pkg/deploy/devworkspace/dev_workspace_config_test.go index 983eb83ca0..d9fee41c59 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_test.go @@ -3342,10 +3342,10 @@ func TestReconcileDevWorkspaceConfigForInitContainers(t *testing.T) { func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedRoutingConfig *controllerv1alpha1.RoutingConfig + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedRoutingConfig *controllerv1alpha1.RoutingConfig } var testCases = []testCase{ @@ -3441,6 +3441,7 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { Name: tls.CheMergedCABundleCertsCMName, Namespace: "eclipse-che", }, + Data: map[string]string{}, }, }, expectedRoutingConfig: nil, @@ -3455,17 +3456,17 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + err := deployContext.ClusterAPI.Client.Get( + context.TODO(), + types.NamespacedName{ + Name: devWorkspaceConfigName, + Namespace: testCase.cheCluster.Namespace, + }, + dwoc, + ) assert.NoError(t, err) - if testCase.expectedRoutingConfig == nil { - if dwoc.Config.Routing != nil { - assert.Nil(t, dwoc.Config.Routing.TLSCertificateConfigmapRef) - } - } else { - assert.NotNil(t, dwoc.Config.Routing) - assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) - } + assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) }) } } From 23ddd1227b3a02901e83ebb8a4ff31bd5ca15eab Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Fri, 12 Jun 2026 11:14:53 +0200 Subject: [PATCH 04/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/common/diffs/diffs.go | 1 + .../devworkspace/dev_workspace_config.go | 52 +++++---- .../devworkspace/dev_workspace_config_test.go | 106 +++++++++++++++++- pkg/deploy/tls/certificates.go | 2 + pkg/deploy/tls/certificates_test.go | 31 +++++ 5 files changed, 169 insertions(+), 23 deletions(-) diff --git a/pkg/common/diffs/diffs.go b/pkg/common/diffs/diffs.go index c9b04be73b..ad737a2d5d 100644 --- a/pkg/common/diffs/diffs.go +++ b/pkg/common/diffs/diffs.go @@ -53,6 +53,7 @@ var ConfigMapEnsureLabels = cmp.Options{ }), } +// ConfigMapWithMetadata respect existed labels and annotations func ConfigMapWithMetadata(labelKeys []string, annotationKeys []string) cmp.Options { return cmp.Options{ cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index ede09a2fe4..f0b37952f1 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -126,22 +126,8 @@ func updateWorkspaceConfig(ctx *chetypes.DeployContext, operatorConfig *controll updateInitContainers(devEnvironments, operatorConfig.Workspace) - cm := &corev1.ConfigMap{} - exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound( - context.TODO(), - types.NamespacedName{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: ctx.CheCluster.Namespace, - }, - cm, - ) - if err != nil { - return fmt.Errorf("failed to get ConfigMap %s: %w", tls.CheMergedCABundleCertsCMName, err) - } else if exists && len(cm.Data) > 0 { - if operatorConfig.Routing == nil { - operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} - } - updateTLSCertificateConfigmapRef(ctx.CheCluster, operatorConfig.Routing) + if err := updateTLSCertificateConfigmapRef(ctx, operatorConfig); err != nil { + return err } // If the CheCluster has a configured proxy, or if the Che Operator has detected a proxy configuration, @@ -322,11 +308,37 @@ func updateInitContainers(devEnvironments *chev2.CheClusterDevEnvironments, work workspaceConfig.InitContainers = devEnvironments.InitContainers } -func updateTLSCertificateConfigmapRef(cheCluster *chev2.CheCluster, routing *controllerv1alpha1.RoutingConfig) { - routing.TLSCertificateConfigmapRef = &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: cheCluster.Namespace, +func updateTLSCertificateConfigmapRef(ctx *chetypes.DeployContext, operatorConfig *controllerv1alpha1.OperatorConfiguration) error { + cm := &corev1.ConfigMap{} + exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound( + context.TODO(), + types.NamespacedName{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: ctx.CheCluster.Namespace, + }, + cm, + ) + + if err != nil { + return fmt.Errorf("failed to get ConfigMap %s: %w", tls.CheMergedCABundleCertsCMName, err) + } + + if exists && len(cm.Data) > 0 { + if operatorConfig.Routing == nil { + operatorConfig.Routing = &controllerv1alpha1.RoutingConfig{} + } + + operatorConfig.Routing.TLSCertificateConfigmapRef = &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: ctx.CheCluster.Namespace, + } + } else { + if operatorConfig.Routing != nil { + operatorConfig.Routing.TLSCertificateConfigmapRef = nil + } } + + return nil } func disableDWOProxy(routingConfig *controllerv1alpha1.RoutingConfig) { diff --git a/pkg/deploy/devworkspace/dev_workspace_config_test.go b/pkg/deploy/devworkspace/dev_workspace_config_test.go index d9fee41c59..2698b39fae 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_test.go @@ -1006,7 +1006,7 @@ func TestReconcileDevWorkspaceConfigForContainerCapabilities(t *testing.T) { diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "ProjectCloneConfig", "DeploymentStrategy", "DefaultStorageSize", "StorageClassName"), - cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), }) assert.Empty(t, diff) }) @@ -1165,7 +1165,7 @@ func TestReconcileDevWorkspaceConfigProgressTimeout(t *testing.T) { diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DefaultStorageSize", "StorageClassName", "ProjectCloneConfig", "DeploymentStrategy"), - cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), }) assert.Empty(t, diff) }) @@ -2424,7 +2424,7 @@ func TestReconcileDevWorkspaceConfigPersistUserHome(t *testing.T) { assert.NoError(t, err) diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DeploymentStrategy", "ContainerSecurityContext"), - cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), }) assert.Empty(t, diff) }) @@ -3446,6 +3446,106 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, expectedRoutingConfig: nil, }, + { + name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap is removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{}, + }, + { + name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap data is emptied", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{}, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{}, + }, + { + name: "Clear TLSCertificateConfigmapRef while preserving other routing config", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + }, + }, } for _, testCase := range testCases { diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 07d562b064..dba3702e85 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -165,6 +165,8 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(openShiftCaBundleCM) + + // add removed label to ensure a new object won't have it labelKeys = append(labelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) diff --git a/pkg/deploy/tls/certificates_test.go b/pkg/deploy/tls/certificates_test.go index 314f4cad56..54e619453e 100644 --- a/pkg/deploy/tls/certificates_test.go +++ b/pkg/deploy/tls/certificates_test.go @@ -385,6 +385,37 @@ func TestSyncCheCABundleCertsExcludesGitHostKeyWithDefaultConfigMap(t *testing.T assert.Equal(t, expected, cm.Data[kubernetesCABundleCertsFile]) } +func TestSyncOpenShiftCABundleCertificatesRemovesInjectLabel(t *testing.T) { + existingCM := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.DefaultCaBundleCertsCMName, + Namespace: "eclipse-che", + Labels: map[string]string{ + constants.KubernetesPartOfLabelKey: constants.CheEclipseOrg, + constants.KubernetesComponentLabelKey: constants.CheCABundle, + constants.ConfigOpenShiftIOInjectTrustedCaBundle: "true", + }, + }, + Data: map[string]string{ + "ca-bundle.crt": "openshift-injected-ca-bundle", + }, + } + + ctx := test.NewCtxBuilder().WithObjects(existingCM).Build() + ctx.CheCluster.Spec.DevEnvironments.TrustedCerts = &chev2.TrustedCerts{DisableWorkspaceCaBundleMount: ptr.To(true)} + + test.EnsureReconcile(t, ctx, NewCertificatesReconciler().Reconcile) + + cm := &corev1.ConfigMap{} + err := ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: constants.DefaultCaBundleCertsCMName, Namespace: "eclipse-che"}, cm) + assert.NoError(t, err) + + assert.Empty(t, cm.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle]) + assert.Equal(t, constants.CheEclipseOrg, cm.Labels[constants.KubernetesPartOfLabelKey]) + assert.Equal(t, constants.CheCABundle, cm.Labels[constants.KubernetesComponentLabelKey]) + assert.Empty(t, cm.Data["ca-bundle.crt"]) +} + func TestToggleDisableWorkspaceCaBundleMount(t *testing.T) { // Enable workspace CA bundle mount ctx := test.NewCtxBuilder().WithObjects(&corev1.ConfigMap{ From 93b884c07f38515f0f6d877241c337de669593fe Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Fri, 12 Jun 2026 12:25:58 +0200 Subject: [PATCH 05/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/common/diffs/diffs.go | 10 +- .../devworkspace/dev_workspace_config.go | 4 + .../devworkspace/dev_workspace_config_test.go | 212 +++++++++++++++++- pkg/deploy/labels.go | 18 +- pkg/deploy/tls/certificates.go | 52 +++-- pkg/deploy/tls/certificates_test.go | 25 +++ 6 files changed, 281 insertions(+), 40 deletions(-) diff --git a/pkg/common/diffs/diffs.go b/pkg/common/diffs/diffs.go index ad737a2d5d..bc8254bb51 100644 --- a/pkg/common/diffs/diffs.go +++ b/pkg/common/diffs/diffs.go @@ -14,7 +14,6 @@ package diffs import ( "maps" - "slices" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -23,7 +22,6 @@ import ( corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) var Role = cmp.Options{ @@ -53,8 +51,8 @@ var ConfigMapEnsureLabels = cmp.Options{ }), } -// ConfigMapWithMetadata respect existed labels and annotations -func ConfigMapWithMetadata(labelKeys []string, annotationKeys []string) cmp.Options { +// ConfigMap respects existed labels and annotations +func ConfigMap(labelKeys []string, annotationKeys []string) cmp.Options { return cmp.Options{ cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), cmpMetadata(labelKeys, annotationKeys), @@ -65,10 +63,6 @@ var ServiceMonitor = cmp.Options{ cmpopts.IgnoreFields(monitoringv1.ServiceMonitor{}, "TypeMeta", "ObjectMeta"), } -func GetLabelsAndAnnotations(obj client.Object) ([]string, []string) { - return slices.Collect(maps.Keys(obj.GetLabels())), slices.Collect(maps.Keys(obj.GetAnnotations())) -} - func cmpMetadata(labels []string, annotations []string) cmp.Option { return cmp.Comparer(func(x, y metav1.ObjectMeta) bool { for _, label := range labels { diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index f0b37952f1..2782aba122 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -335,6 +335,10 @@ func updateTLSCertificateConfigmapRef(ctx *chetypes.DeployContext, operatorConfi } else { if operatorConfig.Routing != nil { operatorConfig.Routing.TLSCertificateConfigmapRef = nil + + if *operatorConfig.Routing == (controllerv1alpha1.RoutingConfig{}) { + operatorConfig.Routing = nil + } } } diff --git a/pkg/deploy/devworkspace/dev_workspace_config_test.go b/pkg/deploy/devworkspace/dev_workspace_config_test.go index 2698b39fae..08506833b5 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_test.go @@ -3474,7 +3474,7 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, }, }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{}, + expectedRoutingConfig: nil, }, { name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap data is emptied", @@ -3511,7 +3511,51 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, }, }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{}, + expectedRoutingConfig: nil, + }, + { + name: "Re-reconcile is stable when TLSCertificateConfigmapRef already matches", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, }, { name: "Clear TLSCertificateConfigmapRef while preserving other routing config", @@ -3570,3 +3614,167 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }) } } + +func TestReconcileDevWorkspaceConfigProxyAndTLSComposition(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + httpProxy string + httpsProxy string + expectedRoutingConfig *controllerv1alpha1.RoutingConfig + } + + var testCases = []testCase{ + { + name: "Both proxy and TLS certificate configmap ref are set on RoutingConfig", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + }, + httpProxy: "http://proxy.example.com:3128", + httpsProxy: "https://proxy.example.com:3128", + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + ProxyConfig: &controllerv1alpha1.Proxy{ + HttpProxy: ptr.To(""), + HttpsProxy: ptr.To(""), + NoProxy: ptr.To(""), + }, + }, + }, + { + name: "Proxy set without TLS certificate configmap does not clobber routing", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + httpProxy: "http://proxy.example.com:3128", + httpsProxy: "https://proxy.example.com:3128", + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + ProxyConfig: &controllerv1alpha1.Proxy{ + HttpProxy: ptr.To(""), + HttpsProxy: ptr.To(""), + NoProxy: ptr.To(""), + }, + }, + }, + { + name: "TLS certificate configmap ref set without proxy does not clobber routing", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + { + name: "Both proxy and TLS compose with existing routing config", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + }, + }, + }, + }, + httpProxy: "http://proxy.example.com:3128", + httpsProxy: "https://proxy.example.com:3128", + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + ProxyConfig: &controllerv1alpha1.Proxy{ + HttpProxy: ptr.To(""), + HttpsProxy: ptr.To(""), + NoProxy: ptr.To(""), + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + deployContext.Proxy.HttpProxy = testCase.httpProxy + deployContext.Proxy.HttpsProxy = testCase.httpsProxy + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get( + context.TODO(), + types.NamespacedName{ + Name: devWorkspaceConfigName, + Namespace: testCase.cheCluster.Namespace, + }, + dwoc, + ) + + assert.NoError(t, err) + assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) + }) + } +} diff --git a/pkg/deploy/labels.go b/pkg/deploy/labels.go index 9defe566a9..efcb654fe6 100644 --- a/pkg/deploy/labels.go +++ b/pkg/deploy/labels.go @@ -13,18 +13,12 @@ package deploy import ( + "maps" + "slices" + "github.com/eclipse-che/che-operator/pkg/common/constants" defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults" -) - -var ( - DefaultsLabelKeys = []string{ - constants.KubernetesNameLabelKey, - constants.KubernetesInstanceLabelKey, - constants.KubernetesPartOfLabelKey, - constants.KubernetesComponentLabelKey, - constants.KubernetesManagedByLabelKey, - } + "sigs.k8s.io/controller-runtime/pkg/client" ) func GetLabels(component string) map[string]string { @@ -61,6 +55,10 @@ func GetLegacyLabels(component string) map[string]string { } } +func GetLabelsAndAnnotations(obj client.Object) ([]string, []string) { + return slices.Collect(maps.Keys(obj.GetLabels())), slices.Collect(maps.Keys(obj.GetAnnotations())) +} + func IsPartOfEclipseCheResourceAndManagedByOperator(labels map[string]string) bool { return labels[constants.KubernetesPartOfLabelKey] == constants.CheEclipseOrg && labels[constants.KubernetesManagedByLabelKey] == GetManagedByLabel() } diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index dba3702e85..e6588bcf23 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -25,6 +25,8 @@ import ( "github.com/eclipse-che/che-operator/pkg/common/diffs" k8sclient "github.com/eclipse-che/che-operator/pkg/common/k8s-client" "github.com/eclipse-che/che-operator/pkg/common/reconciler" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/eclipse-che/che-operator/pkg/common/utils" @@ -137,6 +139,9 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes openShiftCaBundleCM.Labels = utils.GetMapOrDefault(openShiftCaBundleCM.Labels, map[string]string{}) utils.AddMap(openShiftCaBundleCM.Labels, deploy.GetLabels(constants.CheCABundle)) + labelKeys := slices.Collect(maps.Keys(deploy.GetLabels(constants.CheCABundle))) + labelKeys = append(labelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + if ctx.CheCluster.IsDisableWorkspaceCaBundleMount() { // Remove annotation to stop OpenShift network operator from injecting certificates // https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/networking/configuring-a-custom-pki#certificate-injection-using-operators_configuring-a-custom-pki @@ -164,12 +169,7 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } } - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(openShiftCaBundleCM) - - // add removed label to ensure a new object won't have it - labelKeys = append(labelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) - - return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) + return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMap(labelKeys, nil)) } else { // Add annotation to allow OpenShift network operator inject certificates // https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/networking/configuring-a-custom-pki#certificate-injection-using-operators_configuring-a-custom-pki @@ -177,8 +177,14 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes // Ignore Data field to allow OpenShift network operator inject certificates into CM // and avoid endless reconciliation loop - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(openShiftCaBundleCM) - return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) + return deploy.Sync( + ctx, + openShiftCaBundleCM, + cmp.Options{ + diffs.ConfigMap(labelKeys, nil), + cmpopts.IgnoreFields(corev1.ConfigMap{}, "Data"), + }, + ) } } @@ -202,8 +208,8 @@ func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetype Data: map[string]string{kubernetesCABundleCertsFile: string(data)}, } - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(kubernetesCaBundleCM) - return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) + labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(kubernetesCaBundleCM) + return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMap(labelKeys, annotationKeys)) } // syncGitTrustedCertificates adds labels to git trusted certificates ConfigMap @@ -238,11 +244,11 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy gitTrustedCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg gitTrustedCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(gitTrustedCertsCM) + labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(gitTrustedCertsCM) return deploy.Sync( ctx, gitTrustedCertsCM, - diffs.ConfigMapWithMetadata(labelKeys, annotationKeys), + diffs.ConfigMap(labelKeys, annotationKeys), ) } @@ -278,8 +284,8 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy Data: map[string]string{"ca.crt": string(selfSignedCertSecret.Data["ca.crt"])}, } - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(selfSignedCertCM) - return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)) + labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(selfSignedCertCM) + return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMap(labelKeys, annotationKeys)) } return true, nil @@ -314,12 +320,12 @@ func (c *CertificatesReconciler) syncKubernetesRootCertificates(ctx *chetypes.De kubeRootCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg kubeRootCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(kubeRootCertsCM) + labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(kubeRootCertsCM) return deploy.SyncForClient( client, ctx, kubeRootCertsCM, - diffs.ConfigMapWithMetadata(labelKeys, annotationKeys), + diffs.ConfigMap(labelKeys, annotationKeys), ) } @@ -343,11 +349,14 @@ func (c *CertificatesReconciler) syncOIDCIssuerCertificate(ctx *chetypes.DeployC return false, err } - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(cm) + labelKeys := slices.Collect(maps.Keys(cm.GetLabels())) + err := ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), cm, - &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)}, + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap(labelKeys, nil), + }, ) return err == nil, err } @@ -425,11 +434,14 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex return false, err } - labelKeys, annotationKeys := diffs.GetLabelsAndAnnotations(mergedCABundlesCM) + labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(mergedCABundlesCM) + err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), mergedCABundlesCM, - &k8sclient.SyncOptions{DiffOpts: diffs.ConfigMapWithMetadata(labelKeys, annotationKeys)}, + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap(labelKeys, annotationKeys), + }, ) return err == nil, err } diff --git a/pkg/deploy/tls/certificates_test.go b/pkg/deploy/tls/certificates_test.go index 54e619453e..1483d6bfd0 100644 --- a/pkg/deploy/tls/certificates_test.go +++ b/pkg/deploy/tls/certificates_test.go @@ -526,6 +526,31 @@ func TestToggleDisableWorkspaceCaBundleMount(t *testing.T) { assert.Equal(t, 1, len(caCertsMergedCM.Data)) } +func TestSyncOpenShiftCABundleCertificatesIgnoresDataField(t *testing.T) { + ctx := test.NewCtxBuilder().Build() + + test.EnsureReconcile(t, ctx, NewCertificatesReconciler().Reconcile) + + caCertsCM := &corev1.ConfigMap{} + err := ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "ca-certs", Namespace: "eclipse-che"}, caCertsCM) + assert.NoError(t, err) + assert.Equal(t, "true", caCertsCM.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle]) + + // Simulate OpenShift network operator injecting CA bundle into the ConfigMap + caCertsCM.Data = map[string]string{"ca-bundle.crt": "openshift-injected-ca-bundle"} + err = ctx.ClusterAPI.Client.Update(context.TODO(), caCertsCM) + assert.NoError(t, err) + + // Reconcile again — Data field should be ignored in the diff + test.EnsureReconcile(t, ctx, NewCertificatesReconciler().Reconcile) + + caCertsCM = &corev1.ConfigMap{} + err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "ca-certs", Namespace: "eclipse-che"}, caCertsCM) + assert.NoError(t, err) + assert.Equal(t, "openshift-injected-ca-bundle", caCertsCM.Data["ca-bundle.crt"]) + assert.Equal(t, "true", caCertsCM.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle]) +} + func TestSyncCheCABundleCertsWithEmptyConfigMap(t *testing.T) { // A CA bundle ConfigMap exists but has no data entries emptyCert := &corev1.ConfigMap{ From 8ea9731c6ce1a79122539819a6d4f1708ad82451 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Fri, 12 Jun 2026 15:10:56 +0200 Subject: [PATCH 06/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- .../workspaces_config_controller_test.go | 3 +- .../devworkspace/dev_workspace_config.go | 3 +- .../dev_workspace_config_routing_test.go | 469 ++ .../dev_workspace_config_scheduling_test.go | 598 +++ .../dev_workspace_config_security_test.go | 742 ++++ ...v_workspace_config_service_account_test.go | 394 ++ .../dev_workspace_config_storage_test.go | 749 ++++ .../devworkspace/dev_workspace_config_test.go | 3780 ----------------- .../dev_workspace_config_workspace_test.go | 978 +++++ pkg/deploy/server/chehost_reconciler_test.go | 2 +- pkg/deploy/sync.go | 21 - pkg/deploy/sync_test.go | 15 - pkg/deploy/tls/certificates.go | 138 +- pkg/deploy/tls/certificates_test.go | 26 +- 14 files changed, 4013 insertions(+), 3905 deletions(-) create mode 100644 pkg/deploy/devworkspace/dev_workspace_config_routing_test.go create mode 100644 pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go create mode 100644 pkg/deploy/devworkspace/dev_workspace_config_security_test.go create mode 100644 pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go create mode 100644 pkg/deploy/devworkspace/dev_workspace_config_storage_test.go delete mode 100644 pkg/deploy/devworkspace/dev_workspace_config_test.go create mode 100644 pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go diff --git a/controllers/workspaceconfig/workspaces_config_controller_test.go b/controllers/workspaceconfig/workspaces_config_controller_test.go index 86865e3550..1219e7807d 100644 --- a/controllers/workspaceconfig/workspaces_config_controller_test.go +++ b/controllers/workspaceconfig/workspaces_config_controller_test.go @@ -82,10 +82,9 @@ func TestCreate(t *testing.T) { assert.NoError(t, err) dstCm := &corev1.ConfigMap{} - exists, err := deploy.Get(ctx, types.NamespacedName{Namespace: "user-che", Name: "test"}, dstCm) + err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: "user-che", Name: "test"}, dstCm) assert.NoError(t, err) - assert.True(t, exists) assert.Equal(t, 1, len(dstCm.Data)) assert.Equal(t, "new-value", dstCm.Data["key"]) } diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index 2782aba122..9c502b62ef 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "maps" + "reflect" "time" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" @@ -336,7 +337,7 @@ func updateTLSCertificateConfigmapRef(ctx *chetypes.DeployContext, operatorConfi if operatorConfig.Routing != nil { operatorConfig.Routing.TLSCertificateConfigmapRef = nil - if *operatorConfig.Routing == (controllerv1alpha1.RoutingConfig{}) { + if reflect.DeepEqual(operatorConfig.Routing, &controllerv1alpha1.RoutingConfig{}) { operatorConfig.Routing = nil } } diff --git a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go new file mode 100644 index 0000000000..047bf30512 --- /dev/null +++ b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go @@ -0,0 +1,469 @@ +// +// Copyright (c) 2019-2025 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 devworkspace + +import ( + "context" + "testing" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/eclipse-che/che-operator/pkg/deploy/tls" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedRoutingConfig *controllerv1alpha1.RoutingConfig + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef when CA bundle ConfigMap exists with data", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef when CA bundle ConfigMap exists with data", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + { + name: "Do not set TLSCertificateConfigmapRef when CA bundle ConfigMap does not exist", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + expectedRoutingConfig: nil, + }, + { + name: "Do not set TLSCertificateConfigmapRef when CA bundle ConfigMap exists but has no data", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{}, + }, + }, + expectedRoutingConfig: nil, + }, + { + name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap is removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: nil, + }, + { + name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap data is emptied", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{}, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: nil, + }, + { + name: "Re-reconcile is stable when TLSCertificateConfigmapRef already matches", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + { + name: "Clear TLSCertificateConfigmapRef while preserving other routing config", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get( + context.TODO(), + types.NamespacedName{ + Name: devWorkspaceConfigName, + Namespace: testCase.cheCluster.Namespace, + }, + dwoc, + ) + + assert.NoError(t, err) + assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) + }) + } +} + +func TestReconcileDevWorkspaceConfigProxyAndTLSComposition(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + httpProxy string + httpsProxy string + expectedRoutingConfig *controllerv1alpha1.RoutingConfig + } + + var testCases = []testCase{ + { + name: "Both proxy and TLS certificate configmap ref are set on RoutingConfig", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + }, + httpProxy: "http://proxy.example.com:3128", + httpsProxy: "https://proxy.example.com:3128", + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + ProxyConfig: &controllerv1alpha1.Proxy{ + HttpProxy: ptr.To(""), + HttpsProxy: ptr.To(""), + NoProxy: ptr.To(""), + }, + }, + }, + { + name: "Proxy set without TLS certificate configmap does not clobber routing", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + httpProxy: "http://proxy.example.com:3128", + httpsProxy: "https://proxy.example.com:3128", + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + ProxyConfig: &controllerv1alpha1.Proxy{ + HttpProxy: ptr.To(""), + HttpsProxy: ptr.To(""), + NoProxy: ptr.To(""), + }, + }, + }, + { + name: "TLS certificate configmap ref set without proxy does not clobber routing", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + }, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + }, + }, + { + name: "Both proxy and TLS compose with existing routing config", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + }, + existedObjects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + Data: map[string]string{ + "ca-bundle.crt": "certificate-data", + }, + }, + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + }, + }, + }, + }, + httpProxy: "http://proxy.example.com:3128", + httpsProxy: "https://proxy.example.com:3128", + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "che", + TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ + Name: tls.CheMergedCABundleCertsCMName, + Namespace: "eclipse-che", + }, + ProxyConfig: &controllerv1alpha1.Proxy{ + HttpProxy: ptr.To(""), + HttpsProxy: ptr.To(""), + NoProxy: ptr.To(""), + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + deployContext.Proxy.HttpProxy = testCase.httpProxy + deployContext.Proxy.HttpsProxy = testCase.httpsProxy + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get( + context.TODO(), + types.NamespacedName{ + Name: devWorkspaceConfigName, + Namespace: testCase.cheCluster.Namespace, + }, + dwoc, + ) + + assert.NoError(t, err) + assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) + }) + } +} diff --git a/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go b/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go new file mode 100644 index 0000000000..a45ed429ec --- /dev/null +++ b/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go @@ -0,0 +1,598 @@ +// +// Copyright (c) 2019-2025 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 devworkspace + +import ( + "context" + "testing" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestReconcileDevWorkspaceConfigProgressTimeout(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with progressTimeout", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + StartTimeoutSeconds: ptr.To(int32(600)), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProgressTimeout: "600s", + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by adding progressTimeout", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eclipse-che", + Namespace: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + StartTimeoutSeconds: ptr.To(int32(600)), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProgressTimeout: "600s", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by overwriting progressTimeout", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + StartTimeoutSeconds: ptr.To(int32(420)), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProgressTimeout: "1h30m", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProgressTimeout: "420s", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by removing progressTimeout", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProgressTimeout: "1h30m", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + + diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, + cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DefaultStorageSize", "StorageClassName", "ProjectCloneConfig", "DeploymentStrategy"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), + }) + assert.Empty(t, diff) + }) + } +} + +func TestReconcileDevWorkspaceConfigPodSchedulerName(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with podSchedulerName", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PodSchedulerName: "test-scheduler", + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + SchedulerName: "test-scheduler", + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when PodSchedulerName is added", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PodSchedulerName: "test-scheduler", + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + SchedulerName: "test-scheduler", + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when PodSchedulerName is changed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PodSchedulerName: "test-scheduler", + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + SchedulerName: "previous-scheduler", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + SchedulerName: "test-scheduler", + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when PodSchedulerName is removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PodSchedulerName: "", + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + SchedulerName: "previous-scheduler", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DeploymentStrategy: "Recreate", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.SchedulerName, dwoc.Config.Workspace.SchedulerName) + }) + } +} + +func TestReconcileDevWorkspaceConfigRuntimeClassName(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with RuntimeClassName", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + RuntimeClassName: ptr.To("test-runtime-class"), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + RuntimeClassName: ptr.To("test-runtime-class"), + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when RuntimeClassName is added", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + RuntimeClassName: ptr.To("test-runtime-class"), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + RuntimeClassName: ptr.To("test-runtime-class"), + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when RuntimeClassName is changed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + RuntimeClassName: ptr.To("test-runtime-class"), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + RuntimeClassName: ptr.To("previous-runtime-class"), + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + RuntimeClassName: ptr.To("test-runtime-class"), + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when RuntimeClassName is removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + RuntimeClassName: nil, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + RuntimeClassName: ptr.To("previous-runtime-class"), + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.SchedulerName, dwoc.Config.Workspace.SchedulerName) + }) + } +} + +func TestReconcileDevWorkspaceConfigDeploymentStrategy(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with DeploymentStrategy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DeploymentStrategy: "Recreate", + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when DeploymentStrategy is added", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DeploymentStrategy: "Recreate", + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when DeploymentStrategy is changed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DeploymentStrategy: "RollingUpdate", + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DeploymentStrategy: "Recreate", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DeploymentStrategy: "RollingUpdate", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.DeploymentStrategy, dwoc.Config.Workspace.DeploymentStrategy) + }) + } +} diff --git a/pkg/deploy/devworkspace/dev_workspace_config_security_test.go b/pkg/deploy/devworkspace/dev_workspace_config_security_test.go new file mode 100644 index 0000000000..0f110bb4b3 --- /dev/null +++ b/pkg/deploy/devworkspace/dev_workspace_config_security_test.go @@ -0,0 +1,742 @@ +// +// Copyright (c) 2019-2025 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 devworkspace + +import ( + "context" + "fmt" + "sort" + "testing" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestReconcileDevWorkspaceConfigForContainerCapabilities(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var unmasked = corev1.UnmaskedProcMount + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig without Container Security Context if all capabilities disabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(true), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with Container Security Context if container build enabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(false), + DisableContainerRunCapabilities: ptr.To(true), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SETGID", + "SETUID", + }, + }, + AllowPrivilegeEscalation: ptr.To(true), + }, + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with Container Security Context if container run enabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + WorkspacesPodAnnotations: map[string]string{ + "annotation_1": "value_1", + "annotation_2": "value_1", + }, + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(false), + ContainerRunConfiguration: &chev2.ContainerRunConfiguration{ + OpenShiftSecurityContextConstraint: "container-run", + WorkspacesPodAnnotations: map[string]string{ + "annotation_1": "value_2", + "annotation_3": "value_2", + }, + ContainerSecurityContext: &corev1.SecurityContext{ + ProcMount: &unmasked, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SETUID", "SETGID"}, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + HostUsers: ptr.To(false), + PodAnnotations: map[string]string{ + "annotation_1": "value_2", + "annotation_2": "value_1", + "annotation_3": "value_2", + }, + ContainerSecurityContext: &corev1.SecurityContext{ + ProcMount: &unmasked, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SETUID", "SETGID"}, + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by adding Container Security Context", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(false), + DisableContainerRunCapabilities: ptr.To(true), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SETGID", + "SETUID", + }, + }, + AllowPrivilegeEscalation: ptr.To(true), + }, + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by removing Container Security Context", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(true), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SETGID", + "SETUID", + }, + }, + AllowPrivilegeEscalation: ptr.To(true), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with Container Security Context if all capabilities enabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + WorkspacesPodAnnotations: map[string]string{ + "annotation_1": "value_1", + "annotation_2": "value_1", + }, + DisableContainerBuildCapabilities: ptr.To(false), + DisableContainerRunCapabilities: ptr.To(false), + ContainerRunConfiguration: &chev2.ContainerRunConfiguration{ + OpenShiftSecurityContextConstraint: "container-run", + WorkspacesPodAnnotations: map[string]string{ + "annotation_1": "value_2", + "annotation_3": "value_2", + }, + ContainerSecurityContext: &corev1.SecurityContext{ + ProcMount: &unmasked, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SETUID", "SETGID"}, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + HostUsers: ptr.To(false), + PodAnnotations: map[string]string{ + "annotation_1": "value_2", + "annotation_2": "value_1", + "annotation_3": "value_2", + }, + ContainerSecurityContext: &corev1.SecurityContext{ + ProcMount: &unmasked, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SETUID", "SETGID"}, + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + + diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, + cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "ProjectCloneConfig", "DeploymentStrategy", "DefaultStorageSize", "StorageClassName"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), + }) + assert.Empty(t, diff) + }) + } +} + +func TestReconcileDevWorkspaceContainerSecurityContext(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with Container security context", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + // We disable container build capabilities so that it does not override the container security context we configured + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(true), + Security: chev2.WorkspaceSecurityConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_TIME", + "SETGID", + "SETUID", + }, + Drop: []corev1.Capability{ + "CHOWN", + "KILL", + }, + }, + AllowPrivilegeEscalation: ptr.To(false), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_TIME", + "SETGID", + "SETUID", + }, + Drop: []corev1.Capability{ + "CHOWN", + "KILL", + }, + }, + AllowPrivilegeEscalation: ptr.To(false), + }, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig when Container security context is added", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(true), + Security: chev2.WorkspaceSecurityConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_TIME", + "SETGID", + "SETUID", + }, + Drop: []corev1.Capability{ + "CHOWN", + "KILL", + }, + }, + AllowPrivilegeEscalation: ptr.To(false), + }}, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_TIME", + "SETGID", + "SETUID", + }, + Drop: []corev1.Capability{ + "CHOWN", + "KILL", + }, + }, + AllowPrivilegeEscalation: ptr.To(false), + }, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig when Container security is changed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(true), + Security: chev2.WorkspaceSecurityConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_TIME", + "SETGID", + "SETUID", + }, + Drop: []corev1.Capability{ + "CHOWN", + "KILL", + }, + }, + AllowPrivilegeEscalation: ptr.To(false), + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "KILL", + }, + Drop: []corev1.Capability{ + "SYS_TIME", + }, + }, + AllowPrivilegeEscalation: ptr.To(true), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_TIME", + "SETGID", + "SETUID", + }, + Drop: []corev1.Capability{ + "CHOWN", + "KILL", + }, + }, + AllowPrivilegeEscalation: ptr.To(false), + }, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig when Container security is removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DisableContainerRunCapabilities: ptr.To(true), + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerSecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "KILL", + }, + Drop: []corev1.Capability{ + "SYS_TIME", + }, + }, + AllowPrivilegeEscalation: ptr.To(true), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + + sortCapabilities := func(capabilities []corev1.Capability) func(i, j int) bool { + return func(i, j int) bool { + return capabilities[i] > capabilities[j] + } + } + expectedContainerSecurityContext := testCase.expectedOperatorConfig.Workspace.ContainerSecurityContext + actualContainerSecurityContext := dwoc.Config.Workspace.ContainerSecurityContext + if expectedContainerSecurityContext != nil { + sort.Slice(expectedContainerSecurityContext.Capabilities.Add, sortCapabilities(expectedContainerSecurityContext.Capabilities.Add)) + sort.Slice(expectedContainerSecurityContext.Capabilities.Drop, sortCapabilities(expectedContainerSecurityContext.Capabilities.Drop)) + } + if actualContainerSecurityContext != nil { + sort.Slice(actualContainerSecurityContext.Capabilities.Add, sortCapabilities(actualContainerSecurityContext.Capabilities.Add)) + sort.Slice(actualContainerSecurityContext.Capabilities.Drop, sortCapabilities(actualContainerSecurityContext.Capabilities.Drop)) + + } + + assert.Equal(t, expectedContainerSecurityContext, actualContainerSecurityContext, + fmt.Sprintf("Did not get expected ContainerSecurityContext.\nDiff:%s", cmp.Diff(expectedContainerSecurityContext, actualContainerSecurityContext))) + }) + } +} + +func TestReconcileDevWorkspacePodSecurityContext(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + configuredPodSecurityContext := &corev1.PodSecurityContext{ + RunAsUser: ptr.To(int64(0)), + RunAsGroup: ptr.To(int64(0)), + RunAsNonRoot: ptr.To(false), + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with Pod security context", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + Security: chev2.WorkspaceSecurityConfig{ + PodSecurityContext: configuredPodSecurityContext, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodSecurityContext: configuredPodSecurityContext, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig with Pod security context", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + Security: chev2.WorkspaceSecurityConfig{ + PodSecurityContext: configuredPodSecurityContext, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodSecurityContext: configuredPodSecurityContext, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig when Pod security context is changed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + Security: chev2.WorkspaceSecurityConfig{ + PodSecurityContext: configuredPodSecurityContext, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodSecurityContext: &corev1.PodSecurityContext{ + RunAsUser: ptr.To(int64(1000)), + RunAsGroup: ptr.To(int64(10001)), + RunAsNonRoot: ptr.To(true), + SupplementalGroups: []int64{ + 5, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodSecurityContext: configuredPodSecurityContext, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig when Pod security context is removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{}, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodSecurityContext: &corev1.PodSecurityContext{ + RunAsUser: ptr.To(int64(1000)), + RunAsGroup: ptr.To(int64(10001)), + RunAsNonRoot: ptr.To(true), + SupplementalGroups: []int64{ + 5, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.PodSecurityContext, dwoc.Config.Workspace.PodSecurityContext, + fmt.Sprintf("Did not get expected PodSecurityContext.\nDiff:%s", cmp.Diff(testCase.expectedOperatorConfig.Workspace.PodSecurityContext, dwoc.Config.Workspace.PodSecurityContext))) + }) + } +} diff --git a/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go b/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go new file mode 100644 index 0000000000..0d8587478f --- /dev/null +++ b/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go @@ -0,0 +1,394 @@ +// +// Copyright (c) 2019-2025 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 devworkspace + +import ( + "context" + "testing" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestReconcileServiceAccountConfig(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Case #1", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ServiceAccount: "service-account", + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountName: "service-account", + DisableCreation: ptr.To(false), + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Case #2", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DefaultNamespace: chev2.DefaultNamespace{ + AutoProvision: ptr.To(false), + }, + ServiceAccount: "service-account", + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountName: "service-account", + DisableCreation: ptr.To(true), + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Case #3", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{}, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + DisableCreation: ptr.To(false), + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Case #4", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DefaultNamespace: chev2.DefaultNamespace{ + AutoProvision: ptr.To(false), + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + DisableCreation: ptr.To(false), + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ServiceAccount, dwoc.Config.Workspace.ServiceAccount) + }) + } +} + +func TestReconcileDevWorkspaceConfigServiceAccountTokens(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with ServiceAccountTokens", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "test-token-1", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + { + Name: "test-token-2", + MountPath: "/var/run/secrets/tokens", + Audience: "kubernetes", + ExpirationSeconds: 180, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "test-token-1", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + { + Name: "test-token-2", + MountPath: "/var/run/secrets/tokens", + Audience: "kubernetes", + ExpirationSeconds: 180, + }, + }}, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when ServiceAccountTokens are added", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "test-token", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + { + Name: "test-token-2", + MountPath: "/var/run/secrets/tokens", + Audience: "kubernetes", + ExpirationSeconds: 180, + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "test-token", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + { + Name: "test-token-2", + MountPath: "/var/run/secrets/tokens", + Audience: "kubernetes", + ExpirationSeconds: 180, + }, + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when ServiceAccountTokens are changed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "new-token", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "old-token", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 60, + }, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "new-token", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig when ServiceAccountTokens are removed", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{}, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ + ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ + { + Name: "test-token", + MountPath: "/var/run/secrets/tokens", + Audience: "openshift", + ExpirationSeconds: 3600, + }, + { + Name: "test-token-2", + MountPath: "/var/run/secrets/tokens", + Audience: "kubernetes", + ExpirationSeconds: 180, + }, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{}, + DeploymentStrategy: "Recreate", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ServiceAccount.ServiceAccountTokens, dwoc.Config.Workspace.ServiceAccount.ServiceAccountTokens) + }) + } +} diff --git a/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go b/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go new file mode 100644 index 0000000000..d075e85e10 --- /dev/null +++ b/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go @@ -0,0 +1,749 @@ +// +// Copyright (c) 2019-2025 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 devworkspace + +import ( + "context" + "regexp" + "testing" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/constants" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestReconcileDevWorkspaceConfigStorage(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + type errorTestCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedErrorMessage string + } + + var quantity15Gi = resource.MustParse("15Gi") + var quantity10Gi = resource.MustParse("10Gi") + + var expectedErrorTestCases = []errorTestCase{ + { + name: "Invalid claim size string", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "invalid-ClaimSize", + }, + }, + }, + }, + }, + expectedErrorMessage: "quantities must match the regular expression", + }, + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{Workspace: &controllerv1alpha1.WorkspaceConfig{DeploymentStrategy: "Recreate"}}, + }, + { + name: "Create DevWorkspaceOperatorConfig with ephemeral storage strategy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.EphemeralPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "10Gi", + }, + PerWorkspaceStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "10Gi", + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{Workspace: &controllerv1alpha1.WorkspaceConfig{DeploymentStrategy: "Recreate"}}, + }, + { + name: "Create DevWorkspaceOperatorConfig with StorageClassName only", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("test-storage"), + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with non empty StorageAccessMode", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageAccessMode: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteMany, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageAccessMode: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteMany, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Not setting PerUserStrategyPvcConfig should reset DevWorkspaceConfig to default StorageAccessMode", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with nil StorageAccessMode", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageAccessMode: nil, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageAccessMode: nil, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with non empty default container resources", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DefaultContainerResources: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DefaultContainerResources: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with nil default container resources", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + DefaultContainerResources: nil, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + DefaultContainerResources: nil, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with non empty container resource caps", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with nil container resource caps", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + ContainerResourceCaps: nil, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: nil, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by adding container resource caps", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: nil, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by changing container resource caps", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("4Gi"), + corev1.ResourceCPU: resource.MustParse("4000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("4Gi"), + corev1.ResourceCPU: resource.MustParse("4000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig by removing container resource caps", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + ContainerResourceCaps: nil, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + corev1.ResourceCPU: resource.MustParse("2000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ContainerResourceCaps: nil, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with per-user storage strategy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "15Gi", + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("test-storage"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + Common: &quantity15Gi, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig with per-workspace storage strategy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerWorkspacePVCStorageStrategy, + PerWorkspaceStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "15Gi", + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("test-storage"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + PerWorkspace: &quantity15Gi, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update DevWorkspaceOperatorConfig with per-workspace storage strategy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerWorkspacePVCStorageStrategy, + PerWorkspaceStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "15Gi", + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("default-storage-class"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + PerWorkspace: &quantity10Gi, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("test-storage"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + PerWorkspace: &quantity15Gi, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update DevWorkspaceOperatorConfig with per-user storage strategy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "15Gi", + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("default-storage-class"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + Common: &quantity10Gi, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("test-storage"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + Common: &quantity15Gi, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + { + name: "Update populated DevWorkspaceOperatorConfig with storage class name, storage strategy and storage size", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + DisableContainerBuildCapabilities: ptr.To(true), + Storage: chev2.WorkspaceStorage{ + PvcStrategy: constants.PerUserPVCStorageStrategy, + PerUserStrategyPvcConfig: &chev2.PVC{ + StorageClass: "test-storage", + ClaimSize: "15Gi", + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "routing-class", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Routing: &controllerv1alpha1.RoutingConfig{ + DefaultRoutingClass: "routing-class", + }, + Workspace: &controllerv1alpha1.WorkspaceConfig{ + StorageClassName: ptr.To("test-storage"), + DefaultStorageSize: &controllerv1alpha1.StorageSizes{ + Common: &quantity15Gi, + }, + DeploymentStrategy: "Recreate", + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + + diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), + }) + assert.Empty(t, diff) + }) + } + + for _, testCase := range expectedErrorTestCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + _, _, err := devWorkspaceConfigReconciler.Reconcile(deployContext) + assert.Error(t, err) + assert.Regexp(t, regexp.MustCompile(testCase.expectedErrorMessage), err.Error(), "error message must match") + }) + } +} diff --git a/pkg/deploy/devworkspace/dev_workspace_config_test.go b/pkg/deploy/devworkspace/dev_workspace_config_test.go deleted file mode 100644 index 08506833b5..0000000000 --- a/pkg/deploy/devworkspace/dev_workspace_config_test.go +++ /dev/null @@ -1,3780 +0,0 @@ -// -// Copyright (c) 2019-2025 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 devworkspace - -import ( - "context" - "fmt" - "regexp" - "sort" - "testing" - - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" - - controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" - chev2 "github.com/eclipse-che/che-operator/api/v2" - "github.com/eclipse-che/che-operator/pkg/common/constants" - "github.com/eclipse-che/che-operator/pkg/common/test" - "github.com/eclipse-che/che-operator/pkg/deploy/tls" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -func TestReconcileDevWorkspaceConfigStorage(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - type errorTestCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedErrorMessage string - } - - var quantity15Gi = resource.MustParse("15Gi") - var quantity10Gi = resource.MustParse("10Gi") - - var expectedErrorTestCases = []errorTestCase{ - { - name: "Invalid claim size string", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "invalid-ClaimSize", - }, - }, - }, - }, - }, - expectedErrorMessage: "quantities must match the regular expression", - }, - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{Workspace: &controllerv1alpha1.WorkspaceConfig{DeploymentStrategy: "Recreate"}}, - }, - { - name: "Create DevWorkspaceOperatorConfig with ephemeral storage strategy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.EphemeralPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "10Gi", - }, - PerWorkspaceStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "10Gi", - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{Workspace: &controllerv1alpha1.WorkspaceConfig{DeploymentStrategy: "Recreate"}}, - }, - { - name: "Create DevWorkspaceOperatorConfig with StorageClassName only", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("test-storage"), - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with non empty StorageAccessMode", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageAccessMode: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteMany, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageAccessMode: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteMany, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Not setting PerUserStrategyPvcConfig should reset DevWorkspaceConfig to default StorageAccessMode", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with nil StorageAccessMode", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageAccessMode: nil, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageAccessMode: nil, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with non empty default container resources", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DefaultContainerResources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("128Mi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("64Mi"), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DefaultContainerResources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("128Mi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("64Mi"), - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with nil default container resources", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DefaultContainerResources: nil, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DefaultContainerResources: nil, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with non empty container resource caps", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1Gi"), - corev1.ResourceCPU: resource.MustParse("1000m"), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1Gi"), - corev1.ResourceCPU: resource.MustParse("1000m"), - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with nil container resource caps", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - ContainerResourceCaps: nil, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: nil, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by adding container resource caps", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1Gi"), - corev1.ResourceCPU: resource.MustParse("1000m"), - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: nil, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1Gi"), - corev1.ResourceCPU: resource.MustParse("1000m"), - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by changing container resource caps", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("4Gi"), - corev1.ResourceCPU: resource.MustParse("4000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1Gi"), - corev1.ResourceCPU: resource.MustParse("1000m"), - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("4Gi"), - corev1.ResourceCPU: resource.MustParse("4000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by removing container resource caps", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - ContainerResourceCaps: nil, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - corev1.ResourceCPU: resource.MustParse("2000m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1Gi"), - corev1.ResourceCPU: resource.MustParse("1000m"), - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerResourceCaps: nil, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with per-user storage strategy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "15Gi", - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("test-storage"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - Common: &quantity15Gi, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with per-workspace storage strategy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerWorkspacePVCStorageStrategy, - PerWorkspaceStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "15Gi", - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("test-storage"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - PerWorkspace: &quantity15Gi, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update DevWorkspaceOperatorConfig with per-workspace storage strategy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerWorkspacePVCStorageStrategy, - PerWorkspaceStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "15Gi", - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("default-storage-class"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - PerWorkspace: &quantity10Gi, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("test-storage"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - PerWorkspace: &quantity15Gi, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update DevWorkspaceOperatorConfig with per-user storage strategy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "15Gi", - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("default-storage-class"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - Common: &quantity10Gi, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("test-storage"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - Common: &quantity15Gi, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update populated DevWorkspaceOperatorConfig with storage class name, storage strategy and storage size", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - Storage: chev2.WorkspaceStorage{ - PvcStrategy: constants.PerUserPVCStorageStrategy, - PerUserStrategyPvcConfig: &chev2.PVC{ - StorageClass: "test-storage", - ClaimSize: "15Gi", - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "routing-class", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "routing-class", - }, - Workspace: &controllerv1alpha1.WorkspaceConfig{ - StorageClassName: ptr.To("test-storage"), - DefaultStorageSize: &controllerv1alpha1.StorageSizes{ - Common: &quantity15Gi, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - - diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ - cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount"), - cmpopts.IgnoreFields(controllerv1alpha1.OperatorConfiguration{}, "Routing"), - }) - assert.Empty(t, diff) - }) - } - - for _, testCase := range expectedErrorTestCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - _, _, err := devWorkspaceConfigReconciler.Reconcile(deployContext) - assert.Error(t, err) - assert.Regexp(t, regexp.MustCompile(testCase.expectedErrorMessage), err.Error(), "error message must match") - }) - } -} - -func TestReconcileDevWorkspaceConfigForContainerCapabilities(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var unmasked = corev1.UnmaskedProcMount - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig without Container Security Context if all capabilities disabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(true), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with Container Security Context if container build enabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(false), - DisableContainerRunCapabilities: ptr.To(true), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SETGID", - "SETUID", - }, - }, - AllowPrivilegeEscalation: ptr.To(true), - }, - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with Container Security Context if container run enabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - WorkspacesPodAnnotations: map[string]string{ - "annotation_1": "value_1", - "annotation_2": "value_1", - }, - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(false), - ContainerRunConfiguration: &chev2.ContainerRunConfiguration{ - OpenShiftSecurityContextConstraint: "container-run", - WorkspacesPodAnnotations: map[string]string{ - "annotation_1": "value_2", - "annotation_3": "value_2", - }, - ContainerSecurityContext: &corev1.SecurityContext{ - ProcMount: &unmasked, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"SETUID", "SETGID"}, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - HostUsers: ptr.To(false), - PodAnnotations: map[string]string{ - "annotation_1": "value_2", - "annotation_2": "value_1", - "annotation_3": "value_2", - }, - ContainerSecurityContext: &corev1.SecurityContext{ - ProcMount: &unmasked, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"SETUID", "SETGID"}, - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by adding Container Security Context", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(false), - DisableContainerRunCapabilities: ptr.To(true), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SETGID", - "SETUID", - }, - }, - AllowPrivilegeEscalation: ptr.To(true), - }, - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by removing Container Security Context", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(true), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SETGID", - "SETUID", - }, - }, - AllowPrivilegeEscalation: ptr.To(true), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig with Container Security Context if all capabilities enabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - WorkspacesPodAnnotations: map[string]string{ - "annotation_1": "value_1", - "annotation_2": "value_1", - }, - DisableContainerBuildCapabilities: ptr.To(false), - DisableContainerRunCapabilities: ptr.To(false), - ContainerRunConfiguration: &chev2.ContainerRunConfiguration{ - OpenShiftSecurityContextConstraint: "container-run", - WorkspacesPodAnnotations: map[string]string{ - "annotation_1": "value_2", - "annotation_3": "value_2", - }, - ContainerSecurityContext: &corev1.SecurityContext{ - ProcMount: &unmasked, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"SETUID", "SETGID"}, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - HostUsers: ptr.To(false), - PodAnnotations: map[string]string{ - "annotation_1": "value_2", - "annotation_2": "value_1", - "annotation_3": "value_2", - }, - ContainerSecurityContext: &corev1.SecurityContext{ - ProcMount: &unmasked, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"SETUID", "SETGID"}, - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - - diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, - cmp.Options{ - cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "ProjectCloneConfig", "DeploymentStrategy", "DefaultStorageSize", "StorageClassName"), - cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), - }) - assert.Empty(t, diff) - }) - } -} - -func TestReconcileDevWorkspaceConfigProgressTimeout(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with progressTimeout", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - StartTimeoutSeconds: ptr.To(int32(600)), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ProgressTimeout: "600s", - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by adding progressTimeout", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "eclipse-che", - Namespace: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - StartTimeoutSeconds: ptr.To(int32(600)), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ProgressTimeout: "600s", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by overwriting progressTimeout", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - StartTimeoutSeconds: ptr.To(int32(420)), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ProgressTimeout: "1h30m", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ProgressTimeout: "420s", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig by removing progressTimeout", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ProgressTimeout: "1h30m", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - - diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, - cmp.Options{ - cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DefaultStorageSize", "StorageClassName", "ProjectCloneConfig", "DeploymentStrategy"), - cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), - }) - assert.Empty(t, diff) - }) - } -} - -func TestReconcileServiceAccountConfig(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Case #1", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ServiceAccount: "service-account", - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountName: "service-account", - DisableCreation: ptr.To(false), - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Case #2", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DefaultNamespace: chev2.DefaultNamespace{ - AutoProvision: ptr.To(false), - }, - ServiceAccount: "service-account", - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountName: "service-account", - DisableCreation: ptr.To(true), - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Case #3", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{}, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - DisableCreation: ptr.To(false), - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Case #4", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DefaultNamespace: chev2.DefaultNamespace{ - AutoProvision: ptr.To(false), - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - DisableCreation: ptr.To(false), - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ServiceAccount, dwoc.Config.Workspace.ServiceAccount) - }) - } -} - -func TestReconcileDevWorkspaceConfigPodSchedulerName(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with podSchedulerName", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PodSchedulerName: "test-scheduler", - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - SchedulerName: "test-scheduler", - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when PodSchedulerName is added", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PodSchedulerName: "test-scheduler", - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - SchedulerName: "test-scheduler", - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when PodSchedulerName is changed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PodSchedulerName: "test-scheduler", - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - SchedulerName: "previous-scheduler", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - SchedulerName: "test-scheduler", - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when PodSchedulerName is removed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PodSchedulerName: "", - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - SchedulerName: "previous-scheduler", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DeploymentStrategy: "Recreate", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.SchedulerName, dwoc.Config.Workspace.SchedulerName) - }) - } -} - -func TestReconcileDevWorkspaceConfigRuntimeClassName(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with RuntimeClassName", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - RuntimeClassName: ptr.To("test-runtime-class"), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - RuntimeClassName: ptr.To("test-runtime-class"), - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when RuntimeClassName is added", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - RuntimeClassName: ptr.To("test-runtime-class"), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - RuntimeClassName: ptr.To("test-runtime-class"), - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when RuntimeClassName is changed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - RuntimeClassName: ptr.To("test-runtime-class"), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - RuntimeClassName: ptr.To("previous-runtime-class"), - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - RuntimeClassName: ptr.To("test-runtime-class"), - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when RuntimeClassName is removed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - RuntimeClassName: nil, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - RuntimeClassName: ptr.To("previous-runtime-class"), - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.SchedulerName, dwoc.Config.Workspace.SchedulerName) - }) - } -} - -func TestReconcileDevWorkspaceConfigServiceAccountTokens(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with ServiceAccountTokens", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "test-token-1", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - { - Name: "test-token-2", - MountPath: "/var/run/secrets/tokens", - Audience: "kubernetes", - ExpirationSeconds: 180, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "test-token-1", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - { - Name: "test-token-2", - MountPath: "/var/run/secrets/tokens", - Audience: "kubernetes", - ExpirationSeconds: 180, - }, - }}, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when ServiceAccountTokens are added", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "test-token", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - { - Name: "test-token-2", - MountPath: "/var/run/secrets/tokens", - Audience: "kubernetes", - ExpirationSeconds: 180, - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "test-token", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - { - Name: "test-token-2", - MountPath: "/var/run/secrets/tokens", - Audience: "kubernetes", - ExpirationSeconds: 180, - }, - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when ServiceAccountTokens are changed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "new-token", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "old-token", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 60, - }, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "new-token", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - }, - }, - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when ServiceAccountTokens are removed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{}, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{ - ServiceAccountTokens: []controllerv1alpha1.ServiceAccountToken{ - { - Name: "test-token", - MountPath: "/var/run/secrets/tokens", - Audience: "openshift", - ExpirationSeconds: 3600, - }, - { - Name: "test-token-2", - MountPath: "/var/run/secrets/tokens", - Audience: "kubernetes", - ExpirationSeconds: 180, - }, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{}, - DeploymentStrategy: "Recreate", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ServiceAccount.ServiceAccountTokens, dwoc.Config.Workspace.ServiceAccount.ServiceAccountTokens) - }) - } -} - -func TestReconcileDevWorkspaceConfigDeploymentStrategy(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with DeploymentStrategy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DeploymentStrategy: "Recreate", - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when DeploymentStrategy is added", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DeploymentStrategy: "Recreate", - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DeploymentStrategy: "Recreate", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig when DeploymentStrategy is changed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DeploymentStrategy: "RollingUpdate", - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DeploymentStrategy: "Recreate", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - DeploymentStrategy: "RollingUpdate", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.DeploymentStrategy, dwoc.Config.Workspace.DeploymentStrategy) - }) - } -} - -func TestReconcileDevWorkspaceProjectCloneConfig(t *testing.T) { - const testNamespace = "eclipse-che" - testMemLimit := resource.MustParse("2Gi") - testCpuLimit := resource.MustParse("1000m") - testMemRequest := resource.MustParse("1Gi") - testCpuRequest := resource.MustParse("500m") - - type testCase struct { - name string - cheProjectCloneConfig *chev2.Container - expectedDevWorkspaceConfig *controllerv1alpha1.ProjectCloneConfig - existingDevWorkspaceConfig *controllerv1alpha1.ProjectCloneConfig - } - - tests := []testCase{ - { - name: "Syncs Che project clone config to DevWorkspaceOperatorConfig", - cheProjectCloneConfig: &chev2.Container{ - Name: "project-clone", - Image: "test-image", - ImagePullPolicy: "IfNotPresent", - Env: []corev1.EnvVar{ - {Name: "test-env-1", Value: "test-val-1"}, - {Name: "test-env-2", Value: "test-val-2"}, - }, - Resources: &chev2.ResourceRequirements{ - Limits: &chev2.ResourceList{ - Memory: &testMemLimit, - Cpu: &testCpuLimit, - }, - Requests: &chev2.ResourceList{ - Memory: &testMemRequest, - Cpu: &testCpuRequest, - }, - }, - }, - expectedDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ - Image: "test-image", - ImagePullPolicy: "IfNotPresent", - Env: []corev1.EnvVar{ - {Name: "test-env-1", Value: "test-val-1"}, - {Name: "test-env-2", Value: "test-val-2"}, - }, - Resources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: testMemLimit, - corev1.ResourceCPU: testCpuLimit, - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: testMemRequest, - corev1.ResourceCPU: testCpuRequest, - }, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig with new Che project clone config", - cheProjectCloneConfig: &chev2.Container{ - Name: "project-clone", - Image: "test-image", - ImagePullPolicy: "IfNotPresent", - Env: []corev1.EnvVar{ - {Name: "test-env-1", Value: "test-val-1"}, - {Name: "test-env-2", Value: "test-val-2"}, - }, - Resources: &chev2.ResourceRequirements{ - Limits: &chev2.ResourceList{ - Memory: &testMemLimit, - Cpu: &testCpuLimit, - }, - Requests: &chev2.ResourceList{ - Memory: &testMemRequest, - Cpu: &testCpuRequest, - }, - }, - }, - expectedDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ - Image: "test-image", - ImagePullPolicy: "IfNotPresent", - Env: []corev1.EnvVar{ - {Name: "test-env-1", Value: "test-val-1"}, - {Name: "test-env-2", Value: "test-val-2"}, - }, - Resources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: testMemLimit, - corev1.ResourceCPU: testCpuLimit, - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: testMemRequest, - corev1.ResourceCPU: testCpuRequest, - }, - }, - }, - existingDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ - Image: "other image", - ImagePullPolicy: "Always", - Env: []corev1.EnvVar{ - {Name: "other-env", Value: "other-val"}, - }, - Resources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1234Mi"), - corev1.ResourceCPU: resource.MustParse("1234m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1111Mi"), - corev1.ResourceCPU: resource.MustParse("1111m"), - }, - }, - }, - }, - { - name: "Removes fields from existing config when removed from CheCluster", - cheProjectCloneConfig: &chev2.Container{ - Name: "", - Image: "", - ImagePullPolicy: "", - Env: nil, - Resources: nil, - }, - expectedDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ - Image: "", - ImagePullPolicy: "", - Env: nil, - Resources: nil, - }, - existingDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ - Image: "other image", - ImagePullPolicy: "Always", - Env: []corev1.EnvVar{ - {Name: "other-env", Value: "other-val"}, - }, - Resources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1234Mi"), - corev1.ResourceCPU: resource.MustParse("1234m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("1111Mi"), - corev1.ResourceCPU: resource.MustParse("1111m"), - }, - }, - }, - }, - } - - for _, testCase := range tests { - t.Run(testCase.name, func(t *testing.T) { - cheCluster := &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ProjectCloneContainer: testCase.cheProjectCloneConfig, - }, - }, - } - existingDWOC := &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: testNamespace, - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ProjectCloneConfig: testCase.existingDevWorkspaceConfig, - }, - }, - } - runtimeDWOC := client.Object(existingDWOC) - - deployContext := test.NewCtxBuilder().WithCheCluster(cheCluster).WithObjects(runtimeDWOC).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testNamespace}, dwoc) - assert.NoError(t, err) - - diff := cmp.Diff(testCase.expectedDevWorkspaceConfig, dwoc.Config.Workspace.ProjectCloneConfig) - assert.Empty(t, diff) - }) - } - -} - -func TestReconcileDevWorkspaceConfigPersistUserHome(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig when PersistUserHome is enabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PersistUserHome: &chev2.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig that does not have PersistUserHome config defined, when PersistUserHome is enabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PersistUserHome: &chev2.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - { - name: "Set DevWorkspaceOperatorConfig PersistUserHome enabled when PersistUserHome is enabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PersistUserHome: &chev2.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(false), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - { - name: "Set DevWorkspaceOperatorConfig PersistUserHome disableInitContainer to true", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PersistUserHome: &chev2.PersistentHomeConfig{ - Enabled: ptr.To(true), - DisableInitContainer: ptr.To(true), - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(false), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - DisableInitContainer: ptr.To(true), - }, - }, - }, - }, - { - name: "Set DevWorkspaceOperatorConfig PersistUserHome disableInitContainer to false when initially true", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PersistUserHome: &chev2.PersistentHomeConfig{ - Enabled: ptr.To(true), - DisableInitContainer: ptr.To(false), - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - DisableInitContainer: ptr.To(true), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - DisableInitContainer: ptr.To(false), - }, - }, - }, - }, - { - name: "Set DevWorkspaceOperatorConfig PersistUserHome disabled when PersistUserHome is disabled", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - PersistUserHome: &chev2.PersistentHomeConfig{ - Enabled: ptr.To(false), - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(false), - }, - }, - }, - }, - { - name: "Remove PersistUserHome config from existing DevWorkspaceOperatorConfig when PersistUserHome is removed from Che Cluster", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{}, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ - Enabled: ptr.To(true), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ - cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DeploymentStrategy", "ContainerSecurityContext"), - cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), - }) - assert.Empty(t, diff) - }) - } -} - -func TestReconcileDevWorkspaceContainerSecurityContext(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with Container security context", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - // We disable container build capabilities so that it does not override the container security context we configured - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(true), - Security: chev2.WorkspaceSecurityConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SYS_TIME", - "SETGID", - "SETUID", - }, - Drop: []corev1.Capability{ - "CHOWN", - "KILL", - }, - }, - AllowPrivilegeEscalation: ptr.To(false), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SYS_TIME", - "SETGID", - "SETUID", - }, - Drop: []corev1.Capability{ - "CHOWN", - "KILL", - }, - }, - AllowPrivilegeEscalation: ptr.To(false), - }, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig when Container security context is added", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(true), - Security: chev2.WorkspaceSecurityConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SYS_TIME", - "SETGID", - "SETUID", - }, - Drop: []corev1.Capability{ - "CHOWN", - "KILL", - }, - }, - AllowPrivilegeEscalation: ptr.To(false), - }}, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SYS_TIME", - "SETGID", - "SETUID", - }, - Drop: []corev1.Capability{ - "CHOWN", - "KILL", - }, - }, - AllowPrivilegeEscalation: ptr.To(false), - }, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig when Container security is changed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(true), - Security: chev2.WorkspaceSecurityConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SYS_TIME", - "SETGID", - "SETUID", - }, - Drop: []corev1.Capability{ - "CHOWN", - "KILL", - }, - }, - AllowPrivilegeEscalation: ptr.To(false), - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "KILL", - }, - Drop: []corev1.Capability{ - "SYS_TIME", - }, - }, - AllowPrivilegeEscalation: ptr.To(true), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "SYS_TIME", - "SETGID", - "SETUID", - }, - Drop: []corev1.Capability{ - "CHOWN", - "KILL", - }, - }, - AllowPrivilegeEscalation: ptr.To(false), - }, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig when Container security is removed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - DisableContainerBuildCapabilities: ptr.To(true), - DisableContainerRunCapabilities: ptr.To(true), - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ContainerSecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "KILL", - }, - Drop: []corev1.Capability{ - "SYS_TIME", - }, - }, - AllowPrivilegeEscalation: ptr.To(true), - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - - sortCapabilities := func(capabilities []corev1.Capability) func(i, j int) bool { - return func(i, j int) bool { - return capabilities[i] > capabilities[j] - } - } - expectedContainerSecurityContext := testCase.expectedOperatorConfig.Workspace.ContainerSecurityContext - actualContainerSecurityContext := dwoc.Config.Workspace.ContainerSecurityContext - if expectedContainerSecurityContext != nil { - sort.Slice(expectedContainerSecurityContext.Capabilities.Add, sortCapabilities(expectedContainerSecurityContext.Capabilities.Add)) - sort.Slice(expectedContainerSecurityContext.Capabilities.Drop, sortCapabilities(expectedContainerSecurityContext.Capabilities.Drop)) - } - if actualContainerSecurityContext != nil { - sort.Slice(actualContainerSecurityContext.Capabilities.Add, sortCapabilities(actualContainerSecurityContext.Capabilities.Add)) - sort.Slice(actualContainerSecurityContext.Capabilities.Drop, sortCapabilities(actualContainerSecurityContext.Capabilities.Drop)) - - } - - assert.Equal(t, expectedContainerSecurityContext, actualContainerSecurityContext, - fmt.Sprintf("Did not get expected ContainerSecurityContext.\nDiff:%s", cmp.Diff(expectedContainerSecurityContext, actualContainerSecurityContext))) - }) - } -} - -func TestReconcileDevWorkspacePodSecurityContext(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - configuredPodSecurityContext := &corev1.PodSecurityContext{ - RunAsUser: ptr.To(int64(0)), - RunAsGroup: ptr.To(int64(0)), - RunAsNonRoot: ptr.To(false), - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with Pod security context", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - Security: chev2.WorkspaceSecurityConfig{ - PodSecurityContext: configuredPodSecurityContext, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodSecurityContext: configuredPodSecurityContext, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig with Pod security context", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - Security: chev2.WorkspaceSecurityConfig{ - PodSecurityContext: configuredPodSecurityContext, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodSecurityContext: configuredPodSecurityContext, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig when Pod security context is changed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - Security: chev2.WorkspaceSecurityConfig{ - PodSecurityContext: configuredPodSecurityContext, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodSecurityContext: &corev1.PodSecurityContext{ - RunAsUser: ptr.To(int64(1000)), - RunAsGroup: ptr.To(int64(10001)), - RunAsNonRoot: ptr.To(true), - SupplementalGroups: []int64{ - 5, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodSecurityContext: configuredPodSecurityContext, - }, - }, - }, - { - name: "Updates existing DevWorkspaceOperatorConfig when Pod security context is removed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{}, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodSecurityContext: &corev1.PodSecurityContext{ - RunAsUser: ptr.To(int64(1000)), - RunAsGroup: ptr.To(int64(10001)), - RunAsNonRoot: ptr.To(true), - SupplementalGroups: []int64{ - 5, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.PodSecurityContext, dwoc.Config.Workspace.PodSecurityContext, - fmt.Sprintf("Did not get expected PodSecurityContext.\nDiff:%s", cmp.Diff(testCase.expectedOperatorConfig.Workspace.PodSecurityContext, dwoc.Config.Workspace.PodSecurityContext))) - }) - } -} - -func TestReconcileDevWorkspaceImagePullPolicy(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Set specific pull policy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ImagePullPolicy: corev1.PullAlways, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ImagePullPolicy: string(corev1.PullAlways), - }, - }, - }, - { - name: "Clean up pull policy", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - ImagePullPolicy: "", - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ImagePullPolicy: "", - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - ImagePullPolicy: string(corev1.PullAlways), - }, - }, - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ImagePullPolicy, dwoc.Config.Workspace.ImagePullPolicy) - }) - } -} - -func TestReconcileDevWorkspaceAnnotations(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Set specific annotations", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - WorkspacesPodAnnotations: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodAnnotations: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - }, - }, - }, - { - name: "Remove annotations", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - WorkspacesPodAnnotations: nil, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodAnnotations: nil, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - PodAnnotations: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - }, - }, - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.PodAnnotations, dwoc.Config.Workspace.PodAnnotations) - }) - } -} - -func TestReconcileDevWorkspaceIgnoredUnrecoverableEvents(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - var testCases = []testCase{ - { - name: "Set events", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - IgnoredUnrecoverableEvents: []string{ - "value1", - "value2", - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - IgnoredUnrecoverableEvents: []string{ - "value1", - "value2", - }, - }, - }, - }, - { - name: "Remove events", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - IgnoredUnrecoverableEvents: nil, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - IgnoredUnrecoverableEvents: nil, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - IgnoredUnrecoverableEvents: []string{ - "value1", - "value2", - }, - }, - }, - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.IgnoredUnrecoverableEvents, dwoc.Config.Workspace.IgnoredUnrecoverableEvents) - }) - } -} - -func TestReconcileDevWorkspaceConfigForInitContainers(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration - } - - testCases := []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with InitContainers", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - InitContainers: []corev1.Container{ - { - Name: "init-container-1", - Image: "init-image:latest", - Command: []string{ - "/bin/sh", - "-c", - "echo 'Initializing workspace'", - }, - }, - { - Name: "init-container-2", - Image: "init-image-2:v1.0", - Env: []corev1.EnvVar{ - { - Name: "INIT_VAR", - Value: "init-value", - }, - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - InitContainers: []corev1.Container{ - { - Name: "init-container-1", - Image: "init-image:latest", - Command: []string{ - "/bin/sh", - "-c", - "echo 'Initializing workspace'", - }, - }, - { - Name: "init-container-2", - Image: "init-image-2:v1.0", - Env: []corev1.EnvVar{ - { - Name: "INIT_VAR", - Value: "init-value", - }, - }, - }, - }, - }, - }, - }, - { - name: "Create DevWorkspaceOperatorConfig without InitContainers", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{}, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - { - name: "Update DevWorkspaceOperatorConfig with InitContainers", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{ - InitContainers: []corev1.Container{ - { - Name: "new-init-container", - Image: "new-init:v2.0", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "config-volume", - MountPath: "/etc/config", - }, - }, - }, - }, - }, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - InitContainers: []corev1.Container{ - { - Name: "old-init-container", - Image: "old-init:v1.0", - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - InitContainers: []corev1.Container{ - { - Name: "new-init-container", - Image: "new-init:v2.0", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "config-volume", - MountPath: "/etc/config", - }, - }, - }, - }, - }, - }, - }, - { - name: "Clear InitContainers from DevWorkspaceOperatorConfig", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - Spec: chev2.CheClusterSpec{ - DevEnvironments: chev2.CheClusterDevEnvironments{}, - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{ - InitContainers: []corev1.Container{ - { - Name: "init-to-remove", - Image: "init:v1.0", - }, - }, - }, - }, - }, - }, - expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ - Workspace: &controllerv1alpha1.WorkspaceConfig{}, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) - - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.InitContainers, dwoc.Config.Workspace.InitContainers) - }) - } -} - -func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - expectedRoutingConfig *controllerv1alpha1.RoutingConfig - } - - var testCases = []testCase{ - { - name: "Create DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef when CA bundle ConfigMap exists with data", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{ - "ca-bundle.crt": "certificate-data", - }, - }, - }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - { - name: "Update existing DevWorkspaceOperatorConfig with TLSCertificateConfigmapRef when CA bundle ConfigMap exists with data", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{ - "ca-bundle.crt": "certificate-data", - }, - }, - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - }, - }, - }, - }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - { - name: "Do not set TLSCertificateConfigmapRef when CA bundle ConfigMap does not exist", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - expectedRoutingConfig: nil, - }, - { - name: "Do not set TLSCertificateConfigmapRef when CA bundle ConfigMap exists but has no data", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{}, - }, - }, - expectedRoutingConfig: nil, - }, - { - name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap is removed", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - }, - }, - expectedRoutingConfig: nil, - }, - { - name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap data is emptied", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{}, - }, - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - }, - }, - expectedRoutingConfig: nil, - }, - { - name: "Re-reconcile is stable when TLSCertificateConfigmapRef already matches", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{ - "ca-bundle.crt": "certificate-data", - }, - }, - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - }, - }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - { - name: "Clear TLSCertificateConfigmapRef while preserving other routing config", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - }, - }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get( - context.TODO(), - types.NamespacedName{ - Name: devWorkspaceConfigName, - Namespace: testCase.cheCluster.Namespace, - }, - dwoc, - ) - - assert.NoError(t, err) - assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) - }) - } -} - -func TestReconcileDevWorkspaceConfigProxyAndTLSComposition(t *testing.T) { - type testCase struct { - name string - cheCluster *chev2.CheCluster - existedObjects []client.Object - httpProxy string - httpsProxy string - expectedRoutingConfig *controllerv1alpha1.RoutingConfig - } - - var testCases = []testCase{ - { - name: "Both proxy and TLS certificate configmap ref are set on RoutingConfig", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{ - "ca-bundle.crt": "certificate-data", - }, - }, - }, - httpProxy: "http://proxy.example.com:3128", - httpsProxy: "https://proxy.example.com:3128", - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - ProxyConfig: &controllerv1alpha1.Proxy{ - HttpProxy: ptr.To(""), - HttpsProxy: ptr.To(""), - NoProxy: ptr.To(""), - }, - }, - }, - { - name: "Proxy set without TLS certificate configmap does not clobber routing", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - httpProxy: "http://proxy.example.com:3128", - httpsProxy: "https://proxy.example.com:3128", - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - ProxyConfig: &controllerv1alpha1.Proxy{ - HttpProxy: ptr.To(""), - HttpsProxy: ptr.To(""), - NoProxy: ptr.To(""), - }, - }, - }, - { - name: "TLS certificate configmap ref set without proxy does not clobber routing", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{ - "ca-bundle.crt": "certificate-data", - }, - }, - }, - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - }, - }, - { - name: "Both proxy and TLS compose with existing routing config", - cheCluster: &chev2.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - existedObjects: []client.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - Data: map[string]string{ - "ca-bundle.crt": "certificate-data", - }, - }, - &controllerv1alpha1.DevWorkspaceOperatorConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: devWorkspaceConfigName, - Namespace: "eclipse-che", - }, - TypeMeta: metav1.TypeMeta{ - Kind: "DevWorkspaceOperatorConfig", - APIVersion: controllerv1alpha1.GroupVersion.String(), - }, - Config: &controllerv1alpha1.OperatorConfiguration{ - Routing: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - }, - }, - }, - }, - httpProxy: "http://proxy.example.com:3128", - httpsProxy: "https://proxy.example.com:3128", - expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{ - DefaultRoutingClass: "che", - TLSCertificateConfigmapRef: &controllerv1alpha1.ConfigmapReference{ - Name: tls.CheMergedCABundleCertsCMName, - Namespace: "eclipse-che", - }, - ProxyConfig: &controllerv1alpha1.Proxy{ - HttpProxy: ptr.To(""), - HttpsProxy: ptr.To(""), - NoProxy: ptr.To(""), - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() - deployContext.Proxy.HttpProxy = testCase.httpProxy - deployContext.Proxy.HttpsProxy = testCase.httpsProxy - - devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() - test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) - - dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} - err := deployContext.ClusterAPI.Client.Get( - context.TODO(), - types.NamespacedName{ - Name: devWorkspaceConfigName, - Namespace: testCase.cheCluster.Namespace, - }, - dwoc, - ) - - assert.NoError(t, err) - assert.Equal(t, testCase.expectedRoutingConfig, dwoc.Config.Routing) - }) - } -} diff --git a/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go b/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go new file mode 100644 index 0000000000..86f9bdc098 --- /dev/null +++ b/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go @@ -0,0 +1,978 @@ +// +// Copyright (c) 2019-2025 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 devworkspace + +import ( + "context" + "testing" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestReconcileDevWorkspaceProjectCloneConfig(t *testing.T) { + const testNamespace = "eclipse-che" + testMemLimit := resource.MustParse("2Gi") + testCpuLimit := resource.MustParse("1000m") + testMemRequest := resource.MustParse("1Gi") + testCpuRequest := resource.MustParse("500m") + + type testCase struct { + name string + cheProjectCloneConfig *chev2.Container + expectedDevWorkspaceConfig *controllerv1alpha1.ProjectCloneConfig + existingDevWorkspaceConfig *controllerv1alpha1.ProjectCloneConfig + } + + tests := []testCase{ + { + name: "Syncs Che project clone config to DevWorkspaceOperatorConfig", + cheProjectCloneConfig: &chev2.Container{ + Name: "project-clone", + Image: "test-image", + ImagePullPolicy: "IfNotPresent", + Env: []corev1.EnvVar{ + {Name: "test-env-1", Value: "test-val-1"}, + {Name: "test-env-2", Value: "test-val-2"}, + }, + Resources: &chev2.ResourceRequirements{ + Limits: &chev2.ResourceList{ + Memory: &testMemLimit, + Cpu: &testCpuLimit, + }, + Requests: &chev2.ResourceList{ + Memory: &testMemRequest, + Cpu: &testCpuRequest, + }, + }, + }, + expectedDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ + Image: "test-image", + ImagePullPolicy: "IfNotPresent", + Env: []corev1.EnvVar{ + {Name: "test-env-1", Value: "test-val-1"}, + {Name: "test-env-2", Value: "test-val-2"}, + }, + Resources: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: testMemLimit, + corev1.ResourceCPU: testCpuLimit, + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: testMemRequest, + corev1.ResourceCPU: testCpuRequest, + }, + }, + }, + }, + { + name: "Updates existing DevWorkspaceOperatorConfig with new Che project clone config", + cheProjectCloneConfig: &chev2.Container{ + Name: "project-clone", + Image: "test-image", + ImagePullPolicy: "IfNotPresent", + Env: []corev1.EnvVar{ + {Name: "test-env-1", Value: "test-val-1"}, + {Name: "test-env-2", Value: "test-val-2"}, + }, + Resources: &chev2.ResourceRequirements{ + Limits: &chev2.ResourceList{ + Memory: &testMemLimit, + Cpu: &testCpuLimit, + }, + Requests: &chev2.ResourceList{ + Memory: &testMemRequest, + Cpu: &testCpuRequest, + }, + }, + }, + expectedDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ + Image: "test-image", + ImagePullPolicy: "IfNotPresent", + Env: []corev1.EnvVar{ + {Name: "test-env-1", Value: "test-val-1"}, + {Name: "test-env-2", Value: "test-val-2"}, + }, + Resources: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: testMemLimit, + corev1.ResourceCPU: testCpuLimit, + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: testMemRequest, + corev1.ResourceCPU: testCpuRequest, + }, + }, + }, + existingDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ + Image: "other image", + ImagePullPolicy: "Always", + Env: []corev1.EnvVar{ + {Name: "other-env", Value: "other-val"}, + }, + Resources: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1234Mi"), + corev1.ResourceCPU: resource.MustParse("1234m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1111Mi"), + corev1.ResourceCPU: resource.MustParse("1111m"), + }, + }, + }, + }, + { + name: "Removes fields from existing config when removed from CheCluster", + cheProjectCloneConfig: &chev2.Container{ + Name: "", + Image: "", + ImagePullPolicy: "", + Env: nil, + Resources: nil, + }, + expectedDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ + Image: "", + ImagePullPolicy: "", + Env: nil, + Resources: nil, + }, + existingDevWorkspaceConfig: &controllerv1alpha1.ProjectCloneConfig{ + Image: "other image", + ImagePullPolicy: "Always", + Env: []corev1.EnvVar{ + {Name: "other-env", Value: "other-val"}, + }, + Resources: &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1234Mi"), + corev1.ResourceCPU: resource.MustParse("1234m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("1111Mi"), + corev1.ResourceCPU: resource.MustParse("1111m"), + }, + }, + }, + }, + } + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + cheCluster := &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ProjectCloneContainer: testCase.cheProjectCloneConfig, + }, + }, + } + existingDWOC := &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: testNamespace, + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProjectCloneConfig: testCase.existingDevWorkspaceConfig, + }, + }, + } + runtimeDWOC := client.Object(existingDWOC) + + deployContext := test.NewCtxBuilder().WithCheCluster(cheCluster).WithObjects(runtimeDWOC).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testNamespace}, dwoc) + assert.NoError(t, err) + + diff := cmp.Diff(testCase.expectedDevWorkspaceConfig, dwoc.Config.Workspace.ProjectCloneConfig) + assert.Empty(t, diff) + }) + } + +} + +func TestReconcileDevWorkspaceConfigPersistUserHome(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Create DevWorkspaceOperatorConfig when PersistUserHome is enabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PersistUserHome: &chev2.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + { + name: "Update existing DevWorkspaceOperatorConfig that does not have PersistUserHome config defined, when PersistUserHome is enabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PersistUserHome: &chev2.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + { + name: "Set DevWorkspaceOperatorConfig PersistUserHome enabled when PersistUserHome is enabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PersistUserHome: &chev2.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(false), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + { + name: "Set DevWorkspaceOperatorConfig PersistUserHome disableInitContainer to true", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PersistUserHome: &chev2.PersistentHomeConfig{ + Enabled: ptr.To(true), + DisableInitContainer: ptr.To(true), + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(false), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + DisableInitContainer: ptr.To(true), + }, + }, + }, + }, + { + name: "Set DevWorkspaceOperatorConfig PersistUserHome disableInitContainer to false when initially true", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PersistUserHome: &chev2.PersistentHomeConfig{ + Enabled: ptr.To(true), + DisableInitContainer: ptr.To(false), + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + DisableInitContainer: ptr.To(true), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + DisableInitContainer: ptr.To(false), + }, + }, + }, + }, + { + name: "Set DevWorkspaceOperatorConfig PersistUserHome disabled when PersistUserHome is disabled", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + PersistUserHome: &chev2.PersistentHomeConfig{ + Enabled: ptr.To(false), + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(false), + }, + }, + }, + }, + { + name: "Remove PersistUserHome config from existing DevWorkspaceOperatorConfig when PersistUserHome is removed from Che Cluster", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{}, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PersistUserHome: &controllerv1alpha1.PersistentHomeConfig{ + Enabled: ptr.To(true), + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{ + cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount", "DeploymentStrategy", "ContainerSecurityContext"), + cmpopts.IgnoreFields(controllerv1alpha1.RoutingConfig{}, "TLSCertificateConfigmapRef"), + }) + assert.Empty(t, diff) + }) + } +} + +func TestReconcileDevWorkspaceImagePullPolicy(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Set specific pull policy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ImagePullPolicy: corev1.PullAlways, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ImagePullPolicy: string(corev1.PullAlways), + }, + }, + }, + { + name: "Clean up pull policy", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + ImagePullPolicy: "", + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ImagePullPolicy: "", + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ImagePullPolicy: string(corev1.PullAlways), + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ImagePullPolicy, dwoc.Config.Workspace.ImagePullPolicy) + }) + } +} + +func TestReconcileDevWorkspaceAnnotations(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Set specific annotations", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + WorkspacesPodAnnotations: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodAnnotations: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + }, + }, + { + name: "Remove annotations", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + WorkspacesPodAnnotations: nil, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodAnnotations: nil, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + PodAnnotations: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.PodAnnotations, dwoc.Config.Workspace.PodAnnotations) + }) + } +} + +func TestReconcileDevWorkspaceIgnoredUnrecoverableEvents(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + var testCases = []testCase{ + { + name: "Set events", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + IgnoredUnrecoverableEvents: []string{ + "value1", + "value2", + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + IgnoredUnrecoverableEvents: []string{ + "value1", + "value2", + }, + }, + }, + }, + { + name: "Remove events", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + IgnoredUnrecoverableEvents: nil, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + IgnoredUnrecoverableEvents: nil, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + IgnoredUnrecoverableEvents: []string{ + "value1", + "value2", + }, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.IgnoredUnrecoverableEvents, dwoc.Config.Workspace.IgnoredUnrecoverableEvents) + }) + } +} + +func TestReconcileDevWorkspaceConfigForInitContainers(t *testing.T) { + type testCase struct { + name string + cheCluster *chev2.CheCluster + existedObjects []client.Object + expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration + } + + testCases := []testCase{ + { + name: "Create DevWorkspaceOperatorConfig with InitContainers", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + InitContainers: []corev1.Container{ + { + Name: "init-container-1", + Image: "init-image:latest", + Command: []string{ + "/bin/sh", + "-c", + "echo 'Initializing workspace'", + }, + }, + { + Name: "init-container-2", + Image: "init-image-2:v1.0", + Env: []corev1.EnvVar{ + { + Name: "INIT_VAR", + Value: "init-value", + }, + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + InitContainers: []corev1.Container{ + { + Name: "init-container-1", + Image: "init-image:latest", + Command: []string{ + "/bin/sh", + "-c", + "echo 'Initializing workspace'", + }, + }, + { + Name: "init-container-2", + Image: "init-image-2:v1.0", + Env: []corev1.EnvVar{ + { + Name: "INIT_VAR", + Value: "init-value", + }, + }, + }, + }, + }, + }, + }, + { + name: "Create DevWorkspaceOperatorConfig without InitContainers", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{}, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + { + name: "Update DevWorkspaceOperatorConfig with InitContainers", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{ + InitContainers: []corev1.Container{ + { + Name: "new-init-container", + Image: "new-init:v2.0", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "config-volume", + MountPath: "/etc/config", + }, + }, + }, + }, + }, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + InitContainers: []corev1.Container{ + { + Name: "old-init-container", + Image: "old-init:v1.0", + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + InitContainers: []corev1.Container{ + { + Name: "new-init-container", + Image: "new-init:v2.0", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "config-volume", + MountPath: "/etc/config", + }, + }, + }, + }, + }, + }, + }, + { + name: "Clear InitContainers from DevWorkspaceOperatorConfig", + cheCluster: &chev2.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: chev2.CheClusterSpec{ + DevEnvironments: chev2.CheClusterDevEnvironments{}, + }, + }, + existedObjects: []client.Object{ + &controllerv1alpha1.DevWorkspaceOperatorConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: devWorkspaceConfigName, + Namespace: "eclipse-che", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DevWorkspaceOperatorConfig", + APIVersion: controllerv1alpha1.GroupVersion.String(), + }, + Config: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + InitContainers: []corev1.Container{ + { + Name: "init-to-remove", + Image: "init:v1.0", + }, + }, + }, + }, + }, + }, + expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployContext := test.NewCtxBuilder().WithCheCluster(testCase.cheCluster).WithObjects(testCase.existedObjects...).Build() + + devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() + test.EnsureReconcile(t, deployContext, devWorkspaceConfigReconciler.Reconcile) + + dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} + err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) + + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.InitContainers, dwoc.Config.Workspace.InitContainers) + }) + } +} diff --git a/pkg/deploy/server/chehost_reconciler_test.go b/pkg/deploy/server/chehost_reconciler_test.go index e01e9d3bdb..ab2d5ba663 100644 --- a/pkg/deploy/server/chehost_reconciler_test.go +++ b/pkg/deploy/server/chehost_reconciler_test.go @@ -55,7 +55,7 @@ func TestSyncService(t *testing.T) { assert.Nil(t, err) service := &corev1.Service{} - done, err = deploy.Get(ctx, types.NamespacedName{Name: deploy.CheServiceName, Namespace: "eclipse-che"}, service) + err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: deploy.CheServiceName, Namespace: "eclipse-che"}, service) assert.True(t, done) assert.Nil(t, err) diff --git a/pkg/deploy/sync.go b/pkg/deploy/sync.go index 37cebdd71d..11ba26e2a4 100644 --- a/pkg/deploy/sync.go +++ b/pkg/deploy/sync.go @@ -68,21 +68,6 @@ func SyncForClient(cli client.Client, deployContext *chetypes.DeployContext, blu return doUpdate(cli, deployContext, actual.(client.Object), blueprint, diffOpts...) } -// Get gets object. -// Returns true if object exists otherwise returns false. -// Returns error if object cannot be retrieved otherwise returns nil. -func Get(deployContext *chetypes.DeployContext, key client.ObjectKey, actual client.Object) (bool, error) { - cli := getClientForObject(key.Namespace, deployContext) - return doGet(context.TODO(), cli, key, actual) -} - -// Get gets object. -// Returns true if object exists otherwise returns false. -// Returns error if object cannot be retrieved otherwise returns nil. -func GetForClient(cli client.Client, key client.ObjectKey, actual client.Object) (bool, error) { - return doGet(context.TODO(), cli, key, actual) -} - // Gets namespaced scope object by name // Returns true if object exists otherwise returns false. func GetNamespacedObject(deployContext *chetypes.DeployContext, name string, actual client.Object) (bool, error) { @@ -120,12 +105,6 @@ func DeleteNamespacedObject(deployContext *chetypes.DeployContext, name string, return DeleteByKeyWithClient(client, key, objectMeta) } -func DeleteClusterObject(deployContext *chetypes.DeployContext, name string, objectMeta client.Object) (bool, error) { - client := deployContext.ClusterAPI.NonCachingClient - key := types.NamespacedName{Name: name} - return DeleteByKeyWithClient(client, key, objectMeta) -} - func DeleteByKeyWithClient(cli client.Client, key client.ObjectKey, objectMeta client.Object) (bool, error) { runtimeObject, ok := objectMeta.(runtime.Object) if !ok { diff --git a/pkg/deploy/sync_test.go b/pkg/deploy/sync_test.go index c6feb3d277..a585ab4fc0 100644 --- a/pkg/deploy/sync_test.go +++ b/pkg/deploy/sync_test.go @@ -54,21 +54,6 @@ var ( testKey = client.ObjectKey{Name: "test-secret", Namespace: "eclipse-che"} ) -func TestGet(t *testing.T) { - ctx := test.NewCtxBuilder().Build() - - err := ctx.ClusterAPI.Client.Create(context.TODO(), testObj.DeepCopy()) - if err != nil { - t.Fatalf("Failed to create object: %v", err) - } - - actual := &corev1.Secret{} - exists, err := Get(ctx, testKey, actual) - if !exists || err != nil { - t.Fatalf("Failed to get object: %v", err) - } -} - func TestCreateIgnoreIfExistsShouldReturnTrueIfObjectCreated(t *testing.T) { ctx := test.NewCtxBuilder().Build() diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index e6588bcf23..d2daf1f3f0 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -25,8 +25,6 @@ import ( "github.com/eclipse-che/che-operator/pkg/common/diffs" k8sclient "github.com/eclipse-che/che-operator/pkg/common/k8s-client" "github.com/eclipse-che/che-operator/pkg/common/reconciler" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/eclipse-che/che-operator/pkg/common/utils" @@ -102,7 +100,7 @@ func (c *CertificatesReconciler) Reconcile(ctx *chetypes.DeployContext) (reconci return reconcile.Result{}, true, nil } -func (c *CertificatesReconciler) Finalize(ctx *chetypes.DeployContext) bool { +func (c *CertificatesReconciler) Finalize(_ctx *chetypes.DeployContext) bool { return true } @@ -116,9 +114,9 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes // It might contain custom certificates added there before the doc has been introduced // https://eclipse.dev/che/docs/stable/administration-guide/importing-untrusted-tls-certificates/ openShiftCaBundleCM := &corev1.ConfigMap{} - exists, err := deploy.Get(ctx, openShiftCaBundleCMKey, openShiftCaBundleCM) + exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound(context.TODO(), openShiftCaBundleCMKey, openShiftCaBundleCM) if err != nil { - return false, err + return false, fmt.Errorf("failed to read ConfigMap %s: %w", constants.DefaultCaBundleCertsCMName, err) } if !exists { @@ -130,17 +128,10 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } } - // Ensure TypeMeta to avoid "cause: no version "" has been registered in scheme" error - openShiftCaBundleCM.TypeMeta = metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - } + defaultLabels := deploy.GetLabels(constants.CheCABundle) openShiftCaBundleCM.Labels = utils.GetMapOrDefault(openShiftCaBundleCM.Labels, map[string]string{}) - utils.AddMap(openShiftCaBundleCM.Labels, deploy.GetLabels(constants.CheCABundle)) - - labelKeys := slices.Collect(maps.Keys(deploy.GetLabels(constants.CheCABundle))) - labelKeys = append(labelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + utils.AddMap(openShiftCaBundleCM.Labels, defaultLabels) if ctx.CheCluster.IsDisableWorkspaceCaBundleMount() { // Remove annotation to stop OpenShift network operator from injecting certificates @@ -161,31 +152,34 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } trustedCACM := &corev1.ConfigMap{} - if exists, err := deploy.Get(ctx, trustedCACMKey, trustedCACM); exists { + if exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound(context.TODO(), trustedCACMKey, trustedCACM); exists { openShiftCaBundleCM.Data = utils.GetMapOrDefault(openShiftCaBundleCM.Data, map[string]string{}) openShiftCaBundleCM.Data["ca-bundle.crt"] = trustedCACM.Data["ca-bundle.crt"] } else if err != nil { return false, err } } - - return deploy.Sync(ctx, openShiftCaBundleCM, diffs.ConfigMap(labelKeys, nil)) } else { // Add annotation to allow OpenShift network operator inject certificates // https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/networking/configuring-a-custom-pki#certificate-injection-using-operators_configuring-a-custom-pki openShiftCaBundleCM.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle] = "true" + } - // Ignore Data field to allow OpenShift network operator inject certificates into CM - // and avoid endless reconciliation loop - return deploy.Sync( - ctx, - openShiftCaBundleCM, - cmp.Options{ - diffs.ConfigMap(labelKeys, nil), - cmpopts.IgnoreFields(corev1.ConfigMap{}, "Data"), - }, - ) + if err := controllerutil.SetControllerReference(ctx.CheCluster, openShiftCaBundleCM, ctx.ClusterAPI.Scheme); err != nil { + return false, err } + + mandatoryLabelKeys := slices.Collect(maps.Keys(defaultLabels)) + mandatoryLabelKeys = append(mandatoryLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + + err = ctx.ClusterAPI.ClientWrapper.Sync( + context.TODO(), + openShiftCaBundleCM, + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap(mandatoryLabelKeys, nil), + }) + + return err == nil, err } func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetypes.DeployContext) (bool, error) { @@ -208,8 +202,21 @@ func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetype Data: map[string]string{kubernetesCABundleCertsFile: string(data)}, } - labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(kubernetesCaBundleCM) - return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMap(labelKeys, annotationKeys)) + if err := controllerutil.SetControllerReference(ctx.CheCluster, kubernetesCaBundleCM, ctx.ClusterAPI.Scheme); err != nil { + return false, err + } + + err = ctx.ClusterAPI.ClientWrapper.Sync( + context.TODO(), + kubernetesCaBundleCM, + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap( + deploy.GetLabelsAndAnnotations(kubernetesCaBundleCM), + ), + }, + ) + + return err == nil, err } // syncGitTrustedCertificates adds labels to git trusted certificates ConfigMap @@ -225,17 +232,12 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy Name: ctx.CheCluster.Spec.DevEnvironments.TrustedCerts.GitTrustedCertsConfigMapName, } - exists, err := deploy.Get(ctx, gitTrustedCertsKey, gitTrustedCertsCM) + exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound(context.TODO(), gitTrustedCertsKey, gitTrustedCertsCM) if !exists { return err == nil, err } if gitTrustedCertsCM.Data[constants.GitSelfSignedCertsConfigMapCertKey] != "" { - gitTrustedCertsCM.TypeMeta = metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - } - if gitTrustedCertsCM.GetLabels() == nil { gitTrustedCertsCM.Labels = map[string]string{} } @@ -244,12 +246,20 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy gitTrustedCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg gitTrustedCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle - labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(gitTrustedCertsCM) - return deploy.Sync( - ctx, + // We don't need to SetControllerReference on this ConfigMap, since + // has been created by admin + + err = ctx.ClusterAPI.ClientWrapper.Sync( + context.TODO(), gitTrustedCertsCM, - diffs.ConfigMap(labelKeys, annotationKeys), + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap( + deploy.GetLabelsAndAnnotations(gitTrustedCertsCM), + ), + }, ) + + return err == nil, err } return true, nil @@ -264,11 +274,13 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy Namespace: ctx.CheCluster.Namespace, } - exists, err := deploy.Get(ctx, selfSignedCertSecretKey, selfSignedCertSecret) + exists, err := ctx.ClusterAPI.ClientWrapper.GetIgnoreNotFound(context.TODO(), selfSignedCertSecretKey, selfSignedCertSecret) if !exists { return err == nil, err } + defaultLabels := deploy.GetLabels(constants.CheCABundle) + if len(selfSignedCertSecret.Data["ca.crt"]) > 0 { selfSignedCertCM := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ @@ -278,30 +290,36 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy ObjectMeta: metav1.ObjectMeta{ Name: constants.DefaultSelfSignedCertificateSecretName, Namespace: ctx.CheCluster.Namespace, - Labels: deploy.GetLabels(constants.CheCABundle), + Labels: defaultLabels, Annotations: map[string]string{}, }, Data: map[string]string{"ca.crt": string(selfSignedCertSecret.Data["ca.crt"])}, } - labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(selfSignedCertCM) - return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMap(labelKeys, annotationKeys)) + mandatoryLabelKeys := slices.Collect(maps.Keys(defaultLabels)) + mandatoryLabelKeys = append(mandatoryLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + + err = ctx.ClusterAPI.ClientWrapper.Sync( + context.TODO(), + selfSignedCertCM, + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap(mandatoryLabelKeys, nil), + }) } - return true, nil + return err == nil, err } // syncKubernetesRootCertificates adds labels to `kube-root-ca.crt` ConfigMap // to include them into the final bundle func (c *CertificatesReconciler) syncKubernetesRootCertificates(ctx *chetypes.DeployContext) (bool, error) { - client := ctx.ClusterAPI.NonCachingClient kubeRootCertsCM := &corev1.ConfigMap{} kubeRootCertsCMKey := types.NamespacedName{ Name: kubernetesRootCACertsCMName, Namespace: ctx.CheCluster.Namespace, } - exists, err := deploy.GetForClient(client, kubeRootCertsCMKey, kubeRootCertsCM) + exists, err := ctx.ClusterAPI.NonCachingClientWrapper.GetIgnoreNotFound(context.TODO(), kubeRootCertsCMKey, kubeRootCertsCM) if !exists { return err == nil, err } @@ -310,23 +328,23 @@ func (c *CertificatesReconciler) syncKubernetesRootCertificates(ctx *chetypes.De kubeRootCertsCM.SetLabels(map[string]string{}) } - // Set TypeMeta to avoid "cause: no version "" has been registered in scheme" error - kubeRootCertsCM.TypeMeta = metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - } - // Add necessary labels to the ConfigMap kubeRootCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg kubeRootCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle - labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(kubeRootCertsCM) - return deploy.SyncForClient( - client, - ctx, + err = ctx.ClusterAPI.NonCachingClientWrapper.Sync( + context.TODO(), kubeRootCertsCM, - diffs.ConfigMap(labelKeys, annotationKeys), - ) + &k8sclient.SyncOptions{ + DiffOpts: diffs.ConfigMap( + []string{ + constants.KubernetesPartOfLabelKey, + constants.KubernetesComponentLabelKey}, + nil, + ), + }) + + return err == nil, err } func (c *CertificatesReconciler) syncOIDCIssuerCertificate(ctx *chetypes.DeployContext) (bool, error) { @@ -349,13 +367,13 @@ func (c *CertificatesReconciler) syncOIDCIssuerCertificate(ctx *chetypes.DeployC return false, err } - labelKeys := slices.Collect(maps.Keys(cm.GetLabels())) + mandatoryLabelKeys := slices.Collect(maps.Keys(cm.GetLabels())) err := ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), cm, &k8sclient.SyncOptions{ - DiffOpts: diffs.ConfigMap(labelKeys, nil), + DiffOpts: diffs.ConfigMap(mandatoryLabelKeys, nil), }, ) return err == nil, err diff --git a/pkg/deploy/tls/certificates_test.go b/pkg/deploy/tls/certificates_test.go index 1483d6bfd0..5b65d9162f 100644 --- a/pkg/deploy/tls/certificates_test.go +++ b/pkg/deploy/tls/certificates_test.go @@ -408,6 +408,7 @@ func TestSyncOpenShiftCABundleCertificatesRemovesInjectLabel(t *testing.T) { cm := &corev1.ConfigMap{} err := ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: constants.DefaultCaBundleCertsCMName, Namespace: "eclipse-che"}, cm) + assert.NoError(t, err) assert.Empty(t, cm.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle]) @@ -526,31 +527,6 @@ func TestToggleDisableWorkspaceCaBundleMount(t *testing.T) { assert.Equal(t, 1, len(caCertsMergedCM.Data)) } -func TestSyncOpenShiftCABundleCertificatesIgnoresDataField(t *testing.T) { - ctx := test.NewCtxBuilder().Build() - - test.EnsureReconcile(t, ctx, NewCertificatesReconciler().Reconcile) - - caCertsCM := &corev1.ConfigMap{} - err := ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "ca-certs", Namespace: "eclipse-che"}, caCertsCM) - assert.NoError(t, err) - assert.Equal(t, "true", caCertsCM.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle]) - - // Simulate OpenShift network operator injecting CA bundle into the ConfigMap - caCertsCM.Data = map[string]string{"ca-bundle.crt": "openshift-injected-ca-bundle"} - err = ctx.ClusterAPI.Client.Update(context.TODO(), caCertsCM) - assert.NoError(t, err) - - // Reconcile again — Data field should be ignored in the diff - test.EnsureReconcile(t, ctx, NewCertificatesReconciler().Reconcile) - - caCertsCM = &corev1.ConfigMap{} - err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "ca-certs", Namespace: "eclipse-che"}, caCertsCM) - assert.NoError(t, err) - assert.Equal(t, "openshift-injected-ca-bundle", caCertsCM.Data["ca-bundle.crt"]) - assert.Equal(t, "true", caCertsCM.Labels[constants.ConfigOpenShiftIOInjectTrustedCaBundle]) -} - func TestSyncCheCABundleCertsWithEmptyConfigMap(t *testing.T) { // A CA bundle ConfigMap exists but has no data entries emptyCert := &corev1.ConfigMap{ From 951e385c53100c85ef1837bc35a3c7fbad022fb3 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Fri, 12 Jun 2026 15:22:10 +0200 Subject: [PATCH 07/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/deploy/devworkspace/dev_workspace_config_routing_test.go | 2 +- pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go | 2 +- pkg/deploy/devworkspace/dev_workspace_config_security_test.go | 2 +- .../devworkspace/dev_workspace_config_service_account_test.go | 2 +- pkg/deploy/devworkspace/dev_workspace_config_storage_test.go | 2 +- pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go index 047bf30512..f35ce2694f 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2025 Red Hat, Inc. +// Copyright (c) 2019-2026 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/ diff --git a/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go b/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go index a45ed429ec..16d7060b40 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2025 Red Hat, Inc. +// Copyright (c) 2019-2026 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/ diff --git a/pkg/deploy/devworkspace/dev_workspace_config_security_test.go b/pkg/deploy/devworkspace/dev_workspace_config_security_test.go index 0f110bb4b3..07cae628a8 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_security_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_security_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2025 Red Hat, Inc. +// Copyright (c) 2019-2026 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/ diff --git a/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go b/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go index 0d8587478f..ad080762af 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2025 Red Hat, Inc. +// Copyright (c) 2019-2026 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/ diff --git a/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go b/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go index d075e85e10..890c3e2666 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2025 Red Hat, Inc. +// Copyright (c) 2019-2026 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/ diff --git a/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go b/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go index 86f9bdc098..6d33b1772d 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2025 Red Hat, Inc. +// Copyright (c) 2019-2026 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/ From 5d9d65129b606737f8a0b3e06cddcb3a2653086d Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Fri, 12 Jun 2026 15:54:58 +0200 Subject: [PATCH 08/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- .../devworkspace/dev_workspace_config_scheduling_test.go | 4 +++- pkg/deploy/devworkspace/dev_workspace_config_security_test.go | 1 + .../devworkspace/dev_workspace_config_service_account_test.go | 1 + pkg/deploy/devworkspace/dev_workspace_config_storage_test.go | 1 + .../devworkspace/dev_workspace_config_workspace_test.go | 2 +- pkg/deploy/server/chehost_reconciler_test.go | 1 - pkg/deploy/tls/certificates.go | 4 ++++ pkg/deploy/tls/certificates_test.go | 1 + 8 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go b/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go index 16d7060b40..56511a0248 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_scheduling_test.go @@ -479,8 +479,9 @@ func TestReconcileDevWorkspaceConfigRuntimeClassName(t *testing.T) { dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) - assert.Equal(t, testCase.expectedOperatorConfig.Workspace.SchedulerName, dwoc.Config.Workspace.SchedulerName) + assert.Equal(t, testCase.expectedOperatorConfig.Workspace.RuntimeClassName, dwoc.Config.Workspace.RuntimeClassName) }) } } @@ -591,6 +592,7 @@ func TestReconcileDevWorkspaceConfigDeploymentStrategy(t *testing.T) { dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) assert.Equal(t, testCase.expectedOperatorConfig.Workspace.DeploymentStrategy, dwoc.Config.Workspace.DeploymentStrategy) }) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_security_test.go b/pkg/deploy/devworkspace/dev_workspace_config_security_test.go index 07cae628a8..15a1c5357c 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_security_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_security_test.go @@ -734,6 +734,7 @@ func TestReconcileDevWorkspacePodSecurityContext(t *testing.T) { dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) assert.Equal(t, testCase.expectedOperatorConfig.Workspace.PodSecurityContext, dwoc.Config.Workspace.PodSecurityContext, fmt.Sprintf("Did not get expected PodSecurityContext.\nDiff:%s", cmp.Diff(testCase.expectedOperatorConfig.Workspace.PodSecurityContext, dwoc.Config.Workspace.PodSecurityContext))) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go b/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go index ad080762af..a8d193f70f 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_service_account_test.go @@ -387,6 +387,7 @@ func TestReconcileDevWorkspaceConfigServiceAccountTokens(t *testing.T) { dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) + assert.NoError(t, err) assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ServiceAccount.ServiceAccountTokens, dwoc.Config.Workspace.ServiceAccount.ServiceAccountTokens) }) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go b/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go index 890c3e2666..f70b2f25c0 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_storage_test.go @@ -742,6 +742,7 @@ func TestReconcileDevWorkspaceConfigStorage(t *testing.T) { devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler() _, _, err := devWorkspaceConfigReconciler.Reconcile(deployContext) + assert.Error(t, err) assert.Regexp(t, regexp.MustCompile(testCase.expectedErrorMessage), err.Error(), "error message must match") }) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go b/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go index 6d33b1772d..43cc2ac24d 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_workspace_test.go @@ -970,8 +970,8 @@ func TestReconcileDevWorkspaceConfigForInitContainers(t *testing.T) { dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{} err := deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc) - assert.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, testCase.expectedOperatorConfig.Workspace.InitContainers, dwoc.Config.Workspace.InitContainers) }) } diff --git a/pkg/deploy/server/chehost_reconciler_test.go b/pkg/deploy/server/chehost_reconciler_test.go index ab2d5ba663..6ae32b913a 100644 --- a/pkg/deploy/server/chehost_reconciler_test.go +++ b/pkg/deploy/server/chehost_reconciler_test.go @@ -56,7 +56,6 @@ func TestSyncService(t *testing.T) { service := &corev1.Service{} err = ctx.ClusterAPI.Client.Get(context.TODO(), 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") diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index d2daf1f3f0..8bac4dbf26 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -299,6 +299,10 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy mandatoryLabelKeys := slices.Collect(maps.Keys(defaultLabels)) mandatoryLabelKeys = append(mandatoryLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + if err := controllerutil.SetControllerReference(ctx.CheCluster, selfSignedCertCM, ctx.ClusterAPI.Scheme); err != nil { + return false, err + } + err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), selfSignedCertCM, diff --git a/pkg/deploy/tls/certificates_test.go b/pkg/deploy/tls/certificates_test.go index 5b65d9162f..b72abad4a1 100644 --- a/pkg/deploy/tls/certificates_test.go +++ b/pkg/deploy/tls/certificates_test.go @@ -240,6 +240,7 @@ func TestSyncCheCABundleCerts(t *testing.T) { cm := &corev1.ConfigMap{} err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: CheMergedCABundleCertsCMName, Namespace: "eclipse-che"}, cm) assert.Nil(t, err) + assert.Equal(t, "true", cm.Labels[dwconstants.DevWorkspaceWatchConfigMapLabel]) cert2 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ From 8e33d840da87cbca98868d6461b967f4ba471deb Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Fri, 12 Jun 2026 17:45:11 +0200 Subject: [PATCH 09/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/deploy/devworkspace/dev_workspace_config.go | 5 ----- pkg/deploy/devworkspace/dev_workspace_config_routing_test.go | 2 +- pkg/deploy/tls/certificates.go | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/deploy/devworkspace/dev_workspace_config.go b/pkg/deploy/devworkspace/dev_workspace_config.go index 9c502b62ef..f0b37952f1 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config.go +++ b/pkg/deploy/devworkspace/dev_workspace_config.go @@ -17,7 +17,6 @@ import ( "encoding/json" "fmt" "maps" - "reflect" "time" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" @@ -336,10 +335,6 @@ func updateTLSCertificateConfigmapRef(ctx *chetypes.DeployContext, operatorConfi } else { if operatorConfig.Routing != nil { operatorConfig.Routing.TLSCertificateConfigmapRef = nil - - if reflect.DeepEqual(operatorConfig.Routing, &controllerv1alpha1.RoutingConfig{}) { - operatorConfig.Routing = nil - } } } diff --git a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go index f35ce2694f..54c7da1a43 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go @@ -163,7 +163,7 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, }, }, - expectedRoutingConfig: nil, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{}, }, { name: "Clear TLSCertificateConfigmapRef when CA bundle ConfigMap data is emptied", diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 8bac4dbf26..afa4a56b9a 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -297,7 +297,6 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy } mandatoryLabelKeys := slices.Collect(maps.Keys(defaultLabels)) - mandatoryLabelKeys = append(mandatoryLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) if err := controllerutil.SetControllerReference(ctx.CheCluster, selfSignedCertCM, ctx.ClusterAPI.Scheme); err != nil { return false, err From adfbac59ad5b6406e44923ecb1ae8b4046c261c3 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Mon, 15 Jun 2026 11:04:25 +0200 Subject: [PATCH 10/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- .../dev_workspace_config_routing_test.go | 2 +- pkg/deploy/labels.go | 10 ++++++++++ pkg/deploy/tls/certificates.go | 15 ++++----------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go index 54c7da1a43..19f6346ace 100644 --- a/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go +++ b/pkg/deploy/devworkspace/dev_workspace_config_routing_test.go @@ -200,7 +200,7 @@ func TestReconcileDevWorkspaceConfigTLSCertificateConfigmapRef(t *testing.T) { }, }, }, - expectedRoutingConfig: nil, + expectedRoutingConfig: &controllerv1alpha1.RoutingConfig{}, }, { name: "Re-reconcile is stable when TLSCertificateConfigmapRef already matches", diff --git a/pkg/deploy/labels.go b/pkg/deploy/labels.go index efcb654fe6..a8dc38774d 100644 --- a/pkg/deploy/labels.go +++ b/pkg/deploy/labels.go @@ -31,6 +31,16 @@ func GetLabels(component string) map[string]string { } } +func GetLabelKeys() []string { + return []string{ + constants.KubernetesNameLabelKey, + constants.KubernetesInstanceLabelKey, + constants.KubernetesPartOfLabelKey, + constants.KubernetesComponentLabelKey, + constants.KubernetesManagedByLabelKey, + } +} + func GetManagedByLabel() string { return defaults.GetCheFlavor() + "-operator" } diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index afa4a56b9a..0057c04a00 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -128,10 +128,8 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } } - defaultLabels := deploy.GetLabels(constants.CheCABundle) - openShiftCaBundleCM.Labels = utils.GetMapOrDefault(openShiftCaBundleCM.Labels, map[string]string{}) - utils.AddMap(openShiftCaBundleCM.Labels, defaultLabels) + utils.AddMap(openShiftCaBundleCM.Labels, deploy.GetLabels(constants.CheCABundle)) if ctx.CheCluster.IsDisableWorkspaceCaBundleMount() { // Remove annotation to stop OpenShift network operator from injecting certificates @@ -169,8 +167,7 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes return false, err } - mandatoryLabelKeys := slices.Collect(maps.Keys(defaultLabels)) - mandatoryLabelKeys = append(mandatoryLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle) + mandatoryLabelKeys := append(deploy.GetLabelKeys(), constants.ConfigOpenShiftIOInjectTrustedCaBundle) err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), @@ -279,8 +276,6 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy return err == nil, err } - defaultLabels := deploy.GetLabels(constants.CheCABundle) - if len(selfSignedCertSecret.Data["ca.crt"]) > 0 { selfSignedCertCM := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ @@ -290,14 +285,12 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy ObjectMeta: metav1.ObjectMeta{ Name: constants.DefaultSelfSignedCertificateSecretName, Namespace: ctx.CheCluster.Namespace, - Labels: defaultLabels, + Labels: deploy.GetLabels(constants.CheCABundle), Annotations: map[string]string{}, }, Data: map[string]string{"ca.crt": string(selfSignedCertSecret.Data["ca.crt"])}, } - mandatoryLabelKeys := slices.Collect(maps.Keys(defaultLabels)) - if err := controllerutil.SetControllerReference(ctx.CheCluster, selfSignedCertCM, ctx.ClusterAPI.Scheme); err != nil { return false, err } @@ -306,7 +299,7 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy context.TODO(), selfSignedCertCM, &k8sclient.SyncOptions{ - DiffOpts: diffs.ConfigMap(mandatoryLabelKeys, nil), + DiffOpts: diffs.ConfigMap(deploy.GetLabelKeys(), nil), }) } From 186652e42bcba30a43f534369b824e40eb05091d Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Mon, 15 Jun 2026 11:16:00 +0200 Subject: [PATCH 11/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/deploy/tls/certificates.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 0057c04a00..13179ed950 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -167,6 +167,8 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes return false, err } + // adding `ConfigOpenShiftIOInjectTrustedCaBundle` label (even if deleted) ensures + // that destination ConfigMap doesn't have it mandatoryLabelKeys := append(deploy.GetLabelKeys(), constants.ConfigOpenShiftIOInjectTrustedCaBundle) err = ctx.ClusterAPI.ClientWrapper.Sync( @@ -207,9 +209,7 @@ func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetype context.TODO(), kubernetesCaBundleCM, &k8sclient.SyncOptions{ - DiffOpts: diffs.ConfigMap( - deploy.GetLabelsAndAnnotations(kubernetesCaBundleCM), - ), + DiffOpts: diffs.ConfigMap(deploy.GetLabelKeys(), nil), }, ) @@ -243,15 +243,17 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy gitTrustedCertsCM.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg gitTrustedCertsCM.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle - // We don't need to SetControllerReference on this ConfigMap, since - // has been created by admin + // Don't need set SetControllerReference on this ConfigMap since it is created by admin err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), gitTrustedCertsCM, &k8sclient.SyncOptions{ DiffOpts: diffs.ConfigMap( - deploy.GetLabelsAndAnnotations(gitTrustedCertsCM), + []string{ + constants.KubernetesPartOfLabelKey, + constants.KubernetesComponentLabelKey}, + nil, ), }, ) @@ -363,13 +365,11 @@ func (c *CertificatesReconciler) syncOIDCIssuerCertificate(ctx *chetypes.DeployC return false, err } - mandatoryLabelKeys := slices.Collect(maps.Keys(cm.GetLabels())) - err := ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), cm, &k8sclient.SyncOptions{ - DiffOpts: diffs.ConfigMap(mandatoryLabelKeys, nil), + DiffOpts: diffs.ConfigMap(deploy.GetLabelKeys(), nil), }, ) return err == nil, err @@ -448,13 +448,11 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex return false, err } - labelKeys, annotationKeys := deploy.GetLabelsAndAnnotations(mergedCABundlesCM) - err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(), mergedCABundlesCM, &k8sclient.SyncOptions{ - DiffOpts: diffs.ConfigMap(labelKeys, annotationKeys), + DiffOpts: diffs.ConfigMap(deploy.GetLabelsAndAnnotations(mergedCABundlesCM)), }, ) return err == nil, err From dabeede09fd307900100087d9559824d1ab2276c Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Mon, 15 Jun 2026 12:08:31 +0200 Subject: [PATCH 12/12] chore: Configure DWI with tlsCertificateConfigmapRef when certificates imported to che-operator Signed-off-by: Anatolii Bazko --- pkg/deploy/labels.go | 2 ++ pkg/deploy/tls/certificates.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/deploy/labels.go b/pkg/deploy/labels.go index a8dc38774d..cdfdb85d34 100644 --- a/pkg/deploy/labels.go +++ b/pkg/deploy/labels.go @@ -65,6 +65,8 @@ func GetLegacyLabels(component string) map[string]string { } } +// GetLabelsAndAnnotations extracts label and annotation keys from an object. +// Note: key order is non-deterministic (map iteration order). func GetLabelsAndAnnotations(obj client.Object) ([]string, []string) { return slices.Collect(maps.Keys(obj.GetLabels())), slices.Collect(maps.Keys(obj.GetAnnotations())) } diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 13179ed950..518d61452f 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -169,7 +169,7 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes // adding `ConfigOpenShiftIOInjectTrustedCaBundle` label (even if deleted) ensures // that destination ConfigMap doesn't have it - mandatoryLabelKeys := append(deploy.GetLabelKeys(), constants.ConfigOpenShiftIOInjectTrustedCaBundle) + mandatoryLabelKeys := slices.Concat(deploy.GetLabelKeys(), []string{constants.ConfigOpenShiftIOInjectTrustedCaBundle}) err = ctx.ClusterAPI.ClientWrapper.Sync( context.TODO(),