Skip to content

Commit

Permalink
refactor: make syncer generic
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianKramm committed Jul 24, 2024
1 parent dffcd55 commit d3060e6
Show file tree
Hide file tree
Showing 53 changed files with 1,110 additions and 892 deletions.
64 changes: 33 additions & 31 deletions pkg/controllers/generic/export_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,24 @@ type exporter struct {
name string
}

func (f *exporter) SyncToHost(ctx *synccontext.SyncContext, vObj client.Object) (ctrl.Result, error) {
func (f *exporter) Syncer() syncertypes.Sync[client.Object] {
return syncer.ToGenericSyncer[*unstructured.Unstructured](f)
}

func (f *exporter) SyncToHost(ctx *synccontext.SyncContext, event *synccontext.SyncToHostEvent[*unstructured.Unstructured]) (ctrl.Result, error) {
// check if selector matches
if !f.objectMatches(vObj) {
if !f.objectMatches(event.Virtual) {
return ctrl.Result{}, nil
}

// delete object if host was deleted
if ctx.IsDelete {
return syncer.DeleteVirtualObject(ctx, vObj, "host object was deleted")
if event.IsDelete() {
return syncer.DeleteVirtualObject(ctx, event.Virtual, "host object was deleted")
}

// apply object to physical cluster
ctx.Log.Infof("Create physical %s %s/%s, since it is missing, but virtual object exists", f.gvk.Kind, vObj.GetNamespace(), vObj.GetName())
pObj, err := f.patcher.ApplyPatches(ctx, vObj, nil, f)
ctx.Log.Infof("Create physical %s %s/%s, since it is missing, but virtual object exists", f.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName())
pObj, err := f.patcher.ApplyPatches(ctx, event.Virtual, nil, f)
if kerrors.IsConflict(err) {
return ctrl.Result{Requeue: true}, nil
}
Expand All @@ -154,7 +158,7 @@ func (f *exporter) SyncToHost(ctx *synccontext.SyncContext, vObj client.Object)
return ctrl.Result{}, nil
}

f.EventRecorder().Eventf(vObj, "Warning", "SyncError", "Error syncing to physical cluster: %v", err)
f.EventRecorder().Eventf(event.Virtual, "Warning", "SyncError", "Error syncing to physical cluster: %v", err)
return ctrl.Result{}, fmt.Errorf("error applying patches: %w", err)
} else if pObj == nil {
return ctrl.Result{}, nil
Expand Down Expand Up @@ -183,31 +187,31 @@ func (f *exporter) SyncToHost(ctx *synccontext.SyncContext, vObj client.Object)
return ctrl.Result{}, nil
}

func (f *exporter) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj client.Object) (ctrl.Result, error) {
func (f *exporter) Sync(ctx *synccontext.SyncContext, event *synccontext.SyncEvent[*unstructured.Unstructured]) (ctrl.Result, error) {
// check if virtual object is not matching anymore
if !f.objectMatches(vObj) {
ctx.Log.Infof("delete physical %s %s/%s, because it is not used anymore", f.gvk.Kind, pObj.GetNamespace(), pObj.GetName())
err := ctx.PhysicalClient.Delete(ctx, pObj, &client.DeleteOptions{
if !f.objectMatches(event.Virtual) {
ctx.Log.Infof("delete physical %s %s/%s, because it is not used anymore", f.gvk.Kind, event.Host.GetNamespace(), event.Host.GetName())
err := ctx.PhysicalClient.Delete(ctx, event.Host, &client.DeleteOptions{
GracePeriodSeconds: &[]int64{0}[0],
})
if err != nil {
ctx.Log.Infof("error deleting physical %s %s/%s in physical cluster: %v", f.gvk.Kind, pObj.GetNamespace(), pObj.GetName(), err)
ctx.Log.Infof("error deleting physical %s %s/%s in physical cluster: %v", f.gvk.Kind, event.Host.GetNamespace(), event.Host.GetName(), err)
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

// check if either object is getting deleted
if vObj.GetDeletionTimestamp() != nil || pObj.GetDeletionTimestamp() != nil {
if pObj.GetDeletionTimestamp() == nil {
ctx.Log.Infof("delete physical object %s/%s, because the virtual object is being deleted", pObj.GetNamespace(), pObj.GetName())
if err := ctx.PhysicalClient.Delete(ctx, pObj); err != nil {
if event.Virtual.GetDeletionTimestamp() != nil || event.Host.GetDeletionTimestamp() != nil {
if event.Host.GetDeletionTimestamp() == nil {
ctx.Log.Infof("delete physical object %s/%s, because the virtual object is being deleted", event.Host.GetNamespace(), event.Host.GetName())
if err := ctx.PhysicalClient.Delete(ctx, event.Host); err != nil {
return ctrl.Result{}, err
}
} else if vObj.GetDeletionTimestamp() == nil {
ctx.Log.Infof("delete virtual object %s/%s, because physical object %s/%s is being deleted", vObj.GetNamespace(), vObj.GetName(), pObj.GetNamespace(), pObj.GetName())
if err := ctx.VirtualClient.Delete(ctx, vObj); err != nil {
} else if event.Virtual.GetDeletionTimestamp() == nil {
ctx.Log.Infof("delete virtual object %s/%s, because physical object %s/%s is being deleted", event.Virtual.GetNamespace(), event.Virtual.GetName(), event.Host.GetNamespace(), event.Host.GetName())
if err := ctx.VirtualClient.Delete(ctx, event.Virtual); err != nil {
return ctrl.Result{}, nil
}
}
Expand All @@ -216,35 +220,35 @@ func (f *exporter) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj c
}

// apply reverse patches
result, err := f.patcher.ApplyReversePatches(ctx, vObj, pObj, f)
result, err := f.patcher.ApplyReversePatches(ctx, event.Virtual, event.Host, f)
if err != nil {
if kerrors.IsConflict(err) {
return ctrl.Result{Requeue: true}, nil
}
if kerrors.IsInvalid(err) {
ctx.Log.Infof("Warning: this message could indicate a timing issue with no significant impact, or a bug. Please report this if your resource never reaches the expected state. Error message: failed to patch virtual %s %s/%s: %v", f.gvk.Kind, vObj.GetNamespace(), vObj.GetName(), err)
ctx.Log.Infof("Warning: this message could indicate a timing issue with no significant impact, or a bug. Please report this if your resource never reaches the expected state. Error message: failed to patch virtual %s %s/%s: %v", f.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName(), err)
// this happens when some field is being removed shortly after being added, which suggest it's a timing issue
// it doesn't seem to have any negative consequence besides the logged error message
return ctrl.Result{Requeue: true}, nil
}

f.EventRecorder().Eventf(vObj, "Warning", "SyncError", "Error syncing to virtual cluster: %v", err)
return ctrl.Result{}, fmt.Errorf("failed to patch virtual %s %s/%s: %w", f.gvk.Kind, vObj.GetNamespace(), vObj.GetName(), err)
f.EventRecorder().Eventf(event.Virtual, "Warning", "SyncError", "Error syncing to virtual cluster: %v", err)
return ctrl.Result{}, fmt.Errorf("failed to patch virtual %s %s/%s: %w", f.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName(), err)
} else if result == controllerutil.OperationResultUpdated || result == controllerutil.OperationResultUpdatedStatus || result == controllerutil.OperationResultUpdatedStatusOnly {
// a change will trigger reconciliation anyway, and at that point we can make
// a more accurate updates(reverse patches) to the virtual resource
return ctrl.Result{}, nil
}

// apply patches
pObj, err = f.patcher.ApplyPatches(ctx, vObj, pObj, f)
pObj, err := f.patcher.ApplyPatches(ctx, event.Virtual, event.Host, f)
err = IgnoreAcceptableErrors(err)
if err != nil {
// when invalid, auto delete and recreate to recover
if kerrors.IsInvalid(err) && f.replaceWhenInvalid {
// Replace the object
ctx.Log.Infof("Replace physical object, because apply failed: %v", err)
err = ctx.PhysicalClient.Delete(ctx, pObj, &client.DeleteOptions{
err = ctx.PhysicalClient.Delete(ctx, event.Host, &client.DeleteOptions{
GracePeriodSeconds: &[]int64{0}[0],
})
if err != nil {
Expand All @@ -257,7 +261,7 @@ func (f *exporter) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj c
return ctrl.Result{Requeue: true}, nil
}

f.EventRecorder().Eventf(vObj, "Warning", "SyncError", "Error syncing to physical cluster: %v", err)
f.EventRecorder().Eventf(event.Virtual, "Warning", "SyncError", "Error syncing to physical cluster: %v", err)
return ctrl.Result{}, fmt.Errorf("error applying patches: %w", err)
} else if pObj == nil {
return ctrl.Result{}, nil
Expand All @@ -266,18 +270,16 @@ func (f *exporter) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj c
return ctrl.Result{}, nil
}

var _ syncertypes.ToVirtualSyncer = &exporter{}

func (f *exporter) SyncToVirtual(ctx *synccontext.SyncContext, pObj client.Object) (ctrl.Result, error) {
isManaged, err := f.GenericTranslator.IsManaged(ctx, pObj)
func (f *exporter) SyncToVirtual(ctx *synccontext.SyncContext, event *synccontext.SyncToVirtualEvent[*unstructured.Unstructured]) (ctrl.Result, error) {
isManaged, err := f.GenericTranslator.IsManaged(ctx, event.Host)
if err != nil {
return ctrl.Result{}, err
} else if !isManaged {
return ctrl.Result{}, nil
}

// delete physical object because virtual one is missing
return syncer.DeleteHostObject(ctx, pObj, fmt.Sprintf("delete physical %s because virtual is missing", pObj.GetName()))
return syncer.DeleteHostObject(ctx, event.Host, fmt.Sprintf("delete physical %s because virtual is missing", event.Host.GetName()))
}

func (f *exporter) Name() string {
Expand Down
70 changes: 36 additions & 34 deletions pkg/controllers/generic/import_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,18 @@ func (s *importer) Options() *syncertypes.Options {
return s.syncerOptions
}

var _ syncertypes.ToVirtualSyncer = &importer{}
var _ syncertypes.Syncer = &importer{}

func (s *importer) Syncer() syncertypes.Sync[client.Object] {
return syncer.ToGenericSyncer[*unstructured.Unstructured](s)
}

func (s *importer) SyncToVirtual(ctx *synccontext.SyncContext, pObj client.Object) (ctrl.Result, error) {
func (s *importer) SyncToVirtual(ctx *synccontext.SyncContext, event *synccontext.SyncToVirtualEvent[*unstructured.Unstructured]) (ctrl.Result, error) {
// check if annotation is already present
pAnnotations := pObj.GetAnnotations()
pAnnotations := event.Host.GetAnnotations()
if pAnnotations != nil && pAnnotations[translate.ControllerLabel] == s.Name() && !s.syncerOptions.IsClusterScopedCRD { // only delete pObj if its not cluster scoped
ctx.Log.Infof("Delete physical %s %s/%s, since virtual is missing, but physical object was already synced", s.gvk.Kind, pObj.GetNamespace(), pObj.GetName())
err := ctx.PhysicalClient.Delete(ctx, pObj)
ctx.Log.Infof("Delete physical %s %s/%s, since virtual is missing, but physical object was already synced", s.gvk.Kind, event.Host.GetNamespace(), event.Host.GetName())
err := ctx.PhysicalClient.Delete(ctx, event.Host)
if err != nil && !kerrors.IsNotFound(err) {
return ctrl.Result{}, err
}
Expand All @@ -153,8 +157,8 @@ func (s *importer) SyncToVirtual(ctx *synccontext.SyncContext, pObj client.Objec
}

// apply object to virtual cluster
ctx.Log.Infof("Create virtual %s, since it is missing, but physical object %s/%s exists", s.gvk.Kind, pObj.GetNamespace(), pObj.GetName())
vObj, err := s.patcher.ApplyPatches(ctx, pObj, nil, s)
ctx.Log.Infof("Create virtual %s, since it is missing, but physical object %s/%s exists", s.gvk.Kind, event.Host.GetNamespace(), event.Host.GetName())
vObj, err := s.patcher.ApplyPatches(ctx, event.Host, nil, s)
if err != nil {
if err := IgnoreAcceptableErrors(err); err != nil {
return ctrl.Result{}, nil
Expand All @@ -168,7 +172,7 @@ func (s *importer) SyncToVirtual(ctx *synccontext.SyncContext, pObj client.Objec
}

// add annotation to physical resource to mark it as controlled by this syncer
err = s.addAnnotationsToPhysicalObject(ctx, pObj, vObj)
err = s.addAnnotationsToPhysicalObject(ctx, event.Host, vObj)
if err != nil {
return ctrl.Result{}, err
}
Expand Down Expand Up @@ -196,34 +200,32 @@ func (s *importer) SyncToVirtual(ctx *synccontext.SyncContext, pObj client.Objec
return ctrl.Result{}, nil
}

var _ syncertypes.Syncer = &importer{}

func (s *importer) GroupVersionKind() schema.GroupVersionKind {
return s.gvk
}

func (s *importer) SyncToHost(ctx *synccontext.SyncContext, vObj client.Object) (ctrl.Result, error) {
func (s *importer) SyncToHost(ctx *synccontext.SyncContext, event *synccontext.SyncToHostEvent[*unstructured.Unstructured]) (ctrl.Result, error) {
// ignore all virtual resources that were not created by this controller
if !s.isVirtualManaged(vObj) {
if !s.isVirtualManaged(event.Virtual) {
return ctrl.Result{}, nil
}

// should we delete the object?
if vObj.GetDeletionTimestamp() == nil {
ctx.Log.Infof("remove virtual %s %s/%s, because object should get deleted", s.gvk.Kind, vObj.GetNamespace(), vObj.GetName())
return ctrl.Result{}, ctx.VirtualClient.Delete(ctx, vObj)
if event.Virtual.GetDeletionTimestamp() == nil {
ctx.Log.Infof("remove virtual %s %s/%s, because object should get deleted", s.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName())
return ctrl.Result{}, ctx.VirtualClient.Delete(ctx, event.Virtual)
}

// remove finalizers if there are any
if len(vObj.GetFinalizers()) > 0 {
if len(event.Virtual.GetFinalizers()) > 0 {
// delete the finalizer here so that the object can be deleted
vObj.SetFinalizers([]string{})
ctx.Log.Infof("remove virtual %s %s/%s finalizers, because object should get deleted", s.gvk.Kind, vObj.GetNamespace(), vObj.GetName())
return ctrl.Result{}, ctx.VirtualClient.Update(ctx, vObj)
event.Virtual.SetFinalizers([]string{})
ctx.Log.Infof("remove virtual %s %s/%s finalizers, because object should get deleted", s.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName())
return ctrl.Result{}, ctx.VirtualClient.Update(ctx, event.Virtual)
}

// force deletion
err := ctx.VirtualClient.Delete(ctx, vObj, &client.DeleteOptions{
err := ctx.VirtualClient.Delete(ctx, event.Virtual, &client.DeleteOptions{
GracePeriodSeconds: &[]int64{0}[0],
})
if kerrors.IsNotFound(err) {
Expand All @@ -232,25 +234,25 @@ func (s *importer) SyncToHost(ctx *synccontext.SyncContext, vObj client.Object)
return ctrl.Result{}, err
}

func (s *importer) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj client.Object) (ctrl.Result, error) {
func (s *importer) Sync(ctx *synccontext.SyncContext, event *synccontext.SyncEvent[*unstructured.Unstructured]) (ctrl.Result, error) {
// check if physical object is managed by this import controller
managed, err := s.IsManaged(ctx, pObj)
managed, err := s.IsManaged(ctx, event.Host)
if err != nil {
return ctrl.Result{}, err
} else if !managed {
return ctrl.Result{}, nil
}

// check if either object is getting deleted
if vObj.GetDeletionTimestamp() != nil || pObj.GetDeletionTimestamp() != nil {
if pObj.GetDeletionTimestamp() == nil && !s.syncerOptions.IsClusterScopedCRD {
ctx.Log.Infof("delete physical object %s/%s, because the virtual object is being deleted", pObj.GetNamespace(), pObj.GetName())
if err := ctx.PhysicalClient.Delete(ctx, pObj); err != nil {
if event.Virtual.GetDeletionTimestamp() != nil || event.Host.GetDeletionTimestamp() != nil {
if event.Host.GetDeletionTimestamp() == nil && !s.syncerOptions.IsClusterScopedCRD {
ctx.Log.Infof("delete physical object %s/%s, because the virtual object is being deleted", event.Host.GetNamespace(), event.Host.GetName())
if err := ctx.PhysicalClient.Delete(ctx, event.Host); err != nil {
return ctrl.Result{}, err
}
} else if vObj.GetDeletionTimestamp() == nil {
ctx.Log.Infof("delete virtual object %s/%s, because physical object is being deleted", vObj.GetNamespace(), vObj.GetName())
if err := ctx.VirtualClient.Delete(ctx, vObj); err != nil {
} else if event.Virtual.GetDeletionTimestamp() == nil {
ctx.Log.Infof("delete virtual object %s/%s, because physical object is being deleted", event.Virtual.GetNamespace(), event.Virtual.GetName())
if err := ctx.VirtualClient.Delete(ctx, event.Virtual); err != nil {
return ctrl.Result{}, nil
}
}
Expand All @@ -259,24 +261,24 @@ func (s *importer) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj c
}

// execute reverse patches
result, err := s.patcher.ApplyReversePatches(ctx, pObj, vObj, s)
result, err := s.patcher.ApplyReversePatches(ctx, event.Host, event.Virtual, s)
if err != nil {
if kerrors.IsInvalid(err) {
ctx.Log.Infof("Warning: this message could indicate a timing issue with no significant impact, or a bug. Please report this if your resource never reaches the expected state. Error message: failed to patch virtual %s %s/%s: %v", s.gvk.Kind, vObj.GetNamespace(), vObj.GetName(), err)
ctx.Log.Infof("Warning: this message could indicate a timing issue with no significant impact, or a bug. Please report this if your resource never reaches the expected state. Error message: failed to patch virtual %s %s/%s: %v", s.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName(), err)
// this happens when some field is being removed shortly after being added, which suggest it's a timing issue
// it doesn't seem to have any negative consequence besides the logged error message
return ctrl.Result{Requeue: true}, nil
}

return ctrl.Result{}, fmt.Errorf("failed to apply reverse patch on physical %s %s/%s: %w", s.gvk.Kind, vObj.GetNamespace(), vObj.GetName(), err)
return ctrl.Result{}, fmt.Errorf("failed to apply reverse patch on physical %s %s/%s: %w", s.gvk.Kind, event.Virtual.GetNamespace(), event.Virtual.GetName(), err)
} else if result == controllerutil.OperationResultUpdated || result == controllerutil.OperationResultUpdatedStatus || result == controllerutil.OperationResultUpdatedStatusOnly {
// a change will trigger reconciliation anyway, and at that point we can make
// a more accurate updates(reverse patches) to the virtual resource
return ctrl.Result{}, nil
}

// apply patches
vObj, err = s.patcher.ApplyPatches(ctx, pObj, vObj, s)
vObj, err := s.patcher.ApplyPatches(ctx, event.Host, event.Virtual, s)
err = IgnoreAcceptableErrors(err)
if err != nil {
// when invalid, auto delete and recreate to recover
Expand All @@ -302,7 +304,7 @@ func (s *importer) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj c
}

// ensure that annotation on physical resource to mark it as controlled by this syncer is present
return ctrl.Result{}, s.addAnnotationsToPhysicalObject(ctx, pObj, vObj)
return ctrl.Result{}, s.addAnnotationsToPhysicalObject(ctx, event.Host, vObj)
}

var _ syncertypes.ObjectExcluder = &importer{}
Expand Down
Loading

0 comments on commit d3060e6

Please sign in to comment.