Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResourceManager -> ObjectManager #548

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ Within an existing Kubebuilder or controller-runtime project, reconcilers.io may
- [Tracker](#tracker)
- [Status](#status)
- [Finalizers](#finalizers)
- [ResourceManager](#resourcemanager)
- [ObjectManager](#objectmanager)
- [UpdatingObjectManager](#updatingobjectmanager)
- [Time](#time)
- [Breaking Changes](#breaking-changes)
- [Current Deprecations](#current-deprecations)
Expand Down Expand Up @@ -1119,17 +1120,26 @@ A minimal test case for a sub reconciler that adds a finalizer may look like:
...
```

### ResourceManager
### ObjectManager

The [`ResourceManager`](https://pkg.go.dev/reconciler.io/runtime/reconcilers#ResourceManager) provides a means to manage a single resource by synchronizing the current and desired state. The resource will be created if it does not exist, deleted if no longer desired and updated when semantically different. The same resource manager should be reused to manage multiple resources and must be reused when managing the same resource over time in order to take full effect. This utility is used by the [ChildReconciler](#childreconciler) and [AggregateReconciler](#aggregatereconciler).
The [`ObjectManager`](https://pkg.go.dev/reconciler.io/runtime/reconcilers#ObjectManager) is an interface providing a means to manage a single resource by synchronizing the current and desired state. The resource will be created if it does not exist, deleted if no longer desired and updated when semantically different. The same resource manager should be reused to manage multiple resources and must be reused when managing the same resource over time in order to take full effect. This utility is used by the [ChildReconciler](#childreconciler), [ChildSetReconciler](#childsetreconciler) and [AggregateReconciler](#aggregatereconciler).

The interface is designed to allow for multiple synchronization strategies to be used.

The `Manage(ctx context.Context, resource, actual, desired client.Object) (client.Object, error)` method take three objects and returns another object:
- `resource` is the reconciled resource, events, tracks and finalizer are against this object. May be an object of any underlaying type.
- `actual` the resource that exists on the API Server. Must be compatible with the `Type`.
- `desired` the resoruce that should exist on the API Server after this call. Must be compatible with the `Type`.
- `desired` the resource that should exist on the API Server after this call. Must be compatible with the `Type`.
- the returned object is the value as persisted by the API Server.

Internally, a mutations made to the resoruce at admission time (like defaults applied by a mutating webhook) are captured and reapplied to the desired state before checking if an update is needed. This reduces requests that are functionally a no-op but create churn on the API Server. The mutation cache is defensive and fails open to make an API request.
Use a provided ObjectManager or define a custom strategy to change specific behavior or employ entirely new approaches to sync state to the API Server.

<a name="resourcemanager">
#### UpdatingObjectManager

The [`UpdatingObjectManager`](https://pkg.go.dev/reconciler.io/runtime/reconcilers#UpdatingObjectManager) (previously [`ResourceManager`](https://pkg.go.dev/reconciler.io/runtime/reconcilers#ResourceManager)) uses the `client.Client#{Create, Update, Delete}` methods to synchronize state to the API Server.

Internally, a mutations made to the resource at admission time (like defaults applied by a mutating webhook) are captured and reapplied to the desired state before checking if an update is needed. This reduces requests that are functionally a no-op but create churn on the API Server. The mutation cache is defensive and fails open to make an API request.

If configured, a [finalizer](#finalizers) can be managed on the resource which will be added before create/udpate and removed after sucessful delete.

Expand All @@ -1153,6 +1163,10 @@ reconciler.io runtime is rapidly evolving. While we strive for API compatability

Backwards support may be removed in a future release, users are encouraged to migrate.

- `ResourceManager` is deprecated in favor of `ObjectManager` for a generic type, or `UpdatingObjectManager`.
- `AggregateReconciler.{HarmonizeImmutableFields, MergeBeforeUpdate, Sanitize}` are deprecated in favor of `AggregateReconciler.AggregateObjectManager`.
- `ChildReconciler.{Finalizer, HarmonizeImmutableFields, MergeBeforeUpdate, Sanitize, SetResourceManager}` are deprecated in favor of `ChildReconciler.ChildObjectManager`.
- `ChildSetReconciler.{HarmonizeImmutableFields, MergeBeforeUpdate, Sanitize}` are deprecated in favor of `ChildSetReconciler.ChildObjectManager`.
- status `InitializeConditions()` is deprecated in favor of `InitializeConditions(context.Context)`.
- `ConditionSet#Manage` is deprecated in favor of `ConditionSet#ManageWithContext`.
- `HaltSubReconcilers` is deprecated in favor of `ErrHaltSubReconcilers`.
Expand Down
30 changes: 20 additions & 10 deletions reconcilers/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,27 @@ type AggregateReconciler[Type client.Object] struct {
// +optional
DesiredResource func(ctx context.Context, resource Type) (Type, error)

// AggregateObjectManager synchronizes the aggregated resource with the API Server.
AggregateObjectManager ObjectManager[Type]

// Deprecated use AggregateObjectManager instead. Ignored when AggregateObjectManager is defined.
//
// HarmonizeImmutableFields allows fields that are immutable on the current
// object to be copied to the desired object in order to avoid creating
// updates which are guaranteed to fail.
//
// +optional
HarmonizeImmutableFields func(current, desired Type)

// Deprecated use AggregateObjectManager instead. Ignored when AggregateObjectManager is defined.
//
// MergeBeforeUpdate copies desired fields on to the current object before
// calling update. Typically fields to copy are the Spec, Labels and
// Annotations.
MergeBeforeUpdate func(current, desired Type)

// Deprecated use AggregateObjectManager instead. Ignored when AggregateObjectManager is defined.
//
// Sanitize is called with an object before logging the value. Any value may
// be returned. A meaningful subset of the resource is typically returned,
// like the Spec.
Expand All @@ -122,8 +131,6 @@ type AggregateReconciler[Type client.Object] struct {

Config Config

// stamp manages the lifecycle of the aggregated resource.
stamp *ResourceManager[Type]
lazyInit sync.Once
}

Expand Down Expand Up @@ -155,13 +162,16 @@ func (r *AggregateReconciler[T]) init() {
}
}

r.stamp = &ResourceManager[T]{
Name: r.Name,
Type: r.Type,
if r.AggregateObjectManager == nil {
// Deprecated compatibility fallback
r.AggregateObjectManager = &UpdatingObjectManager[T]{
Name: r.Name,
Type: r.Type,

HarmonizeImmutableFields: r.HarmonizeImmutableFields,
MergeBeforeUpdate: r.MergeBeforeUpdate,
Sanitize: r.Sanitize,
HarmonizeImmutableFields: r.HarmonizeImmutableFields,
MergeBeforeUpdate: r.MergeBeforeUpdate,
Sanitize: r.Sanitize,
}
}
})
}
Expand Down Expand Up @@ -213,7 +223,7 @@ func (r *AggregateReconciler[T]) SetupWithManagerYieldingController(ctx context.
if err := r.Reconciler.SetupWithManager(ctx, mgr, bldr); err != nil {
return nil, err
}
if err := r.stamp.Setup(ctx); err != nil {
if err := r.AggregateObjectManager.SetupWithManager(ctx, mgr, bldr); err != nil {
return nil, err
}
return bldr.Build(r)
Expand Down Expand Up @@ -309,7 +319,7 @@ func (r *AggregateReconciler[T]) reconcile(ctx context.Context, req Request) (Re
if err != nil {
return result, err
}
_, err = r.stamp.Manage(ctx, resource, resource, desired)
_, err = r.AggregateObjectManager.Manage(ctx, resource, resource, desired)
return result, err
}

Expand Down
55 changes: 36 additions & 19 deletions reconcilers/child.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ type ChildReconciler[Type, ChildType client.Object, ChildListType client.ObjectL
// +optional
ChildListType ChildListType

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// Finalizer is set on the reconciled resource before a child resource is created, and cleared
// after a child resource is deleted. The value must be unique to this specific reconciler
// instance and not shared. Reusing a value may result in orphaned resources when the
Expand Down Expand Up @@ -119,13 +121,20 @@ type ChildReconciler[Type, ChildType client.Object, ChildListType client.ObjectL
// may grow, implementations should be defensive rather than assuming the error type.
ReflectChildStatusOnParent func(ctx context.Context, parent Type, child ChildType, err error)

// ChildObjectManager synchronizes the desired child state to the API Server.
ChildObjectManager ObjectManager[ChildType]

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// HarmonizeImmutableFields allows fields that are immutable on the current
// object to be copied to the desired object in order to avoid creating
// updates which are guaranteed to fail.
//
// +optional
HarmonizeImmutableFields func(current, desired ChildType)

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// MergeBeforeUpdate copies desired fields on to the current object before
// calling update. Typically fields to copy are the Spec, Labels and
// Annotations.
Expand Down Expand Up @@ -158,14 +167,15 @@ type ChildReconciler[Type, ChildType client.Object, ChildListType client.ObjectL
// +optional
OurChild func(resource Type, child ChildType) bool

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// Sanitize is called with an object before logging the value. Any value may
// be returned. A meaningful subset of the resource is typically returned,
// like the Spec.
//
// +optional
Sanitize func(child ChildType) interface{}

stamp *ResourceManager[ChildType]
lazyInit sync.Once
}

Expand All @@ -182,11 +192,9 @@ func (r *ChildReconciler[T, CT, CLT]) init() {
if r.Name == "" {
r.Name = fmt.Sprintf("%sChildReconciler", typeName(r.ChildType))
}
if r.Sanitize == nil {
r.Sanitize = func(child CT) interface{} { return child }
}
if r.stamp == nil {
r.stamp = &ResourceManager[CT]{
if r.ChildObjectManager == nil {
// Deprecated compatibility fallback
r.ChildObjectManager = &UpdatingObjectManager[CT]{
Name: r.Name,
Type: r.ChildType,
Finalizer: r.Finalizer,
Expand All @@ -213,16 +221,21 @@ func (r *ChildReconciler[T, CT, CLT]) SetupWithManager(ctx context.Context, mgr
return err
}

if r.SkipOwnerReference {
bldr.Watches(r.ChildType, EnqueueTracked(ctx))
} else {
if !r.SkipOwnerReference {
bldr.Owns(r.ChildType)
}

if r.Setup == nil {
return nil
if err := r.ChildObjectManager.SetupWithManager(ctx, mgr, bldr); err != nil {
return err
}
return r.Setup(ctx, mgr, bldr)

if r.Setup != nil {
if err := r.Setup(ctx, mgr, bldr); err != nil {
return err
}
}

return nil
}

func (r *ChildReconciler[T, CT, CLT]) validate(ctx context.Context) error {
Expand Down Expand Up @@ -251,19 +264,23 @@ func (r *ChildReconciler[T, CT, CLT]) validate(ctx context.Context) error {
return fmt.Errorf("ChildReconciler %q must implement ListOptions since owner references are not used", r.Name)
}

// require MergeBeforeUpdate
if r.MergeBeforeUpdate == nil {
return fmt.Errorf("ChildReconciler %q must implement MergeBeforeUpdate", r.Name)
// Deprecated fallback validation
if m, ok := r.ChildObjectManager.(*UpdatingObjectManager[CT]); ok {
// require MergeBeforeUpdate
if m.MergeBeforeUpdate == nil {
return fmt.Errorf("ChildReconciler %q must implement MergeBeforeUpdate", r.Name)
}
}

return nil
}

func (r *ChildReconciler[T, CT, CLT]) SetResourceManager(rm *ResourceManager[CT]) {
if r.stamp != nil {
// Deprecated use ChildObjectManager instead
func (r *ChildReconciler[T, CT, CLT]) SetResourceManager(rm ObjectManager[CT]) {
if r.ChildObjectManager != nil {
panic(fmt.Errorf("cannot call SetResourceManager after a resource manager is defined"))
}
r.stamp = rm
r.ChildObjectManager = rm
}

func (r *ChildReconciler[T, CT, CLT]) Reconcile(ctx context.Context, resource T) (Result, error) {
Expand Down Expand Up @@ -364,7 +381,7 @@ func (r *ChildReconciler[T, CT, CLT]) reconcile(ctx context.Context, resource T)
}

// create/update/delete desired child
return r.stamp.Manage(ctx, resource, actual, desired)
return r.ChildObjectManager.Manage(ctx, resource, actual, desired)
}

func (r *ChildReconciler[T, CT, CLT]) desiredChild(ctx context.Context, resource T) (CT, error) {
Expand Down
47 changes: 31 additions & 16 deletions reconcilers/childset.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ type ChildSetReconciler[Type, ChildType client.Object, ChildListType client.Obje
// status on the reconciled resource, return OnlyReconcileChildStatus as an error.
DesiredChildren func(ctx context.Context, resource Type) ([]ChildType, error)

// ChildObjectManager synchronizes the desired child state to the API Server.
ChildObjectManager ObjectManager[ChildType]

// ReflectChildrenStatusOnParent updates the reconciled resource's status with values from the
// child reconciliations. Select types of errors are captured, including:
// - apierrs.IsAlreadyExists
Expand All @@ -114,13 +117,17 @@ type ChildSetReconciler[Type, ChildType client.Object, ChildListType client.Obje
// reconciled, (sorted by identifier).
ReflectChildrenStatusOnParent func(ctx context.Context, parent Type, result ChildSetResult[ChildType])

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// HarmonizeImmutableFields allows fields that are immutable on the current
// object to be copied to the desired object in order to avoid creating
// updates which are guaranteed to fail.
//
// +optional
HarmonizeImmutableFields func(current, desired ChildType)

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// MergeBeforeUpdate copies desired fields on to the current object before
// calling update. Typically fields to copy are the Spec, Labels and
// Annotations.
Expand Down Expand Up @@ -161,14 +168,15 @@ type ChildSetReconciler[Type, ChildType client.Object, ChildListType client.Obje
// Non-deterministic IDs will result in the rapid deletion and creation of child resources.
IdentifyChild func(child ChildType) string

// Deprecated use ChildObjectManager instead. Ignored when ChildObjectManager is defined.
//
// Sanitize is called with an object before logging the value. Any value may
// be returned. A meaningful subset of the resource is typically returned,
// like the Spec.
//
// +optional
Sanitize func(child ChildType) interface{}

stamp *ResourceManager[ChildType]
lazyInit sync.Once
voidReconciler *ChildReconciler[Type, ChildType, ChildListType]
}
Expand All @@ -186,13 +194,16 @@ func (r *ChildSetReconciler[T, CT, CLT]) init() {
if r.Name == "" {
r.Name = fmt.Sprintf("%sChildSetReconciler", typeName(r.ChildType))
}
r.stamp = &ResourceManager[CT]{
Name: r.Name,
Type: r.ChildType,
TrackDesired: r.SkipOwnerReference,
HarmonizeImmutableFields: r.HarmonizeImmutableFields,
MergeBeforeUpdate: r.MergeBeforeUpdate,
Sanitize: r.Sanitize,
if r.ChildObjectManager == nil {
// Deprecated compatibility fallback
r.ChildObjectManager = &UpdatingObjectManager[CT]{
Name: r.Name,
Type: r.ChildType,
TrackDesired: r.SkipOwnerReference,
HarmonizeImmutableFields: r.HarmonizeImmutableFields,
MergeBeforeUpdate: r.MergeBeforeUpdate,
Sanitize: r.Sanitize,
}
}
r.voidReconciler = r.childReconcilerFor(nilCT, nil, "", true)
})
Expand All @@ -212,14 +223,21 @@ func (r *ChildSetReconciler[T, CT, CLT]) SetupWithManager(ctx context.Context, m
return err
}

if err := r.ChildObjectManager.SetupWithManager(ctx, mgr, bldr); err != nil {
return err
}

if err := r.voidReconciler.SetupWithManager(ctx, mgr, bldr); err != nil {
return err
}

if r.Setup == nil {
return nil
if r.Setup != nil {
if err := r.Setup(ctx, mgr, bldr); err != nil {
return err
}
}
return r.Setup(ctx, mgr, bldr)

return nil
}

func (r *ChildSetReconciler[T, CT, CLT]) childReconcilerFor(desired CT, desiredErr error, id string, void bool) *ChildReconciler[T, CT, CLT] {
Expand All @@ -231,6 +249,7 @@ func (r *ChildSetReconciler[T, CT, CLT]) childReconcilerFor(desired CT, desiredE
DesiredChild: func(ctx context.Context, resource T) (CT, error) {
return desired, desiredErr
},
ChildObjectManager: r.ChildObjectManager,
ReflectChildStatusOnParent: func(ctx context.Context, parent T, child CT, err error) {
result := childSetResultStasher[CT]().RetrieveOrEmpty(ctx)
result.Children = append(result.Children, ChildSetPartialResult[CT]{
Expand All @@ -240,16 +259,13 @@ func (r *ChildSetReconciler[T, CT, CLT]) childReconcilerFor(desired CT, desiredE
})
childSetResultStasher[CT]().Store(ctx, result)
},
HarmonizeImmutableFields: r.HarmonizeImmutableFields,
MergeBeforeUpdate: r.MergeBeforeUpdate,
ListOptions: r.ListOptions,
ListOptions: r.ListOptions,
OurChild: func(resource T, child CT) bool {
if r.OurChild != nil && !r.OurChild(resource, child) {
return false
}
return void || id == r.IdentifyChild(child)
},
Sanitize: r.Sanitize,
}
}

Expand Down Expand Up @@ -356,7 +372,6 @@ func (r *ChildSetReconciler[T, CT, CLT]) composeChildReconcilers(ctx context.Con
for _, id := range childIDs.List() {
child := desiredChildByID[id]
cr := r.childReconcilerFor(child, desiredChildrenErr, id, false)
cr.SetResourceManager(r.stamp)
sequence = append(sequence, cr)
}

Expand Down
Loading
Loading