diff --git a/pkg/component/component.go b/pkg/component/component.go index 12245a73..601fa283 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -77,6 +77,17 @@ func assertImpersonationConfiguration[T Component](component T) (ImpersonationCo return nil, false } +// Check if given component or its spec implements SuspensionConfiguration (and return it). +func assertSuspensionConfiguration[T Component](component T) (SuspensionConfiguration, bool) { + if suspensionConfiguration, ok := Component(component).(SuspensionConfiguration); ok { + return suspensionConfiguration, true + } + if suspensionConfiguration, ok := getSpec(component).(SuspensionConfiguration); ok { + return suspensionConfiguration, true + } + return nil, false +} + // Check if given component or its spec implements RequeueConfiguration (and return it). func assertRequeueConfiguration[T Component](component T) (RequeueConfiguration, bool) { if requeueConfiguration, ok := Component(component).(RequeueConfiguration); ok { @@ -176,6 +187,11 @@ func (s *ImpersonationSpec) GetImpersonationGroups() []string { return nil } +// Implement the SuspensionConfiguration interface. +func (s *SuspensionSpec) IsSuspended() bool { + return s.Suspend +} + // Implement the RequeueConfiguration interface. func (s *RequeueSpec) GetRequeueInterval() time.Duration { if s.RequeueInterval != nil { diff --git a/pkg/component/reconciler.go b/pkg/component/reconciler.go index 1eb27287..62096a6b 100755 --- a/pkg/component/reconciler.go +++ b/pkg/component/reconciler.go @@ -76,6 +76,7 @@ const ( ReadyConditionReasonReady = "Ready" ReadyConditionReasonError = "Error" ReadyConditionReasonTimeout = "Timeout" + ReadyConditionReasonSuspended = "Suspended" ReadyConditionReasonDeletionRetrying = "DeletionRetrying" ReadyConditionReasonDeletionBlocked = "DeletionBlocked" ReadyConditionReasonDeletionProcessing = "DeletionProcessing" @@ -456,6 +457,13 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result return ctrl.Result{RequeueAfter: time.Millisecond}, nil } + if component.GetDeletionTimestamp().IsZero() { + if suspensionConfiguration, ok := assertSuspensionConfiguration(component); ok && suspensionConfiguration.IsSuspended() { + status.SetState(StatePending, ReadyConditionReasonSuspended, "Reconciliation is suspended") + return ctrl.Result{RequeueAfter: r.backoff.Next(req, ReadyConditionReasonSuspended)}, nil + } + } + // resolve references componentDigest, err = resolveReferences(ctx, r.client, r.hookClient, component) if err != nil { diff --git a/pkg/component/types.go b/pkg/component/types.go index 01ae4455..bf670c77 100644 --- a/pkg/component/types.go +++ b/pkg/component/types.go @@ -63,6 +63,14 @@ type ImpersonationConfiguration interface { GetImpersonationGroups() []string } +// The SuspensionConfiguration interface is meant to be implemented by components (or their spec) which offer +// the ability to suspend reconciliation. +type SuspensionConfiguration interface { + // Whether the reconciliation (apply) or the implementing component is supended. If true the component goes + // into Pending state with reason Suspended. Note that deletion is not affected by this suspension. + IsSuspended() bool +} + // The RequeueConfiguration interface is meant to be implemented by components (or their spec) which offer // tweaking the requeue interval (by default, it would be 10 minutes). type RequeueConfiguration interface { @@ -170,6 +178,16 @@ var _ ImpersonationConfiguration = &ImpersonationSpec{} // +kubebuilder:object:generate=true +// SuspensionSpec defines whether the reconciliation (apply) or the implementing component is suspended. +// Components providing SuspensionConfiguration may include this into their spec. +type SuspensionSpec struct { + Suspend bool `json:"suspend,omitempty"` +} + +var _ SuspensionConfiguration = &SuspensionSpec{} + +// +kubebuilder:object:generate=true + // RequeueSpec defines the requeue interval, that is, the interval after which components will be re-reconciled after a successful reconciliation. // Components providing RequeueConfiguration may include this into their spec. type RequeueSpec struct { diff --git a/pkg/component/zz_generated.deepcopy.go b/pkg/component/zz_generated.deepcopy.go index d65a2e3c..48d4ef12 100644 --- a/pkg/component/zz_generated.deepcopy.go +++ b/pkg/component/zz_generated.deepcopy.go @@ -508,6 +508,21 @@ func (in *Status) DeepCopy() *Status { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SuspensionSpec) DeepCopyInto(out *SuspensionSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SuspensionSpec. +func (in *SuspensionSpec) DeepCopy() *SuspensionSpec { + if in == nil { + return nil + } + out := new(SuspensionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TimeoutSpec) DeepCopyInto(out *TimeoutSpec) { *out = *in diff --git a/website/content/en/docs/concepts/types.md b/website/content/en/docs/concepts/types.md index 548c3f9a..b9a6e09f 100644 --- a/website/content/en/docs/concepts/types.md +++ b/website/content/en/docs/concepts/types.md @@ -122,8 +122,21 @@ type ImpersonationConfiguration interface { to use different user/groups for the deployment of dependent objects. -Note that, as mentioned above, the interfaces `PlacementConfiguration`, `ClientConfiguration` and `ImpersonationConfiguration` can be implemented by the component -itself as well as by its spec type. In the theoretical case that both is the case, the implementation on the component level takes higher precedence. +Sometimes it is desired to suspend the reconciliation of a component. In offer this, the compoent (or its spec) can implement the interface + +```go +package component + +// The SuspensionConfiguration interface is meant to be implemented by components (or their spec) which offer +// the ability to suspend reconciliation. +type SuspensionConfiguration interface { + // Whether the reconciliation (apply) or the implementing component is supended. If true the component goes + // into Pending state with reason Suspended. Note that deletion is not affected by this suspension. + IsSuspended() bool +} +``` + +Note that, as mentioned above, the interfaces `PlacementConfiguration`, `ClientConfiguration`, `ImpersonationConfiguration` and `SuspensionConfiguration` can be implemented by the component itself as well as by its spec type. In the theoretical case that both implement it, the component takes higher precedence. ## The Generator interface