diff --git a/controllers/usernamespace/usernamespace_controller.go b/controllers/usernamespace/usernamespace_controller.go index 333fd403bb..91d1d40027 100644 --- a/controllers/usernamespace/usernamespace_controller.go +++ b/controllers/usernamespace/usernamespace_controller.go @@ -19,6 +19,8 @@ import ( "strconv" "strings" + "github.com/eclipse-che/che-operator/pkg/common/diffs" + "github.com/eclipse-che/che-operator/controllers/namespacecache" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -410,7 +412,7 @@ func (r *CheUserNamespaceReconciler) reconcileUserSettings( Data: data, } - _, err := deploy.Sync(deployContext, cm, deploy.ConfigMapDiffOpts) + _, err := deploy.Sync(deployContext, cm, diffs.ConfigMapAllLabels) return err } @@ -464,7 +466,7 @@ func (r *CheUserNamespaceReconciler) reconcileGitTlsCertificate(ctx context.Cont target.Data["host"] = gitCert.Data["githost"] } - _, err := deploy.Sync(deployContext, &target, deploy.ConfigMapDiffOpts) + _, err := deploy.Sync(deployContext, &target, diffs.ConfigMapAllLabels) return err } diff --git a/pkg/common/diffs/diffs.go b/pkg/common/diffs/diffs.go new file mode 100644 index 0000000000..e55d36f781 --- /dev/null +++ b/pkg/common/diffs/diffs.go @@ -0,0 +1,66 @@ +// +// 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 diffs + +import ( + "reflect" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ConfigMapAllLabels = cmp.Options{ + cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), + cmp.Comparer(func(x, y metav1.ObjectMeta) bool { + return reflect.DeepEqual(x.Labels, y.Labels) + }), +} + +func ConfigMap(labels []string, annotations []string) cmp.Options { + return cmp.Options{ + cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), + objectMetaComparator(labels, annotations), + } +} + +func ConfigMapIgnoreData(labels []string, annotations []string) cmp.Options { + return cmp.Options{ + cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), + cmpopts.IgnoreFields(corev1.ConfigMap{}, "Data"), + objectMetaComparator(labels, annotations), + } +} + +func objectMetaComparator(labels []string, annotations []string) cmp.Option { + return cmp.Comparer(func(x, y metav1.ObjectMeta) bool { + if labels != nil { + for _, label := range labels { + if x.Labels[label] != y.Labels[label] { + return false + } + } + } + + if annotations != nil { + for _, annotation := range annotations { + if x.Annotations[annotation] != y.Annotations[annotation] { + return false + } + } + } + + return true + }) +} diff --git a/pkg/deploy/configmap.go b/pkg/deploy/configmap.go deleted file mode 100644 index c816876450..0000000000 --- a/pkg/deploy/configmap.go +++ /dev/null @@ -1,100 +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 deploy - -import ( - "reflect" - - "github.com/eclipse-che/che-operator/pkg/common/chetypes" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var ConfigMapDiffOpts = cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return reflect.DeepEqual(x.Labels, y.Labels) - }), -} - -// InitConfigMap returns a new ConfigMap Kubernetes object. -func InitConfigMap( - ctx *chetypes.DeployContext, - name string, - data map[string]string, - component string) *corev1.ConfigMap { - - return &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ctx.CheCluster.Namespace, - Labels: GetLabels(component), - Annotations: map[string]string{}, - }, - Data: data, - } -} - -func SyncConfigMapDataToCluster( - deployContext *chetypes.DeployContext, - name string, - data map[string]string, - component string) (bool, error) { - - configMapSpec := InitConfigMap(deployContext, name, data, component) - return Sync(deployContext, configMapSpec, ConfigMapDiffOpts) -} - -func SyncConfigMapSpecToCluster( - deployContext *chetypes.DeployContext, - configMapSpec *corev1.ConfigMap) (bool, error) { - - return Sync(deployContext, configMapSpec, ConfigMapDiffOpts) -} - -// SyncConfigMap synchronizes the ConfigMap with the cluster. -func SyncConfigMap( - ctx *chetypes.DeployContext, - cm *corev1.ConfigMap, - ensuredLabels []string, - ensuredAnnotations []string) (bool, error) { - - diffs := cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - GetLabelsAndAnnotationsComparator(ensuredLabels, ensuredAnnotations), - } - - return Sync(ctx, cm, diffs) -} - -// SyncConfigMapIgnoreData synchronizes the ConfigMap with the cluster ignoring the data field. -func SyncConfigMapIgnoreData( - ctx *chetypes.DeployContext, - cm *corev1.ConfigMap, - ensuredLabels []string, - ensuredAnnotations []string) (bool, error) { - - diffs := cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - cmpopts.IgnoreFields(corev1.ConfigMap{}, "Data"), - GetLabelsAndAnnotationsComparator(ensuredLabels, ensuredAnnotations), - } - - return Sync(ctx, cm, diffs) -} diff --git a/pkg/deploy/configmap_test.go b/pkg/deploy/configmap_test.go deleted file mode 100644 index 59dc5ba8de..0000000000 --- a/pkg/deploy/configmap_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// -// Copyright (c) 2019-2023 Red Hat, Inc. -// This program and the accompanying materials are made -// available under the terms of the Eclipse Public License 2.0 -// which is available at https://www.eclipse.org/legal/epl-2.0/ -// -// SPDX-License-Identifier: EPL-2.0 -// -// Contributors: -// Red Hat, Inc. - initial API and implementation -// - -package deploy - -import ( - "context" - "testing" - - "github.com/eclipse-che/che-operator/pkg/common/test" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" -) - -func TestSyncConfigMapDataToCluster(t *testing.T) { - ctx := test.NewCtxBuilder().Build() - - done, err := SyncConfigMapDataToCluster(ctx, "test", map[string]string{"a": "b"}, "che") - if !done || err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - // sync a new config map - _, err = SyncConfigMapDataToCluster(ctx, "test", map[string]string{"c": "d"}, "che") - if err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - // sync twice to be sure update done correctly - done, err = SyncConfigMapDataToCluster(ctx, "test", map[string]string{"c": "d"}, "che") - if !done || err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - actual := &corev1.ConfigMap{} - err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test", Namespace: "eclipse-che"}, actual) - if err != nil { - t.Fatalf("Failed to get config map: %v", err) - } - - if actual.Data["c"] != "d" { - t.Fatalf("Failed to sync config map: %v", err) - } - - if actual.Data["a"] == "b" { - t.Fatalf("Failed to sync config map: %v", err) - } -} - -func TestSyncConfigMapSpecDataToCluster(t *testing.T) { - ctx := test.NewCtxBuilder().Build() - - spec := InitConfigMap(ctx, "test", map[string]string{"a": "b"}, "che") - done, err := SyncConfigMapSpecToCluster(ctx, spec) - if !done || err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - // check if labels - spec = InitConfigMap(ctx, "test", map[string]string{"a": "b"}, "che") - spec.ObjectMeta.Labels = map[string]string{"l": "v"} - _, err = SyncConfigMapSpecToCluster(ctx, spec) - if err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - // sync twice to be sure update done correctly - done, err = SyncConfigMapSpecToCluster(ctx, spec) - if !done || err != nil { - t.Fatalf("Failed to sync config map: %v", err) - } - - actual := &corev1.ConfigMap{} - err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test", Namespace: "eclipse-che"}, actual) - if err != nil { - t.Fatalf("Failed to get config map: %v", err) - } - if actual.ObjectMeta.Labels["l"] != "v" { - t.Fatalf("Failed to sync config map") - } -} diff --git a/pkg/deploy/editors-definitions/editors_definitions.go b/pkg/deploy/editors-definitions/editors_definitions.go index 86851f978b..ccd8f98478 100644 --- a/pkg/deploy/editors-definitions/editors_definitions.go +++ b/pkg/deploy/editors-definitions/editors_definitions.go @@ -18,6 +18,8 @@ import ( "path/filepath" "regexp" + "github.com/eclipse-che/che-operator/pkg/common/diffs" + "github.com/eclipse-che/che-operator/pkg/common/utils" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -159,5 +161,5 @@ func syncEditorDefinitions(ctx *chetypes.DeployContext, editorDefinitions map[st cm.Data[fileName] = string(content) } - return deploy.Sync(ctx, cm, deploy.ConfigMapDiffOpts) + return deploy.Sync(ctx, cm, diffs.ConfigMapAllLabels) } diff --git a/pkg/deploy/expose/expose.go b/pkg/deploy/expose/expose.go index 2c3cb6d109..9149a20c78 100644 --- a/pkg/deploy/expose/expose.go +++ b/pkg/deploy/expose/expose.go @@ -15,6 +15,8 @@ package expose import ( "strings" + "github.com/eclipse-che/che-operator/pkg/common/diffs" + "github.com/devfile/devworkspace-operator/pkg/infrastructure" routev1 "github.com/openshift/api/route/v1" @@ -74,7 +76,7 @@ func exposeWithGateway(deployContext *chetypes.DeployContext, if err != nil { return "", false, err } - done, err = deploy.SyncConfigMapSpecToCluster(deployContext, &cfg) + done, err = deploy.Sync(deployContext, &cfg, diffs.ConfigMapAllLabels) if !done { if err != nil { logrus.Error(err) diff --git a/pkg/deploy/labels.go b/pkg/deploy/labels.go index fde6d341fd..25d309b1f5 100644 --- a/pkg/deploy/labels.go +++ b/pkg/deploy/labels.go @@ -17,6 +17,16 @@ import ( defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults" ) +var ( + DefaultsLabelKeys = []string{ + constants.KubernetesNameLabelKey, + constants.KubernetesInstanceLabelKey, + constants.KubernetesPartOfLabelKey, + constants.KubernetesComponentLabelKey, + constants.KubernetesManagedByLabelKey, + } +) + func GetLabels(component string) map[string]string { return map[string]string{ constants.KubernetesNameLabelKey: defaults.GetCheFlavor(), @@ -27,19 +37,6 @@ func GetLabels(component string) map[string]string { } } -func GetDefaultKubernetesLabelsWith(labels ...string) []string { - defaultK8sLabels := append( - []string{ - constants.KubernetesNameLabelKey, - constants.KubernetesInstanceLabelKey, - constants.KubernetesPartOfLabelKey, - constants.KubernetesComponentLabelKey, - constants.KubernetesManagedByLabelKey, - }, labels...) - - return defaultK8sLabels -} - func GetManagedByLabel() string { return defaults.GetCheFlavor() + "-operator" } diff --git a/pkg/deploy/pluginregistry/pluginregistry.go b/pkg/deploy/pluginregistry/pluginregistry.go index 7e7189ca06..5936616631 100644 --- a/pkg/deploy/pluginregistry/pluginregistry.go +++ b/pkg/deploy/pluginregistry/pluginregistry.go @@ -16,6 +16,9 @@ import ( "fmt" "strings" + "github.com/eclipse-che/che-operator/pkg/common/diffs" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -98,7 +101,22 @@ func (p *PluginRegistryReconciler) syncConfigMap(ctx *chetypes.DeployContext) (b if err != nil { return false, err } - return deploy.SyncConfigMapDataToCluster(ctx, constants.PluginRegistryName, data, constants.PluginRegistryName) + + cm := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: constants.PluginRegistryName, + Namespace: ctx.CheCluster.Namespace, + Labels: deploy.GetLabels(constants.PluginRegistryName), + Annotations: data, + }, + Data: data, + } + + return deploy.Sync(ctx, cm, diffs.ConfigMapAllLabels) } func (p *PluginRegistryReconciler) ExposeEndpoint(ctx *chetypes.DeployContext) (string, bool, error) { diff --git a/pkg/deploy/server/server_reconciler.go b/pkg/deploy/server/server_reconciler.go index d36ce9b88b..47fc700a35 100644 --- a/pkg/deploy/server/server_reconciler.go +++ b/pkg/deploy/server/server_reconciler.go @@ -16,11 +16,13 @@ import ( chev2 "github.com/eclipse-che/che-operator/api/v2" "github.com/eclipse-che/che-operator/pkg/common/chetypes" "github.com/eclipse-che/che-operator/pkg/common/constants" + "github.com/eclipse-che/che-operator/pkg/common/diffs" defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -91,7 +93,21 @@ func (s *CheServerReconciler) syncCheConfigMap(ctx *chetypes.DeployContext) (boo return false, err } - return deploy.SyncConfigMapDataToCluster(ctx, CheConfigMapName, data, getComponentName(ctx)) + cm := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: CheConfigMapName, + Namespace: ctx.CheCluster.Namespace, + Labels: deploy.GetLabels(getComponentName(ctx)), + Annotations: data, + }, + Data: data, + } + + return deploy.Sync(ctx, cm, diffs.ConfigMapAllLabels) } func (s *CheServerReconciler) syncActiveChePhase(ctx *chetypes.DeployContext) (bool, error) { diff --git a/pkg/deploy/sync.go b/pkg/deploy/sync.go index cd2bff2a3c..02c1dd2d99 100644 --- a/pkg/deploy/sync.go +++ b/pkg/deploy/sync.go @@ -202,27 +202,6 @@ func DeleteIgnoreIfNotFound( return err } -func GetLabelsAndAnnotationsComparator( - ensuredLabels []string, - ensuredAnnotations []string) cmp.Option { - - return cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - for _, label := range ensuredLabels { - if x.Labels[label] != y.Labels[label] { - return false - } - } - - for _, annotation := range ensuredAnnotations { - if x.Annotations[annotation] != y.Annotations[annotation] { - return false - } - } - - return true - }) -} - // doCreate creates object. // Return error if object cannot be created otherwise returns nil. func doCreate( diff --git a/pkg/deploy/tls/certificates.go b/pkg/deploy/tls/certificates.go index 4c541724e9..6bcb398be5 100644 --- a/pkg/deploy/tls/certificates.go +++ b/pkg/deploy/tls/certificates.go @@ -16,16 +16,15 @@ import ( "errors" "fmt" "os" - "reflect" + "sort" "strings" + "github.com/eclipse-che/che-operator/pkg/common/diffs" + "github.com/eclipse-che/che-operator/pkg/common/utils" dwconstants "github.com/devfile/devworkspace-operator/pkg/constants" "github.com/devfile/devworkspace-operator/pkg/infrastructure" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/eclipse-che/che-operator/pkg/common/chetypes" @@ -43,12 +42,6 @@ const ( // The ConfigMap name for merged CA bundle certificates CheMergedCABundleCertsCMName = "ca-certs-merged" - - // Annotation holds revisions of included config maps - // in the format: name1#ver1 name2=ver2 - cheCABundleIncludedCMRevisions = "che.eclipse.org/included-configmaps" - entrySplitter = " " - keyValueSplitter = "#" ) type CertificatesReconciler struct { @@ -158,11 +151,10 @@ func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes } } - return deploy.SyncConfigMap( + return deploy.Sync( ctx, openShiftCaBundleCM, - deploy.GetDefaultKubernetesLabelsWith(constants.ConfigOpenShiftIOInjectTrustedCaBundle), - []string{}, + diffs.ConfigMap(append(deploy.DefaultsLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle), nil), ) } else { // Add annotation to allow OpenShift network operator inject certificates @@ -171,11 +163,10 @@ 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.SyncConfigMapIgnoreData( + return deploy.Sync( ctx, openShiftCaBundleCM, - deploy.GetDefaultKubernetesLabelsWith(constants.ConfigOpenShiftIOInjectTrustedCaBundle), - []string{}, + diffs.ConfigMap(append(deploy.DefaultsLabelKeys, constants.ConfigOpenShiftIOInjectTrustedCaBundle), nil), ) } } @@ -186,14 +177,21 @@ func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetype return false, err } - kubernetesCaBundleCM := deploy.InitConfigMap( - ctx, - constants.DefaultCaBundleCertsCMName, - map[string]string{kubernetesCABundleCertsFile: string(data)}, - constants.CheCABundle, - ) + kubernetesCaBundleCM := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: constants.DefaultCaBundleCertsCMName, + Namespace: ctx.CheCluster.Namespace, + Labels: deploy.GetLabels(constants.CheCABundle), + Annotations: map[string]string{}, + }, + Data: map[string]string{kubernetesCABundleCertsFile: string(data)}, + } - return deploy.Sync(ctx, kubernetesCaBundleCM, deploy.ConfigMapDiffOpts) + return deploy.Sync(ctx, kubernetesCaBundleCM, diffs.ConfigMapAllLabels) } // syncGitTrustedCertificates adds labels to git trusted certificates ConfigMap @@ -231,13 +229,8 @@ func (c *CertificatesReconciler) syncGitTrustedCertificates(ctx *chetypes.Deploy return deploy.Sync( ctx, gitTrustedCertsCM, - cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return x.Labels[constants.KubernetesPartOfLabelKey] == y.Labels[constants.KubernetesPartOfLabelKey] && - x.Labels[constants.KubernetesComponentLabelKey] == y.Labels[constants.KubernetesComponentLabelKey] - }), - }) + diffs.ConfigMap([]string{constants.KubernetesPartOfLabelKey, constants.KubernetesComponentLabelKey}, nil), + ) } return true, nil @@ -258,15 +251,21 @@ func (c *CertificatesReconciler) syncSelfSignedCertificates(ctx *chetypes.Deploy } if len(selfSignedCertSecret.Data["ca.crt"]) > 0 { - selfSignedCertCM := deploy.InitConfigMap( - ctx, - constants.DefaultSelfSignedCertificateSecretName, - map[string]string{ - "ca.crt": string(selfSignedCertSecret.Data["ca.crt"]), + selfSignedCertCM := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", }, - constants.CheCABundle) + ObjectMeta: metav1.ObjectMeta{ + Name: constants.DefaultSelfSignedCertificateSecretName, + Namespace: ctx.CheCluster.Namespace, + Labels: deploy.GetLabels(constants.CheCABundle), + Annotations: map[string]string{}, + }, + Data: map[string]string{"ca.crt": string(selfSignedCertSecret.Data["ca.crt"])}, + } - return deploy.Sync(ctx, selfSignedCertCM, deploy.ConfigMapDiffOpts) + return deploy.Sync(ctx, selfSignedCertCM, diffs.ConfigMapAllLabels) } return true, nil @@ -305,13 +304,8 @@ func (c *CertificatesReconciler) syncKubernetesRootCertificates(ctx *chetypes.De client, ctx, kubeRootCertsCM, - cmp.Options{ - cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"), - cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return x.Labels[constants.KubernetesPartOfLabelKey] == y.Labels[constants.KubernetesPartOfLabelKey] && - x.Labels[constants.KubernetesComponentLabelKey] == y.Labels[constants.KubernetesComponentLabelKey] - }), - }) + diffs.ConfigMap([]string{constants.KubernetesPartOfLabelKey, constants.KubernetesComponentLabelKey}, nil), + ) } // syncCheCABundleCerts merges all trusted CA certificates into a single ConfigMap `ca-certs-merged`, @@ -323,17 +317,17 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex return false, err } - // Calculated expected revisions and content - cheCABundlesExpectedContent := "" - cheCABundleExpectedRevisions := make(map[string]string) - cheCABundlesExpectedRevisionsAsString := "" - for _, cm := range cheCABundlesCMs { - cheCABundleExpectedRevisions[cm.Name] = cm.ResourceVersion - cheCABundlesExpectedRevisionsAsString += - cm.ObjectMeta.Name + keyValueSplitter + cm.ObjectMeta.ResourceVersion + entrySplitter + // Sort configmaps by name, always have the same order and content + // to avoid endless reconcile loop + sort.Slice(cheCABundlesCMs, func(i, j int) bool { + return strings.Compare(cheCABundlesCMs[i].Name, cheCABundlesCMs[j].Name) < 0 + }) + // Calculated revisions and content + cheCABundlesContent := "" + for _, cm := range cheCABundlesCMs { for dataKey, dataValue := range cm.Data { - cheCABundlesExpectedContent += fmt.Sprintf( + cheCABundlesContent += fmt.Sprintf( "# ConfigMap: %s, Key: %s\n%s\n\n", cm.Name, dataKey, @@ -342,52 +336,25 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex } } - // Calculated actual revisions - mergedCABundlesCM := &corev1.ConfigMap{} - mergedCABundlesCMKey := types.NamespacedName{ - Name: CheMergedCABundleCertsCMName, - Namespace: ctx.CheCluster.Namespace, - } - - exists, err := deploy.Get(ctx, mergedCABundlesCMKey, mergedCABundlesCM) - if err != nil { - return false, err - } - - if exists { - cheCABundleCMActualRevisions := make(map[string]string) - if mergedCABundlesCM.GetAnnotations() != nil { - if revs, ok := mergedCABundlesCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions]; ok { - for _, rev := range strings.Split(revs, entrySplitter) { - item := strings.Split(rev, keyValueSplitter) - if len(item) == 2 { - cheCABundleCMActualRevisions[item[0]] = item[1] - } - } - } - } - - // Compare actual and expected revisions to check if we need to update the ConfigMap - if reflect.DeepEqual(cheCABundleExpectedRevisions, cheCABundleCMActualRevisions) { - return true, nil - } - } - // Sync a new ConfigMap with all trusted CA certificates - mergedCABundlesCM = deploy.InitConfigMap( - ctx, - CheMergedCABundleCertsCMName, - map[string]string{}, - // Mark ConfigMap as workspace config (will be mounted in all users' containers) - constants.WorkspacesConfig, - ) - - if len(strings.TrimSpace(cheCABundlesExpectedContent)) != 0 { - mergedCABundlesCM.Data[kubernetesCABundleCertsFile] = cheCABundlesExpectedContent + mergedCABundlesCM := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + 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), + Annotations: map[string]string{}, + }, + Data: map[string]string{}, } - // Add annotations with included config maps revisions - mergedCABundlesCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions] = cheCABundlesExpectedRevisionsAsString + if len(strings.TrimSpace(cheCABundlesContent)) != 0 { + mergedCABundlesCM.Data[kubernetesCABundleCertsFile] = cheCABundlesContent + } if !ctx.CheCluster.IsDisableWorkspaceCaBundleMount() { // Mount the CA bundle into /etc/pki/ca-trust/extracted/pem @@ -398,16 +365,20 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex mergedCABundlesCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation] = "file" mergedCABundlesCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation] = constants.PublicCertsDir } + mergedCABundlesCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAccessModeAnnotation] = "0444" - return deploy.SyncConfigMap( + return deploy.Sync( ctx, mergedCABundlesCM, - deploy.GetDefaultKubernetesLabelsWith(), - []string{ - cheCABundleIncludedCMRevisions, - dwconstants.DevWorkspaceMountAsAnnotation, - dwconstants.DevWorkspaceMountPathAnnotation, - }) + diffs.ConfigMap( + deploy.DefaultsLabelKeys, + []string{ + dwconstants.DevWorkspaceMountAsAnnotation, + dwconstants.DevWorkspaceMountPathAnnotation, + dwconstants.DevWorkspaceMountAccessModeAnnotation, + }, + ), + ) } func readKubernetesCaBundle() ([]byte, error) { diff --git a/pkg/deploy/tls/certificates_test.go b/pkg/deploy/tls/certificates_test.go index b7b10032c9..6b438f4cb9 100644 --- a/pkg/deploy/tls/certificates_test.go +++ b/pkg/deploy/tls/certificates_test.go @@ -14,7 +14,6 @@ package tls import ( "context" - "strings" dwconstants "github.com/devfile/devworkspace-operator/pkg/constants" "k8s.io/utils/pointer" @@ -49,7 +48,6 @@ func TestSyncOpenShiftCABundleCertificates(t *testing.T) { assert.Equal(t, constants.WorkspacesConfig, caCertsMergedCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey]) assert.Equal(t, kubernetesCABundleCertsDir, caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation]) assert.Equal(t, "subpath", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation]) - assert.Equal(t, "ca-certs#1", strings.TrimSpace(caCertsMergedCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions])) assert.Empty(t, caCertsMergedCM.Data) } @@ -78,7 +76,6 @@ func TestSyncEmptyOpenShiftCABundleCertificates(t *testing.T) { assert.Equal(t, constants.WorkspacesConfig, caCertsMergedCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey]) assert.Equal(t, kubernetesCABundleCertsDir, caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation]) assert.Equal(t, "subpath", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation]) - assert.Equal(t, "ca-certs#2", strings.TrimSpace(caCertsMergedCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions])) assert.Equal(t, caCertsMergedCM.Data["tls-ca-bundle.pem"], "# ConfigMap: ca-certs, Key: ca-bundle.crt\nopenshift-ca-bundle\n\n") } @@ -243,7 +240,6 @@ 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, "cert1#1 ", cm.ObjectMeta.Annotations["che.eclipse.org/included-configmaps"]) cert2 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -264,7 +260,6 @@ 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, "cert1#1 cert2#1 ", cm.ObjectMeta.Annotations["che.eclipse.org/included-configmaps"]) assert.Equal(t, cm.Data[kubernetesCABundleCertsFile], "# ConfigMap: cert1, Key: a1\nb1\n\n# ConfigMap: cert2, Key: a2\nb2\n\n") } @@ -303,8 +298,8 @@ func TestToggleDisableWorkspaceCaBundleMount(t *testing.T) { assert.Nil(t, err) assert.Equal(t, constants.WorkspacesConfig, caCertsMergedCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey]) assert.Equal(t, kubernetesCABundleCertsDir, caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation]) + assert.Equal(t, "0444", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAccessModeAnnotation]) assert.Equal(t, "subpath", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation]) - assert.Equal(t, "ca-certs#2", strings.TrimSpace(caCertsMergedCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions])) assert.Equal(t, caCertsMergedCM.Data["tls-ca-bundle.pem"], "# ConfigMap: ca-certs, Key: ca-bundle.crt\nopenshift-ca-bundle\n\n") assert.Equal(t, 1, len(caCertsMergedCM.Data)) @@ -326,8 +321,8 @@ func TestToggleDisableWorkspaceCaBundleMount(t *testing.T) { assert.Nil(t, err) assert.Equal(t, constants.WorkspacesConfig, caCertsMergedCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey]) assert.Equal(t, constants.PublicCertsDir, caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation]) + assert.Equal(t, "0444", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAccessModeAnnotation]) assert.Equal(t, "file", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation]) - assert.Equal(t, "ca-certs#3", strings.TrimSpace(caCertsMergedCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions])) assert.Equal(t, caCertsMergedCM.Data["tls-ca-bundle.pem"], "# ConfigMap: ca-certs, Key: ca-bundle.crt\nopenshift-cert\n\n") assert.Equal(t, 1, len(caCertsMergedCM.Data)) @@ -354,8 +349,26 @@ func TestToggleDisableWorkspaceCaBundleMount(t *testing.T) { assert.Nil(t, err) assert.Equal(t, constants.WorkspacesConfig, caCertsMergedCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey]) assert.Equal(t, kubernetesCABundleCertsDir, caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation]) + assert.Equal(t, "0444", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAccessModeAnnotation]) + assert.Equal(t, "subpath", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation]) + assert.Equal(t, caCertsMergedCM.Data["tls-ca-bundle.pem"], "# ConfigMap: ca-certs, Key: ca-bundle.crt\nopenshift-ca-bundle-new\n\n") + assert.Equal(t, 1, len(caCertsMergedCM.Data)) + + // Check CM is reverted after changing the annotations + caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation] = "a" + caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation] = "b" + err = ctx.ClusterAPI.Client.Update(context.TODO(), caCertsMergedCM) + assert.NoError(t, err) + + test.EnsureReconcile(t, ctx, NewCertificatesReconciler().Reconcile) + + caCertsMergedCM = &corev1.ConfigMap{} + err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "ca-certs-merged", Namespace: "eclipse-che"}, caCertsMergedCM) + assert.Nil(t, err) + assert.Equal(t, constants.WorkspacesConfig, caCertsMergedCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey]) + assert.Equal(t, kubernetesCABundleCertsDir, caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountPathAnnotation]) + assert.Equal(t, "0444", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAccessModeAnnotation]) assert.Equal(t, "subpath", caCertsMergedCM.ObjectMeta.Annotations[dwconstants.DevWorkspaceMountAsAnnotation]) - assert.Equal(t, "ca-certs#5", strings.TrimSpace(caCertsMergedCM.ObjectMeta.Annotations[cheCABundleIncludedCMRevisions])) assert.Equal(t, caCertsMergedCM.Data["tls-ca-bundle.pem"], "# ConfigMap: ca-certs, Key: ca-bundle.crt\nopenshift-ca-bundle-new\n\n") assert.Equal(t, 1, len(caCertsMergedCM.Data)) }