diff --git a/docs/v2-architecture/controller-architecture/guide.md b/docs/v2-architecture/controller-architecture/guide.md index 5c429677db..1c26838a6a 100644 --- a/docs/v2-architecture/controller-architecture/guide.md +++ b/docs/v2-architecture/controller-architecture/guide.md @@ -477,6 +477,73 @@ func (barInitializer) Initialize(ctx context.Context, rt controller.Runtime) err } ``` +### Finalizer + +A finalizer allows a controller to execute teardown logic before a +resource is deleted. This can be useful to perform cleanup or block +deletion until certain conditions are met. + +Finalizers are encoded as keys within a resource's metadata map. It +is the responsibility of each controller that adds a finalizer to a +resource to remove the finalizer when it is marked for deletion. +Once a resource has no finalizers present, it is deleted by the +resource service. + +When the `Delete` endpoint is called on a resource with one or more +finalizers, the resource is marked for deletion by adding an immutable +`deletionTimestamp` key to the resource's metadata map. The resource is +now effectively frozen and will only accept subsequent `Write`s +that remove finalizers. `WriteStatus` is still allowed. + +The `resource` package API can be used to manage finalizers and +check whether a resource has been marked for deletion. You would +typically use this API within the logic of your controller's +`Reconcile` method to either put a finalizer in place or perform +cleanup and then remove a finalizer. Don't forget to `Write` your +changes once you add or remove finalizers. + +```Go +package resource + +// IsMarkedForDeletion returns true if a resource has been marked for deletion, +// false otherwise. +func IsMarkedForDeletion(res *pbresource.Resource) bool { ... } + +// HasFinalizers returns true if a resource has one or more finalizers, false otherwise. +func HasFinalizers(res *pbresource.Resource) bool { ... } + +// HasFinalizer returns true if a resource has a given finalizer, false otherwise. +func HasFinalizer(res *pbresource.Resource, finalizer string) bool { ... } + +// AddFinalizer adds a finalizer to the given resource. +func AddFinalizer(res *pbresource.Resource, finalizer string) { ... } + +// RemoveFinalizer removes a finalizer from the given resource. +func RemoveFinalizer(res *pbresource.Resource, finalizer string) { ... } + +// GetFinalizers returns the set of finalizers for the given resource. +func GetFinalizers(res *pbresource.Resource) mapset.Set[string] { ... } +``` + +Example flow in a controller's `Reconcile` method +```Go +const finalizer = "consul.io/bar-finalizer" + +func (barReconciler) Reconcile(ctx context.Context, rt controller.Runtime, req controller.Request) error { + ... + // Check if resource is marked for deletion. If yes, perform cleanup, remove finalizer, and Write the resource + if resource.IsMarkedForDeletion(res) { + // Perform some cleanup... + return EnsureFinalizerRemoved(ctx, rt, res, finalizer) + } + + // Check if resource has finalizer. If not, add it and Write the resource + if err := EnsureHasFinalizer(ctx, rt, res, finalizer); err != nil { + return err + } +} +``` + ## Ownership & Cascading Deletion The resource service implements a lightweight `1:N` ownership model where, on