diff --git a/controllers/usernamespace/workspace_cm_syncer_test.go b/controllers/usernamespace/workspace_cm_syncer_test.go index 78b324f17..2a7bf838e 100644 --- a/controllers/usernamespace/workspace_cm_syncer_test.go +++ b/controllers/usernamespace/workspace_cm_syncer_test.go @@ -82,7 +82,7 @@ func TestSyncConfigMap(t *testing.T) { }) // Sync ConfigMap - err := workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err := workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -106,7 +106,7 @@ func TestSyncConfigMap(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -130,7 +130,7 @@ func TestSyncConfigMap(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -155,7 +155,7 @@ func TestSyncConfigMap(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -177,7 +177,7 @@ func TestSyncConfigMap(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -197,7 +197,7 @@ func TestSyncConfigMap(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 0, v1ConfigMapGKV) @@ -248,7 +248,7 @@ func TestSyncConfigMapShouldMergeLabelsAndAnnotationsOnUpdate(t *testing.T) { }) // Sync ConfigMap - err := workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err := workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -273,7 +273,7 @@ func TestSyncConfigMapShouldMergeLabelsAndAnnotationsOnUpdate(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) @@ -301,7 +301,7 @@ func TestSyncConfigMapShouldMergeLabelsAndAnnotationsOnUpdate(t *testing.T) { assert.Nil(t, err) // Sync ConfigMap - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1ConfigMapGKV) diff --git a/controllers/usernamespace/workspace_pvc_syncer_test.go b/controllers/usernamespace/workspace_pvc_syncer_test.go index 0881b7b13..768bba6ee 100644 --- a/controllers/usernamespace/workspace_pvc_syncer_test.go +++ b/controllers/usernamespace/workspace_pvc_syncer_test.go @@ -73,7 +73,7 @@ func TestSyncPVC(t *testing.T) { assertSyncConfig(t, workspaceConfigReconciler, 0, v1PvcGKV) // Sync PVC to a user namespace - err := workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err := workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1PvcGKV) @@ -93,7 +93,7 @@ func TestSyncPVC(t *testing.T) { err = workspaceConfigReconciler.client.Update(context.TODO(), pvc) // Sync PVC - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1PvcGKV) @@ -110,7 +110,7 @@ func TestSyncPVC(t *testing.T) { assert.Nil(t, err) // Sync PVC - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1PvcGKV) @@ -127,7 +127,7 @@ func TestSyncPVC(t *testing.T) { assert.Nil(t, err) // Sync PVC - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 0, v1PvcGKV) diff --git a/controllers/usernamespace/workspace_secret_syncer_test.go b/controllers/usernamespace/workspace_secret_syncer_test.go index 6d4781e42..009ba49e1 100644 --- a/controllers/usernamespace/workspace_secret_syncer_test.go +++ b/controllers/usernamespace/workspace_secret_syncer_test.go @@ -73,7 +73,7 @@ func TestSyncSecrets(t *testing.T) { }) // Sync Secret - err := workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err := workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -101,7 +101,7 @@ func TestSyncSecrets(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -127,7 +127,7 @@ func TestSyncSecrets(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -153,7 +153,7 @@ func TestSyncSecrets(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -176,7 +176,7 @@ func TestSyncSecrets(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -197,7 +197,7 @@ func TestSyncSecrets(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 0, v1SecretGKV) @@ -248,7 +248,7 @@ func TestSyncSecretShouldMergeLabelsAndAnnotationsOnUpdate(t *testing.T) { }) // Sync Secret - err := workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err := workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -273,7 +273,7 @@ func TestSyncSecretShouldMergeLabelsAndAnnotationsOnUpdate(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) @@ -301,7 +301,7 @@ func TestSyncSecretShouldMergeLabelsAndAnnotationsOnUpdate(t *testing.T) { assert.Nil(t, err) // Sync Secret - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1SecretGKV) diff --git a/controllers/usernamespace/workspace_unstructured_syncer.go b/controllers/usernamespace/workspace_unstructured_syncer.go index e13521e1d..827513aff 100644 --- a/controllers/usernamespace/workspace_unstructured_syncer.go +++ b/controllers/usernamespace/workspace_unstructured_syncer.go @@ -26,8 +26,8 @@ import ( const ( // Supported templates parameters - PROJECT_USER = "${PROJECT_USER}" - PROJECT_NAME = "${PROJECT_NAME}" + PROJECT_ADMIN_USER = "${PROJECT_ADMIN_USER}" + PROJECT_NAME = "${PROJECT_NAME}" ) type unstructuredSyncer struct { @@ -40,14 +40,14 @@ type unstructuredSyncer struct { func newUnstructuredSyncer( raw []byte, - user string, - project string) (*unstructuredSyncer, error) { + userName string, + namespaceName string) (*unstructuredSyncer, error) { hash := utils.ComputeHash256(raw) objAsString := string(raw) - objAsString = strings.ReplaceAll(objAsString, PROJECT_USER, user) - objAsString = strings.ReplaceAll(objAsString, PROJECT_NAME, project) + objAsString = strings.ReplaceAll(objAsString, PROJECT_ADMIN_USER, userName) + objAsString = strings.ReplaceAll(objAsString, PROJECT_NAME, namespaceName) srcObj := &unstructured.Unstructured{} if err := yaml.Unmarshal([]byte(objAsString), srcObj); err != nil { @@ -74,8 +74,8 @@ func (p *unstructuredSyncer) getGKV() schema.GroupVersionKind { func (p *unstructuredSyncer) newDstObject() client.Object { dstObj := p.dstObj.DeepCopyObject().(client.Object) - switch dstObj.GetObjectKind().GroupVersionKind().String() { - case v1ConfigMapGKV.String(): + switch dstObj.GetObjectKind().GroupVersionKind() { + case v1ConfigMapGKV: dstObj.SetLabels(utils.MergeMaps([]map[string]string{ dstObj.GetLabels(), { @@ -84,7 +84,7 @@ func (p *unstructuredSyncer) newDstObject() client.Object { }}), ) break - case v1SecretGKV.String(): + case v1SecretGKV: dstObj.SetLabels(utils.MergeMaps([]map[string]string{ dstObj.GetLabels(), { @@ -103,9 +103,5 @@ func (p *unstructuredSyncer) getSrcObjectVersion() string { } func (p *unstructuredSyncer) hasROSpec() bool { - switch p.dstObj.GetObjectKind().GroupVersionKind().String() { - case v1PvcGKV.String(): - return true - } - return false + return p.dstObj.GetObjectKind().GroupVersionKind() == v1PvcGKV } diff --git a/controllers/usernamespace/workspace_unstructured_syncer_test.go b/controllers/usernamespace/workspace_unstructured_syncer_test.go index f0b6eabc9..16cc298a9 100644 --- a/controllers/usernamespace/workspace_unstructured_syncer_test.go +++ b/controllers/usernamespace/workspace_unstructured_syncer_test.go @@ -65,7 +65,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: objectName, Labels: map[string]string{ - "user": "${PROJECT_USER}", + "user": "${PROJECT_ADMIN_USER}", "namespace": "${PROJECT_NAME}", }, }, @@ -97,7 +97,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { }) // Sync Template - err := workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err := workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1LimitRangeGKV) @@ -139,7 +139,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { assert.Nil(t, err) // Sync Template - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1LimitRangeGKV) @@ -160,7 +160,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { assert.Nil(t, err) // Sync Template - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1LimitRangeGKV) @@ -182,7 +182,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { assert.Nil(t, err) // Sync Template - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1LimitRangeGKV) @@ -201,7 +201,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { assert.Nil(t, err) // Sync Template - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 2, v1LimitRangeGKV) @@ -218,7 +218,7 @@ func TestSyncTemplateWithLimitRange(t *testing.T) { assert.Nil(t, err) // Sync Template - err = workspaceConfigReconciler.syncWorkspace(context.TODO(), userNamespace) + err = workspaceConfigReconciler.syncWorkspace(context.TODO(), eclipseCheNamespace, userNamespace) assert.Nil(t, err) assertSyncConfig(t, workspaceConfigReconciler, 0, v1LimitRangeGKV) diff --git a/controllers/usernamespace/workspaces_config_controller.go b/controllers/usernamespace/workspaces_config_controller.go index 158695a35..ce61f3ee3 100644 --- a/controllers/usernamespace/workspaces_config_controller.go +++ b/controllers/usernamespace/workspaces_config_controller.go @@ -113,6 +113,12 @@ func (r *WorkspacesConfigReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } + checluster, err := deploy.FindCheClusterCRInNamespace(r.client, "") + if checluster == nil { + // There is no CheCluster CR, the source namespace is unknown + return ctrl.Result{}, nil + } + info, err := r.namespaceCache.ExamineNamespace(ctx, req.Name) if err != nil { logger.Error(err, "Failed to examine namespace", "namespace", req.Name) @@ -124,7 +130,12 @@ func (r *WorkspacesConfigReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } - if err = r.syncWorkspace(ctx, req.Name); err != nil { + if info.Username == "" { + logger.Info("Username is not set for the namespace", "namespace", req.Name) + return ctrl.Result{}, nil + } + + if err = r.syncWorkspace(ctx, checluster.Namespace, req.Name); err != nil { logger.Error(err, "Failed to sync workspace configs", "namespace", req.Name) return ctrl.Result{}, err } @@ -182,28 +193,16 @@ func (r *WorkspacesConfigReconciler) watchRules( // syncWorkspace sync user namespace. // Iterates over all objects in the source namespace labeled as `app.kubernetes.io/component=workspaces-config` // and syncs them to the target user namespace. -func (r *WorkspacesConfigReconciler) syncWorkspace(ctx context.Context, dstNamespace string) error { - checluster, err := deploy.FindCheClusterCRInNamespace(r.client, "") - if checluster == nil { - // There is no CheCluster CR, the source namespace is unknown - return nil - } - +func (r *WorkspacesConfigReconciler) syncWorkspace( + ctx context.Context, + srcNamespace string, + dstNamespace string, +) error { syncConfig, err := r.getSyncConfig(ctx, dstNamespace) if err != nil { return err } - info, err := r.namespaceCache.GetNamespaceInfo(context.TODO(), dstNamespace) - if err != nil { - return err - } - - if info.Username == "" { - logger.Info("Username is not set for the namespace", "namespace", dstNamespace) - return nil - } - defer func() { // Update sync config in the end of the reconciliation // despite the result of the reconciliation @@ -226,7 +225,7 @@ func (r *WorkspacesConfigReconciler) syncWorkspace(ctx context.Context, dstNames if infrastructure.IsOpenShift() { if err = r.syncTemplates( ctx, - checluster.Namespace, + srcNamespace, dstNamespace, syncConfig.Data, syncedSrcObjKeys, @@ -237,7 +236,7 @@ func (r *WorkspacesConfigReconciler) syncWorkspace(ctx context.Context, dstNames if err = r.syncConfigMaps( ctx, - checluster.Namespace, + srcNamespace, dstNamespace, syncConfig.Data, syncedSrcObjKeys, @@ -247,7 +246,7 @@ func (r *WorkspacesConfigReconciler) syncWorkspace(ctx context.Context, dstNames if err = r.syncSecretes( ctx, - checluster.Namespace, + srcNamespace, dstNamespace, syncConfig.Data, syncedSrcObjKeys, @@ -257,7 +256,7 @@ func (r *WorkspacesConfigReconciler) syncWorkspace(ctx context.Context, dstNames if err = r.syncPVCs( ctx, - checluster.Namespace, + srcNamespace, dstNamespace, syncConfig.Data, syncedSrcObjKeys, @@ -271,7 +270,7 @@ func (r *WorkspacesConfigReconciler) syncWorkspace(ctx context.Context, dstNames if err := r.deleteIfObjectIsObsolete( objKey, ctx, - checluster.Namespace, + srcNamespace, dstNamespace, syncConfig.Data, syncedSrcObjKeys); err != nil { diff --git a/controllers/usernamespace/workspaces_config_controller_test.go b/controllers/usernamespace/workspaces_config_controller_test.go index 586d42d96..5becdfbf1 100644 --- a/controllers/usernamespace/workspaces_config_controller_test.go +++ b/controllers/usernamespace/workspaces_config_controller_test.go @@ -198,7 +198,7 @@ func TestBuildKey(t *testing.T) { assert.Equal(t, testCase.name, getNameItem(key)) assert.Equal(t, testCase.namespace, getNamespaceItem(key)) - assert.Equal(t, testCase.gkv.String(), item2gkv(getGkvItem(key)).String()) + assert.Equal(t, testCase.gkv, item2gkv(getGkvItem(key))) }) } } diff --git a/pkg/deploy/sync/sync.go b/pkg/deploy/sync/sync.go new file mode 100644 index 000000000..c884e9f67 --- /dev/null +++ b/pkg/deploy/sync/sync.go @@ -0,0 +1,231 @@ +// +// Copyright (c) 2019-2024 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 sync + +import ( + "context" + "fmt" + "reflect" + + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var ( + syncLog = ctrl.Log.WithName("sync") +) + +type Syncer interface { + // Get reads object. + // Returns true if object exists otherwise returns false. + // Returns error if object cannot be retrieved otherwise returns nil. + Get(key client.ObjectKey, actual client.Object) (bool, error) + // CreateIgnoreIfExists creates object. + // Set owner reference for Eclipse Che namespace objects. + // Return true if a new object is created or object already exists, otherwise returns false. + // Returns error if object cannot be created otherwise returns nil. + CreateIgnoreIfExists(blueprint client.Object) (bool, error) + // Delete deletes object. + // Returns true if object deleted or not found otherwise returns false. + // Returns error if object cannot be deleted otherwise returns nil. + Delete(key client.ObjectKey, objectMeta client.Object) (bool, error) + // Sync syncs the blueprint to the cluster in a generic (as much as Go allows) manner. + // Returns true if object is up-to-date otherwise returns false + // Returns error if object cannot be created/updated otherwise returns nil. + Sync(blueprint client.Object, diffOpts ...cmp.Option) (bool, error) +} + +type ObjSyncer struct { + Syncer + + cheCluster *chev2.CheCluster + scheme *runtime.Scheme + cli client.Client + ctx context.Context +} + +func (s *ObjSyncer) Get(key client.ObjectKey, actual client.Object) (bool, error) { + return s.doGet(key, actual) +} + +func (s *ObjSyncer) CreateIgnoreIfExists(blueprint client.Object) (bool, error) { + return s.doCreate(blueprint, true) +} + +func (s *ObjSyncer) Delete(key client.ObjectKey, objectMeta client.Object) (bool, error) { + return s.doDelete(key, objectMeta) +} + +func (s *ObjSyncer) Sync(blueprint client.Object, diffOpts ...cmp.Option) (bool, error) { + runtimeObject, ok := blueprint.(runtime.Object) + if !ok { + return false, fmt.Errorf("object %T is not a runtime.Object. Cannot sync it", runtimeObject) + } + + // we will compare this object later with blueprint + // we can't use runtimeObject.DeepCopyObject() + actual, err := s.scheme.New(runtimeObject.GetObjectKind().GroupVersionKind()) + if err != nil { + return false, err + } + + key := types.NamespacedName{Name: blueprint.GetName(), Namespace: blueprint.GetNamespace()} + exists, err := s.doGet(key, actual.(client.Object)) + if err != nil { + return false, err + } + + // set GroupVersionKind (it might be empty) + actual.GetObjectKind().SetGroupVersionKind(runtimeObject.GetObjectKind().GroupVersionKind()) + if !exists { + return s.doCreate(blueprint, false) + } + + return s.doUpdate(actual.(client.Object), blueprint, diffOpts...) +} + +func (s *ObjSyncer) doUpdate( + actual client.Object, + blueprint client.Object, + diffOpts ...cmp.Option, +) (bool, error) { + actualMeta, ok := actual.(metav1.Object) + if !ok { + return false, fmt.Errorf("object %T is not a metav1.Object. Cannot update it", actualMeta) + } + + diff := cmp.Diff(actual, blueprint, diffOpts...) + if len(diff) > 0 { + // don't print difference if there are no diffOpts mainly to avoid huge output + if len(diffOpts) != 0 { + fmt.Printf("Difference:\n%s", diff) + } + + if isUpdateUsingDeleteCreate(actual.GetObjectKind().GroupVersionKind().Kind) { + done, err := s.doDeleteIgnoreIfNotFound(actual) + if !done { + return false, err + } + return s.doCreate(blueprint, false) + } else { + err := s.setOwnerReferenceForCheNamespaceObject(blueprint) + if err != nil { + return false, err + } + + // to be able to update, we need to set the resource version of the object that we know of + blueprint.(metav1.Object).SetResourceVersion(actualMeta.GetResourceVersion()) + err = s.cli.Update(context.TODO(), blueprint) + if err == nil { + syncLog.Info("Object updated", "namespace", actual.GetNamespace(), "kind", GetObjectType(actual), "name", actual.GetName()) + } + return false, err + } + } + + return true, nil +} + +func (s *ObjSyncer) doDelete(key client.ObjectKey, objectMeta client.Object) (bool, error) { + runtimeObject, ok := objectMeta.(runtime.Object) + if !ok { + return false, fmt.Errorf("object %T is not a runtime.Object. Cannot delete it", runtimeObject) + } + + actual := runtimeObject.DeepCopyObject().(client.Object) + exists, err := s.doGet(key, actual) + if !exists { + return true, nil + } else if err != nil { + return false, err + } + + return s.doDeleteIgnoreIfNotFound(actual) +} + +func (s *ObjSyncer) doDeleteIgnoreIfNotFound(actual client.Object) (bool, error) { + err := s.cli.Delete(s.ctx, actual) + if err == nil { + if errors.IsNotFound(err) { + syncLog.Info("Object not found", "namespace", actual.GetNamespace(), "kind", GetObjectType(actual), "name", actual.GetName()) + } else { + syncLog.Info("Object deleted", "namespace", actual.GetNamespace(), "kind", GetObjectType(actual), "name", actual.GetName()) + } + return true, nil + } else { + return false, err + } +} + +func (s *ObjSyncer) doGet( + key client.ObjectKey, + object client.Object, +) (bool, error) { + err := s.cli.Get(s.ctx, key, object) + if err == nil { + return true, nil + } else if errors.IsNotFound(err) { + return false, nil + } else { + return false, err + } +} + +func (s *ObjSyncer) doCreate( + blueprint client.Object, + returnTrueIfAlreadyExists bool, +) (bool, error) { + err := s.setOwnerReferenceForCheNamespaceObject(blueprint) + if err != nil { + return false, err + } + + err = s.cli.Create(s.ctx, blueprint) + if err == nil { + syncLog.Info("Object created", "namespace", blueprint.GetNamespace(), "kind", GetObjectType(blueprint), "name", blueprint.GetName()) + return true, nil + } else if errors.IsAlreadyExists(err) { + return returnTrueIfAlreadyExists, nil + } else { + return false, err + } +} + +func (s *ObjSyncer) setOwnerReferenceForCheNamespaceObject(blueprint metav1.Object) error { + if blueprint.GetNamespace() == s.cheCluster.Namespace { + return controllerutil.SetControllerReference(s.cheCluster, blueprint, s.scheme) + } + + // cluster scope object (empty workspace) or object in another namespace + return nil +} + +func GetObjectType(obj interface{}) string { + objType := reflect.TypeOf(obj).String() + if reflect.TypeOf(obj).Kind().String() == "ptr" { + objType = objType[1:] + } + + return objType +} + +func isUpdateUsingDeleteCreate(kind string) bool { + return "Service" == kind || "Ingress" == kind || "Route" == kind || "Job" == kind || "Secret" == kind +}