From 88c5286ddcec90f8d6c2dd21070ccb2fa9908e48 Mon Sep 17 00:00:00 2001 From: cbarbian-sap <52255556+cbarbian-sap@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:27:48 +0100 Subject: [PATCH] revisit deletion handling, update website (#180) --- pkg/component/component.go | 37 ++++++++++-- pkg/component/reconciler.go | 56 ++++++++++++++----- pkg/component/types.go | 29 ++++++++++ pkg/component/zz_generated.deepcopy.go | 15 +++++ pkg/reconciler/reconciler.go | 30 +++++++--- website/config.toml | 2 +- website/content/en/docs/_index.md | 16 +----- website/content/en/docs/concepts/_index.md | 12 +++- .../content/en/docs/concepts/dependents.md | 4 +- .../content/en/docs/concepts/reconciler.md | 18 +++--- website/content/en/docs/concepts/types.md | 2 +- 11 files changed, 168 insertions(+), 53 deletions(-) diff --git a/pkg/component/component.go b/pkg/component/component.go index db8809a..ff91640 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -12,6 +12,7 @@ import ( "time" "github.com/sap/component-operator-runtime/internal/walk" + "github.com/sap/component-operator-runtime/pkg/reconciler" ) // Instantiate given Component type T; panics unless T is a pointer type. @@ -97,6 +98,17 @@ func assertTimeoutConfiguration[T Component](component T) (TimeoutConfiguration, return nil, false } +// Check if given component or its spec implements PolicyConfiguration (and return it). +func assertPolicyConfiguration[T Component](component T) (PolicyConfiguration, bool) { + if policyConfiguration, ok := Component(component).(PolicyConfiguration); ok { + return policyConfiguration, true + } + if policyConfiguration, ok := getSpec(component).(PolicyConfiguration); ok { + return policyConfiguration, true + } + return nil, false +} + // Calculate digest of given component, honoring annotations, spec, and references. func calculateComponentDigest[T Component](component T) string { digestData := make(map[string]any) @@ -181,12 +193,6 @@ func (s *RetrySpec) GetRetryInterval() time.Duration { return time.Duration(0) } -// Check if state is Ready. -func (s *Status) IsReady() bool { - // caveat: this operates only on the status, so it does not check that observedGeneration == generation - return s.State == StateReady -} - // Implement the TimeoutConfiguration interface. func (s *TimeoutSpec) GetTimeout() time.Duration { if s.Timeout != nil { @@ -195,6 +201,25 @@ func (s *TimeoutSpec) GetTimeout() time.Duration { return time.Duration(0) } +// Implement the PolicyConfiguration interface. +func (s *PolicySpec) GetAdoptionPolicy() reconciler.AdoptionPolicy { + return s.AdoptionPolicy +} + +func (s *PolicySpec) GetUpdatePolicy() reconciler.UpdatePolicy { + return s.UpdatePolicy +} + +func (s *PolicySpec) GetDeletePolicy() reconciler.DeletePolicy { + return s.DeletePolicy +} + +// Check if state is Ready. +func (s *Status) IsReady() bool { + // caveat: this operates only on the status, so it does not check that observedGeneration == generation + return s.State == StateReady +} + // Get condition (and return nil if not existing). // Caveat: the returned pointer might become invalid if further appends happen to the Conditions slice in the status object. func (s *Status) getCondition(condType ConditionType) *Condition { diff --git a/pkg/component/reconciler.go b/pkg/component/reconciler.go index 7e2e3f9..4dd49cb 100644 --- a/pkg/component/reconciler.go +++ b/pkg/component/reconciler.go @@ -52,8 +52,8 @@ import ( // TODO: emitting events to deployment target may fail if corresponding rbac privileges are missing; either this should be pre-discovered or we // should stop emitting events to remote targets at all; howerver pre-discovering is difficult (may vary from object to object); one option could // be to send events only if we are cluster-admin -// TODO: allow to override namespace auto-creation and policies on a per-component level -// (e.g. through annotations or another interface that components could optionally implement) +// TODO: allow to override namespace auto-creation and reconcile policy on a per-component level +// that is: consider adding them to the PolicyConfiguration interface? // TODO: allow to override namespace auto-creation on a per-object level // TODO: allow some timeout feature, such that component will go into error state if not ready within the given timeout // (e.g. through a TimeoutConfiguration interface that components could optionally implement) @@ -101,6 +101,10 @@ type ReconcilerOptions struct { // If unspecified, UpdatePolicyReplace is assumed. // Can be overridden by annotation on object level. UpdatePolicy *reconciler.UpdatePolicy + // How to perform deletion of dependent objects. + // If unspecified, DeletePolicyDelete is assumed. + // Can be overridden by annotation on object level. + DeletePolicy *reconciler.DeletePolicy // SchemeBuilder allows to define additional schemes to be made available in the // target client. SchemeBuilder types.SchemeBuilder @@ -133,6 +137,8 @@ type Reconciler[T Component] struct { // resourceGenerator must be an implementation of the manifests.Generator interface. func NewReconciler[T Component](name string, resourceGenerator manifests.Generator, options ReconcilerOptions) *Reconciler[T] { // TOOD: validate options + // TODO: currently, the defaulting of CreateMissingNamespaces and *Policy here is identical to the defaulting in the underlying reconciler.Reconciler; + // under the assumption that these attributes are not used here, we could skip the defaulting here, and let it happen in the underlying implementation only if options.CreateMissingNamespaces == nil { options.CreateMissingNamespaces = ref(true) } @@ -142,6 +148,9 @@ func NewReconciler[T Component](name string, resourceGenerator manifests.Generat if options.UpdatePolicy == nil { options.UpdatePolicy = ref(reconciler.UpdatePolicyReplace) } + if options.DeletePolicy == nil { + options.DeletePolicy = ref(reconciler.DeletePolicyDelete) + } return &Reconciler[T]{ name: name, @@ -330,18 +339,8 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result if err != nil { return ctrl.Result{}, errors.Wrap(err, "error getting client for component") } - target := newReconcileTarget[T](r.name, r.id, targetClient, r.resourceGenerator, reconciler.ReconcilerOptions{ - CreateMissingNamespaces: r.options.CreateMissingNamespaces, - AdoptionPolicy: r.options.AdoptionPolicy, - UpdatePolicy: r.options.UpdatePolicy, - StatusAnalyzer: r.statusAnalyzer, - Metrics: reconciler.ReconcilerMetrics{ - ReadCounter: metrics.Operations.WithLabelValues(r.controllerName, "read"), - CreateCounter: metrics.Operations.WithLabelValues(r.controllerName, "create"), - UpdateCounter: metrics.Operations.WithLabelValues(r.controllerName, "update"), - DeleteCounter: metrics.Operations.WithLabelValues(r.controllerName, "delete"), - }, - }) + targetOptions := r.getOptionsForComponent(component) + target := newReconcileTarget[T](r.name, r.id, targetClient, r.resourceGenerator, targetOptions) // TODO: enhance ctx with tailored logger and event recorder // TODO: enhance ctx with the local client hookCtx = NewContext(ctx).WithReconcilerName(r.name).WithClient(targetClient) @@ -636,3 +635,32 @@ func (r *Reconciler[T]) getClientForComponent(component T) (cluster.Client, erro } return clnt, nil } + +func (r *Reconciler[T]) getOptionsForComponent(component T) reconciler.ReconcilerOptions { + options := reconciler.ReconcilerOptions{ + CreateMissingNamespaces: r.options.CreateMissingNamespaces, + AdoptionPolicy: r.options.AdoptionPolicy, + UpdatePolicy: r.options.UpdatePolicy, + DeletePolicy: r.options.DeletePolicy, + StatusAnalyzer: r.statusAnalyzer, + Metrics: reconciler.ReconcilerMetrics{ + ReadCounter: metrics.Operations.WithLabelValues(r.controllerName, "read"), + CreateCounter: metrics.Operations.WithLabelValues(r.controllerName, "create"), + UpdateCounter: metrics.Operations.WithLabelValues(r.controllerName, "update"), + DeleteCounter: metrics.Operations.WithLabelValues(r.controllerName, "delete"), + }, + } + if policyConfiguration, ok := assertPolicyConfiguration(component); ok { + // TODO: check the values returned by the PolicyConfiguration + if adoptionPolicy := policyConfiguration.GetAdoptionPolicy(); adoptionPolicy != "" { + options.AdoptionPolicy = &adoptionPolicy + } + if updatePolicy := policyConfiguration.GetUpdatePolicy(); updatePolicy != "" { + options.UpdatePolicy = &updatePolicy + } + if deletePolicy := policyConfiguration.GetDeletePolicy(); deletePolicy != "" { + options.DeletePolicy = &deletePolicy + } + } + return options +} diff --git a/pkg/component/types.go b/pkg/component/types.go index 28441df..8b5aef9 100644 --- a/pkg/component/types.go +++ b/pkg/component/types.go @@ -84,6 +84,20 @@ type TimeoutConfiguration interface { GetTimeout() time.Duration } +// The PolicyConfiguration interface is meant to be implemented by compoments (or their spec) which offer +// tweaking policies affecting the dependents handling. +type PolicyConfiguration interface { + // Get adoption policy. + // Must return a valid AdoptionPolicy, or the empty string (then the reconciler/framework default applies). + GetAdoptionPolicy() reconciler.AdoptionPolicy + // Get update policy. + // Must return a valid UpdatePolicy, or the empty string (then the reconciler/framework default applies). + GetUpdatePolicy() reconciler.UpdatePolicy + // Get delete policy. + // Must return a valid DeletePolicy, or the empty string (then the reconciler/framework default applies). + GetDeletePolicy() reconciler.DeletePolicy +} + // +kubebuilder:object:generate=true // Legacy placement spec. Components may include this into their spec. @@ -167,6 +181,21 @@ var _ TimeoutConfiguration = &TimeoutSpec{} // +kubebuilder:object:generate=true +// PolicySpec defines some of the policies tuning the reconciliation of the compooment's dependent objects. +// Components providing PolicyConfiguration may include this into their spec. +type PolicySpec struct { + // +kubebuilder:validation:Enum=Never;IfUnowned;Always + AdoptionPolicy reconciler.AdoptionPolicy `json:"adoptionPolicy,omitempty"` + // +kubebuilder:validation:Enum=Recreate;Replace;SsaMerge;SsaOverride + UpdatePolicy reconciler.UpdatePolicy `json:"updatePolicy,omitempty"` + // +kubebuilder:validation:Enum=Delete;Orphan + DeletePolicy reconciler.DeletePolicy `json:"deletePolicy,omitempty"` +} + +var _ PolicyConfiguration = &PolicySpec{} + +// +kubebuilder:object:generate=true + // Component Status. Components must include this into their status. type Status struct { ObservedGeneration int64 `json:"observedGeneration"` diff --git a/pkg/component/zz_generated.deepcopy.go b/pkg/component/zz_generated.deepcopy.go index a19c9d6..307654a 100644 --- a/pkg/component/zz_generated.deepcopy.go +++ b/pkg/component/zz_generated.deepcopy.go @@ -288,6 +288,21 @@ func (in *PlacementSpec) DeepCopy() *PlacementSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicySpec) DeepCopyInto(out *PolicySpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicySpec. +func (in *PolicySpec) DeepCopy() *PolicySpec { + if in == nil { + return nil + } + out := new(PolicySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RequeueSpec) DeepCopyInto(out *RequeueSpec) { *out = *in diff --git a/pkg/reconciler/reconciler.go b/pkg/reconciler/reconciler.go index 0de7d1c..e416760 100644 --- a/pkg/reconciler/reconciler.go +++ b/pkg/reconciler/reconciler.go @@ -94,6 +94,10 @@ type ReconcilerOptions struct { // If unspecified, UpdatePolicyReplace is assumed. // Can be overridden by annotation on object level. UpdatePolicy *UpdatePolicy + // How to perform deletion of dependent objects. + // If unspecified, DeletePolicyDelete is assumed. + // Can be overridden by annotation on object level. + DeletePolicy *DeletePolicy // How to analyze the state of the dependent objects. // If unspecified, an optimized kstatus based implementation is used. StatusAnalyzer status.StatusAnalyzer @@ -147,6 +151,9 @@ func NewReconciler(name string, clnt cluster.Client, options ReconcilerOptions) if options.UpdatePolicy == nil { options.UpdatePolicy = ref(UpdatePolicyReplace) } + if options.DeletePolicy == nil { + options.DeletePolicy = ref(DeletePolicyDelete) + } if options.StatusAnalyzer == nil { options.StatusAnalyzer = status.NewStatusAnalyzer(name) } @@ -160,7 +167,7 @@ func NewReconciler(name string, clnt cluster.Client, options ReconcilerOptions) adoptionPolicy: *options.AdoptionPolicy, reconcilePolicy: ReconcilePolicyOnObjectChange, updatePolicy: *options.UpdatePolicy, - deletePolicy: DeletePolicyDelete, + deletePolicy: *options.DeletePolicy, labelKeyOwnerId: name + "/" + types.LabelKeySuffixOwnerId, annotationKeyOwnerId: name + "/" + types.AnnotationKeySuffixOwnerId, annotationKeyDigest: name + "/" + types.AnnotationKeySuffixDigest, @@ -196,15 +203,14 @@ func NewReconciler(name string, clnt cluster.Client, options ReconcilerOptions) // will re-claim (and therefore potentially drop) fields owned by certain field managers, such as kubectl and helm // - if the effective update policy is UpdatePolicyRecreate, the object will be deleted and recreated. // -// Redundant objects will be removed; that means, in the regular case, a http DELETE request will be sent to the Kubernetes API; if the object specifies -// its delete policy as DeletePolicyOrphan, no physcial deletion will be performed, and the object will be left around in the cluster; however it will be no -// longer be part of the inventory. -// // Objects will be applied and deleted in waves, according to their apply/delete order. Objects which specify a purge order will be deleted from the cluster at the // end of the wave specified as purge order; other than redundant objects, a purged object will remain as Completed in the inventory; // and it might be re-applied/re-purged in case it runs out of sync. Within a wave, objects are processed following a certain internal order; // in particular, instances of types which are part of the wave are processed only if all other objects in that wave have a ready state. // +// Redundant objects will be removed; that means, a http DELETE request will be sent to the Kubernetes API; note that an effective Orphan deletion +// policy will not prevent deletion here; the deletion policy will only be honored when the component as whole gets deleted. +// // This method will change the passed inventory (add or remove elements, change elements). If Apply() returns true, then all objects are successfully reconciled; // otherwise, if it returns false, the caller should recall it timely, until it returns true. In any case, the passed inventory should match the state of the // inventory after the previous invocation of Apply(); usually, the caller saves the inventory after calling Apply(), and loads it before calling Apply(). @@ -727,7 +733,12 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj return false, errors.Wrapf(err, "error reading object %s", item) } - orphan := item.DeletePolicy == DeletePolicyOrphan + // note: objects becoming obsolete during an apply are no longer honoring deletion policy (orphan) + // TODO: not sure if there is a case where someone would like to orphan such resources while applying; + // if so, then we probably should introduce a third deletion policy, OrphanApply or similar ... + // in any case, the following code should be revisited; cleaned up or adjusted + // orphan := item.DeletePolicy == DeletePolicyOrphan + orphan := false switch item.Phase { case PhaseScheduledForDeletion: @@ -788,6 +799,7 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj // objects having a certain delete order will only start if all objects with lower delete order are gone. Within a wave, objects are // deleted following a certain internal ordering; in particular, if there are instances of types which are part of the wave, then these // instances will be deleted first; only if all such instances are gone, the remaining objects of the wave will be deleted. +// Objects which have an effective Orphan deletion policy will not be touched (remain in the cluster), but will no longer appear in the inventory. // // This method will change the passed inventory (remove elements, change elements). If Delete() returns true, then all objects are gone; otherwise, // if it returns false, the caller should recall it timely, until it returns true. In any case, the passed inventory should match the state of the @@ -873,8 +885,12 @@ func (r *Reconciler) Delete(ctx context.Context, inventory *[]*InventoryItem) (b // Check if the object set defined by inventory is ready for deletion; that means: check if the inventory contains // types (as custom resource definition or from an api service), while there exist instances of these types in the cluster, -// which are not contained in the inventory. +// which are not contained in the inventory. There is one exception of this rule: if all objects in the inventory have their +// delete policy set to DeletePolicyOrphan, then the deletion of the component is immediately allowed. func (r *Reconciler) IsDeletionAllowed(ctx context.Context, inventory *[]*InventoryItem) (bool, string, error) { + if slices.All(*inventory, func(item *InventoryItem) bool { return item.DeletePolicy == DeletePolicyOrphan }) { + return true, "", nil + } for _, item := range *inventory { switch { case isCrd(item): diff --git a/website/config.toml b/website/config.toml index 07550ba..23700bc 100644 --- a/website/config.toml +++ b/website/config.toml @@ -12,7 +12,7 @@ enableMissingTranslationPlaceholders = true enableRobotsTXT = true # Will give values to .Lastmod etc. -enableGitInfo = true +enableGitInfo = false # Comment out to enable taxonomies in Docsy # disableKinds = ["taxonomy", "taxonomyTerm"] diff --git a/website/content/en/docs/_index.md b/website/content/en/docs/_index.md index 33fd393..7dda006 100644 --- a/website/content/en/docs/_index.md +++ b/website/content/en/docs/_index.md @@ -5,23 +5,13 @@ weight: 40 type: "docs" --- -This repository provides a framework supporting the development of opinionated Kubernetes operators +This repository provides a framework supporting the development of Kubernetes operators managing the lifecycle of arbitrary deployment components of Kubernetes clusters, with a special focus on such components that are or contain Kubernetes operators themselves. It can therefore serve as a starting point to develop [SAP Kyma module operators](https://github.com/kyma-project/template-operator), -but can also be used independently of Kyma. - -Regarding its mission statement, this project can be compared with the [Operator Lifecycle Manager (OLM)](https://olm.operatorframework.io/). -However, other than OLM, which follows a generic modelling approach, component-operator-runtime encourages the development of opinionated, -concretely modeled, component-specific operators. This makes the resulting logic much more explicit, and also allows to react better on -specific lifecycle needs of the managed component. - -Of course, components might equally be managed by using generic Kustomization or Helm chart deployers (such as provided by [ArgoCD](https://argoproj.github.io/) or [FluxCD](https://fluxcd.io/flux/)). -However, these tools have certain weaknesses when it is about to deploy other operators, i.e. components which extend the Kubernetes API, -e.g. by adding custom resource definitions, aggregated API servers, according controllers, or admission webhooks. -For example these generic solutions tend to produce race conditions or dead locks upon first installation or deletion of the managed components. -This is where component-operator-runtime tries to act in a smarter and more robust way. +but can also be used independently of Kyma. While being perfectly suited to develop opiniated operators like Kyma module operators, it can be +equally used to cover more generic use cases. A prominent example for such a generic operator is the [SAP component operator](https://github.com/sap/component-operator) which can be compared to flux's [kustomize controller](https://github.com/fluxcd/kustomize-controller) and [helm controller](https://github.com/fluxcd/helm-controller). If you want to report bugs, or request new features or enhancements, please [open an issue](https://github.com/sap/component-operator-runtime/issues) or [raise a pull request](https://github.com/sap/component-operator-runtime/pulls). \ No newline at end of file diff --git a/website/content/en/docs/concepts/_index.md b/website/content/en/docs/concepts/_index.md index f0eda2e..c0d56e9 100644 --- a/website/content/en/docs/concepts/_index.md +++ b/website/content/en/docs/concepts/_index.md @@ -10,8 +10,14 @@ description: > The framework provided in this repository aims to automate the lifecycle of an arbitrary component in a Kubernetes cluster. Usually (but not necessarily) the managed component contains one or multiple other operators, including extension types, such as custom resource definitions. -Other than existing tools addressing this case, such as the [Operator Lifecycle Manager (OLM)](https://olm.operatorframework.io/), -this project proposes a more opinionated programming model. That is, the idea is to represent the managed component by an own custom resource type, -which (usually) will be instantiated only once in the cluster. We feel encouraged to go this way, as many community projects are following the pattern of providing dedicated lifecycle operators. +Components are described as a set of Kubernetes manifests. How these manifests are produced is up to the consumer of the framework. +It is possible to build up the manifests from scratch in code, or to reuse or enhance the included helm generator or kustomize generator generators. +The manifest list is then applied to (or removed from) the cluster by an own deployer logic, standing out with the following features: +- apply and delete waves +- configurable status handling +- apply through replace or server-side-apply patch +- smart deletion handling in case the component contains custom types which are still in use +- impersonination +- remote deployment mode via a given kubeconfig The component-operator-runtime framework plugs into the [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) SDK by implementing controller-runtime's `Reconciler` interface. \ No newline at end of file diff --git a/website/content/en/docs/concepts/dependents.md b/website/content/en/docs/concepts/dependents.md index e72fc82..2b083a5 100644 --- a/website/content/en/docs/concepts/dependents.md +++ b/website/content/en/docs/concepts/dependents.md @@ -33,10 +33,12 @@ To support such cases, the `Generator` implementation can set the following anno - `ssa-merge`: use server side apply to update existing dependents - `ssa-override`: use server side apply to update existing dependents and, in addition, reclaim fields owned by certain field owners, such as kubectl or Helm - `recreate`: if the object would be updated, it will be deleted and recreated instead -- `mycomponent-operator.mydomain.io/delete-policy`: defines what happens if the object is deleted; can be one of: +- `mycomponent-operator.mydomain.io/delete-policy`: defines what happens to the object when the compoment is deleted; can be one of: - `default` (deprecated): equivalent to the annotation being unset (which means that the reconciler default will be used) - `delete` (which is the default): a delete call will be sent to the Kubernetes API server - `orphan`: the object will not be deleted, and it will be no longer tracked + + note that the deletion policy has no effect in the case when objects are deleted because they become obsolete by applying a new version of the component manifests - `mycomponent-operator.mydomain.io/apply-order`: the wave in which this object will be reconciled; dependents will be reconciled wave by wave; that is, objects of the same wave will be deployed in a canonical order, and the reconciler will only proceed to the next wave if all objects of previous waves are ready; specified orders can be negative or positive numbers between -32768 and 32767, objects with no explicit order set are treated as if they would specify order 0 - `mycomponent-operator.mydomain.io/purge-order` (optional): the wave by which this object will be purged; here, purged means that, while applying the dependents, the object will be deleted from the cluster at the end of the specified wave; the according record in `status.Inventory` will be set to phase `Completed`; setting purge orders is useful to spawn ad-hoc objects during the reconcilation, which are not permanently needed; so it's comparable to Helm hooks, in a certain sense - `mycomponent-operator.mydomain.io/delete-order` (optional): the wave by which this object will be deleted; that is, if the dependent is no longer part of the component, or if the whole component is being deleted; dependents will be deleted wave by wave; that is, objects of the same wave will be deleted in a canonical order, and the reconciler will only proceed to the next wave if all objects of previous saves are gone; specified orders can be negative or positive numbers between -32768 and 32767, objects with no explicit order set are treated as if they would specify order 0; note that the delete order is completely independent of the apply order diff --git a/website/content/en/docs/concepts/reconciler.md b/website/content/en/docs/concepts/reconciler.md index 6b0db7d..420790c 100644 --- a/website/content/en/docs/concepts/reconciler.md +++ b/website/content/en/docs/concepts/reconciler.md @@ -40,21 +40,25 @@ The passed type parameter `T Component` is the concrete runtime type of the comp // If unspecified, true is assumed. CreateMissingNamespaces *bool // How to react if a dependent object exists but has no or a different owner. - // If unspecified, AdoptionPolicyIfUnowned is assumed. + // If unspecified, AdoptionPolicyIfUnowned is assumed. // Can be overridden by annotation on object level. - AdoptionPolicy *AdoptionPolicy + AdoptionPolicy *reconciler.AdoptionPolicy // How to perform updates to dependent objects. // If unspecified, UpdatePolicyReplace is assumed. // Can be overridden by annotation on object level. - UpdatePolicy *UpdatePolicy - // Schemebuilder allows to define additional schemes to be made available in the + UpdatePolicy *reconciler.UpdatePolicy + // How to perform deletion of dependent objects. + // If unspecified, DeletePolicyDelete is assumed. + // Can be overridden by annotation on object level. + DeletePolicy *reconciler.DeletePolicy + // SchemeBuilder allows to define additional schemes to be made available in the // target client. SchemeBuilder types.SchemeBuilder } ``` The object returned by `NewReconciler` implements controller-runtime's `Reconciler` interface, and can therefore be used as a drop-in -in kubebuilder managed projects. After creation, the reconciler has to be registered with the responsible controller-runtime manager instance by calling +in kubebuilder managed projects. After creation, the reconciler can be registered with the responsible controller-runtime manager instance by calling ```go package component @@ -119,8 +123,8 @@ func (r *Reconciler[T]) WithPostDeleteHook(hook HookFunc[T]) *Reconciler[T] ``` Note that the client passed to the hook functions is the client of the manager that was used when calling `SetupWithManager()` -(that is, the return value of that manager's `GetClient()` method). In addition, reconcile and delete hooks (that is all except the -post-read hook) can retrieve a client for the deployment target by calling `utils.ClientFromContext()`. +(that is, the return value of that manager's `GetClient()` method). In addition, reconcile and delete hooks (that is, all except the +post-read hook) can retrieve a client for the deployment target by calling `ClientFromContext()`. ## Tuning the retry behavior diff --git a/website/content/en/docs/concepts/types.md b/website/content/en/docs/concepts/types.md index eca1515..54a4a29 100644 --- a/website/content/en/docs/concepts/types.md +++ b/website/content/en/docs/concepts/types.md @@ -67,7 +67,7 @@ type Status struct { Note that, other than with the `GetSpec()` accessor, the framework will make changes to the returned `Status` structure. Thus, in almost all cases, the returned pointer should just reference the status of the component's API type (or an according substructure of that status). -The component's custom resource type is supposed to be namespaced, and by default, dependent objects will be created in that same namespace. To be more precise, the `namespace` and `name` parameters of the used generator's `Generate()` method will be set to the component's `metadata.namespace` and `metadata.name`, respectively. Sometimes it might be desired to override these default, and to render the dependent objects with a different namespace or name. To allow this, the component (or its spec) can implement +The component's custom resource type is supposed to be namespaced, and by default, dependent objects will be created in that same namespace. To be more precise, the `namespace` and `name` parameters of the used generator's `Generate()` method will be set to the component's `metadata.namespace` and `metadata.name`, respectively. Sometimes it might be desired to override these defaults, and to render the dependent objects with a different namespace or name. To allow this, the component (or its spec) can implement ```go package component