ctrlfwk

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 17, 2025 License: BSD-3-Clause Imports: 28 Imported by: 0

README

Controller Framework (ctrlfwk)

Pipeline Coverage Go Version Go Reference GitHub release License Go Report Card

A powerful and extensible framework for building Kubernetes controllers using controller-runtime. Transform your imperative controller logic into a declarative, step-based system that's easier to understand, test, and extend.

Key Features

  • Step-based Reconciliation: Break complex logic into manageable steps
  • Declarative Resources: Builder pattern for resource and dependency management
  • Type Safety: Full generic support for custom resources
  • Minimal Migration: Works with existing Kubebuilder controllers
  • Built-in Observability: Instrumentation, logging, and tracing

Quick Start

Installation
go get github.com/u-ctf/controller-fwk
Example Usage

Transform your Kubebuilder controller with minimal changes:

func (r *TestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    logger := logf.FromContext(ctx)

    stepper := ctrlfwk.NewStepper(logger,
        ctrlfwk.WithStep(ctrlfwk.NewFindControllerCustomResourceStep(r)),
        ctrlfwk.WithStep(ctrlfwk.NewResolveDynamicDependenciesStep(r)),
        ctrlfwk.WithStep(ctrlfwk.NewReconcileResourcesStep(r)),
        ctrlfwk.WithStep(ctrlfwk.NewEndStep(r, ctrlfwk.SetReadyCondition(r))),
    )

    return stepper.Execute(ctx, req)
}

Documentation

📚 Visit our Wiki for comprehensive guides and documentation:

Support & Community

Contributing

We welcome contributions! Please see our Contributing Guide for details.


Built with ❤️ by the U-CTF team

Documentation

Index

Constants

View Source
const (
	FinalizerDependenciesManagedBy = "dependencies.ctrlfwk.com/cleanup-dependencies-managed-by"

	// LabelReconciliationPaused can be added to a resource to pause its reconciliation
	// when using resources that support pausing.
	// It can also be added to CRs to pause the whole reconciliation if the NotPausedPredicate is used.
	// You can set the value to anything, so you can use it to document who/what paused the reconciliation.
	LabelReconciliationPaused = "ctrlfwk.com/pause"
)
View Source
const (
	StepFindControllerCustomResource = "find controller custom resource"
	StepAddFinalizer                 = "adding finalizer %s"
	StepExecuteFinalizer             = "executing finalizer %s"
	StepResolveDependency            = "resolve dependency %s"
	StepResolveDependencies          = "resolve dependencies"
	StepReconcileResource            = "reconcile resource %s"
	StepReconcileResources           = "reconcile resources"
	StepEndReconciliation            = "end reconciliation"
)
View Source
const (
	AnnotationRef = "dependencies.ctrlfwk.com/managed-by"
)

Variables

This section is empty.

Functions

func AddManagedBy

func AddManagedBy(obj client.Object, controlledBy client.Object, scheme *runtime.Scheme) (changed bool, err error)

AddManagedBy adds a ManagedBy reference to the specified object's annotations. It returns true if the annotation was added or modified, and false if the reference already exists. If there is an error during the process, it returns the error.

func DecodeMetaTime

func DecodeMetaTime() mapstructure.DecodeHookFuncType

func GetAnnotation

func GetAnnotation(obj client.Object, key string) string

func GetContract

func GetContract[K any](object *unstructured.Unstructured, path ...string) (*K, error)

func GetManagedByReconcileRequests

func GetManagedByReconcileRequests(ownedBy client.Object, scheme *runtime.Scheme) (func(ctx context.Context, obj client.Object) []reconcile.Request, error)

func IsFinalizing

func IsFinalizing(obj client.Object) bool

func NewInstanceOf

func NewInstanceOf[T client.Object](object T) T

func NilFinalizerFunc

func NilFinalizerFunc(ctx context.Context, logger logr.Logger, req ctrl.Request) (done bool, err error)

func PatchCustomResourceStatus

func PatchCustomResourceStatus[CustomResourceType client.Object](ctx Context[CustomResourceType], reconciler Reconciler[CustomResourceType]) error

PatchCustomResourceStatus patches the status subresource of the custom resource stored in the context. This function assumes that the context contains a ReconcilerContextData with the CustomResource field populated. The step "FindControllerResource" does exactly that, populating the context.

It also sets the updated custom resource back into the context after patching.

func RemoveManagedBy

func RemoveManagedBy(obj client.Object, controlledBy client.Object, scheme *runtime.Scheme) (changed bool, err error)

func SetAnnotation

func SetAnnotation(obj client.Object, key, value string)

func SetReadyCondition

func SetReadyCondition[ControllerResourceType client.Object](_ Reconciler[ControllerResourceType]) func(obj ControllerResourceType) (bool, error)

SetReadyCondition is a function type that sets the Ready condition on a controller resource. It uses reflection and assumes that the controller resource has a standard status field with conditions. Your api MUST have a field like so:

type MyCustomResourceStatus struct {
    Conditions []metav1.Condition `json:"conditions,omitempty"`
    ...
}

If your status field or conditions field is named differently, this function will not work correctly.

func SetupWatch

func SetupWatch[
	ControllerResourceType ControllerCustomResource,
](
	reconciler ReconcilerWithWatcher[ControllerResourceType],
	object client.Object,
	isDependency bool,
) func(ctx Context[ControllerResourceType], req ctrl.Request) StepResult

Types

type Context

type Context[K client.Object] interface {
	context.Context

	ImplementsCustomResource[K]
}

func NewContext

func NewContext[K client.Object](ctx context.Context, reconciler Reconciler[K]) Context[K]

NewContext creates a new Context for the given reconciler and base context. K is the type of the custom resource being reconciled. You can use it as such:

func (reconciler *SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := logf.FromContext(ctx)
	context := ctrlfwk.NewContext(ctx, reconciler)

type ContextWithData

type ContextWithData[K client.Object, D any] struct {
	Context[K]
	Data D
}

ContextWithData is a context that holds additional data of type D along with the base context. K is the type of the custom resource being reconciled. D is the type of the additional data to be stored in the context.

func NewContextWithData

func NewContextWithData[K client.Object, D any](ctx context.Context, reconciler Reconciler[K], data D) *ContextWithData[K, D]

NewContextWithData creates a new ContextWithData for the given reconciler, base context, and data. K is the type of the custom resource being reconciled. D is the type of the additional data to be stored in the context. You can use it as such:

func (reconciler *TestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := logf.FromContext(ctx)
	context := ctrlfwk.NewContextWithData(ctx, reconciler, &MyDataType{})

type ControllerCustomResource

type ControllerCustomResource interface {
	client.Object
}

type CustomResource

type CustomResource[K client.Object] struct {
	CR K
	// contains filtered or unexported fields
}

func (*CustomResource[K]) GetCleanCustomResource

func (cr *CustomResource[K]) GetCleanCustomResource() K

GetCleanCustomResource gives back the resource that was stored previously unedited of any changes that the resource may have went through, It is especially useful to generate patches between the time it was first seen and the second time.

func (*CustomResource[K]) GetCustomResource

func (cr *CustomResource[K]) GetCustomResource() K

GetCustomResource gives back the resource that was stored previously, This resource can be edited as it should always be a client.Object which is a pointer to something

func (*CustomResource[K]) SetCustomResource

func (cr *CustomResource[K]) SetCustomResource(key K)

SetCustomResource sets the resource and also the base resource, It should only be used once per reconciliation.

type Dependency

type Dependency[CustomResourceType client.Object, ContextType Context[CustomResourceType], DependencyType client.Object] struct {
	// contains filtered or unexported fields
}

func (*Dependency[CustomResourceType, ContextType, DependencyType]) AfterReconcile

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) AfterReconcile(ctx ContextType, resource client.Object) error

func (*Dependency[CustomResourceType, ContextType, DependencyType]) BeforeReconcile

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) BeforeReconcile(ctx ContextType) error

func (*Dependency[CustomResourceType, ContextType, DependencyType]) Get

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) Get() client.Object

func (*Dependency[CustomResourceType, ContextType, DependencyType]) ID

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) ID() string

func (*Dependency[CustomResourceType, ContextType, DependencyType]) IsOptional

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) IsOptional() bool

func (*Dependency[CustomResourceType, ContextType, DependencyType]) IsReady

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) IsReady() bool

func (*Dependency[CustomResourceType, ContextType, DependencyType]) Key

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) Key() types.NamespacedName

func (*Dependency[CustomResourceType, ContextType, DependencyType]) Kind

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) Kind() string

func (*Dependency[CustomResourceType, ContextType, DependencyType]) New

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) New() client.Object

func (*Dependency[CustomResourceType, ContextType, DependencyType]) Set

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) Set(obj client.Object)

func (*Dependency[CustomResourceType, ContextType, DependencyType]) ShouldAddManagedByAnnotation

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) ShouldAddManagedByAnnotation() bool

func (*Dependency[CustomResourceType, ContextType, DependencyType]) ShouldWaitForReady

func (c *Dependency[CustomResourceType, ContextType, DependencyType]) ShouldWaitForReady() bool

type DependencyBuilder

type DependencyBuilder[CustomResourceType client.Object, ContextType Context[CustomResourceType], DependencyType client.Object] struct {
	// contains filtered or unexported fields
}

DependencyBuilder provides a fluent builder pattern for creating Dependency instances. It enables declarative construction of dependencies with type safety and method chaining.

Type parameters:

  • CustomResourceType: The custom resource that owns this dependency
  • ContextType: The context type containing the custom resource and additional data
  • DependencyType: The Kubernetes resource type this dependency represents

func NewDependencyBuilder

func NewDependencyBuilder[
	CustomResourceType client.Object,
	ContextType Context[CustomResourceType],
	DependencyType client.Object,
](
	ctx ContextType,
	_ DependencyType,
) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

NewDependencyBuilder creates a new DependencyBuilder for constructing dependencies on external Kubernetes resources.

Dependencies are external resources that your custom resource depends on to function correctly. They are resolved during the dependency resolution step of reconciliation.

Parameters:

  • ctx: The context containing the custom resource and additional data
  • _: A zero-value instance used for type inference (e.g., &corev1.Secret{})

The dependency will only be resolved when used with ResolveDependencyStep or ResolveDynamicDependenciesStep during reconciliation.

Common use cases:

  • Waiting for secrets or configmaps to be available
  • Ensuring other custom resources are ready
  • Validating external service availability

Example:

// Wait for a secret to contain required data
dep := NewDependencyBuilder(ctx, &corev1.Secret{}).
	WithName("database-credentials").
	WithNamespace(ctx.GetCustomResource().Namespace).
	WithWaitForReady(true).
	WithIsReadyFunc(func(secret *corev1.Secret) bool {
		return secret.Data["username"] != nil && secret.Data["password"] != nil
	}).
	WithOutput(ctx.Data.DatabaseSecret).
	Build()

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) Build

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) Build() *Dependency[CustomResourceType, ContextType, DependencyType]

Build constructs and returns the final Dependency instance with all configured options.

This method finalizes the builder pattern and creates the dependency that can be used in reconciliation steps. The returned dependency contains all the configuration specified through the builder methods.

The dependency must be used with appropriate reconciliation steps (such as ResolveDynamicDependenciesStep) to actually perform the dependency resolution.

Returns a configured Dependency instance ready for use in reconciliation.

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithAddManagedByAnnotation

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithAddManagedByAnnotation(add bool) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithAddManagedByAnnotation controls whether to add a "managed-by" annotation to the dependency resource.

When enabled, this adds metadata to help identify which controller is managing or depending on this resource. This is useful for:

  • Debugging resource relationships
  • Resource lifecycle management
  • Avoiding conflicts between controllers

Also, when enabled, if the reconciler has a Watcher configured, it will automatically watch for changes to this dependency resource and trigger reconciliations accordingly.

This is not enabled by default to avoid unnecessary annotations on resources.

The annotation typically follows the format:

"dependencies.ctrlfwk.com/managed-by": "[{'name':'<controller-name>','namespace':'<controller-namespace>','gvk':{'group':'<group>','version':'<version>','kind':'<kind>'}}]"

Example:

.WithAddManagedByAnnotation(true) // Mark this resource as managed by our controller

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithAfterReconcile

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithAfterReconcile(f func(ctx ContextType, resource DependencyType) error) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithAfterReconcile registers a hook function to execute after successful dependency resolution.

This function is called with the resolved dependency resource and can be used for post-processing, validation, or updating your custom resource's status. If the function returns an error, the reconciliation will fail.

The resource parameter contains the current state of the resolved dependency.

Common use cases:

  • Extracting and caching dependency data
  • Updating custom resource status with dependency information
  • Triggering additional processing based on dependency state

Example:

.WithAfterReconcile(func(ctx MyContext, secret *corev1.Secret) error {
	// Cache database connection string from secret
	connStr := string(secret.Data["connection-string"])
	ctx.Data.DatabaseConnectionString = connStr
	return updateCustomResourceStatus(ctx, "DatabaseReady", "Connected")
})

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithBeforeReconcile

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithBeforeReconcile(f func(ctx ContextType) error) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithBeforeReconcile registers a hook function to execute before dependency resolution.

This function is called before attempting to resolve the dependency and can be used for setup tasks, validation, or logging. If the function returns an error, dependency resolution will be aborted.

Common use cases:

  • Validating preconditions
  • Setting up authentication
  • Logging dependency resolution attempts

Example:

.WithBeforeReconcile(func(ctx MyContext) error {
	logger := ctx.GetLogger()
	logger.Info("Resolving database secret dependency")
	return nil
})

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithIsReadyFunc

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithIsReadyFunc(f func(obj DependencyType) bool) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithIsReadyFunc defines custom logic to determine if the dependency is ready for use.

The provided function is called with the resolved dependency resource and should return true if the dependency meets your readiness criteria, false otherwise.

If no readiness function is provided, the dependency is considered ready as soon as it exists in the cluster.

Common readiness patterns:

  • Checking for specific data fields in secrets/configmaps
  • Validating status conditions on custom resources
  • Ensuring required labels or annotations are present

Example:

.WithIsReadyFunc(func(secret *corev1.Secret) bool {
	// Secret is ready when it contains required database credentials
	return secret.Data["host"] != nil &&
	       secret.Data["username"] != nil &&
	       secret.Data["password"] != nil
})

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithName

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithName(name string) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithName specifies the name of the Kubernetes resource to depend on.

This is the metadata.name field of the target resource. The name is required for dependency resolution and should match exactly with the existing resource.

Example:

.WithName("database-credentials") // Look for resource named "database-credentials"

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithNamespace

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithNamespace(namespace string) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithNamespace specifies the namespace where the dependency resource is located.

This is the metadata.namespace field of the target resource. If not specified, the dependency will be looked up in the same namespace as the custom resource.

For cluster-scoped resources, this field is ignored.

Example:

.WithNamespace("kube-system") // Look in kube-system namespace
.WithNamespace(ctx.GetCustomResource().Namespace) // Same namespace as custom resource

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithOptional

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithOptional(optional bool) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithOptional configures whether this dependency is required for reconciliation.

When set to true, the dependency resolution will continue even if this dependency is missing or not ready. When false (default), missing or unready dependencies will cause reconciliation to requeue and wait.

Use optional dependencies for:

  • Feature flags or optional configurations
  • Dependencies that provide enhanced functionality but aren't required
  • Resources that may not exist in all environments

Example:

// Optional feature configuration
dep := NewDependencyBuilder(ctx, &corev1.ConfigMap{}).
	WithName("optional-features").
	WithOptional(true). // Don't block reconciliation if missing
	Build()

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithOutput

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithOutput(obj DependencyType) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithOutput specifies where to store the resolved dependency resource.

The provided object will be populated with the dependency's data after successful resolution. This allows other parts of your reconciliation logic to access the dependency's current state.

The output object should be a field in your context's data structure to ensure it's accessible throughout the reconciliation process.

Note: Setting an output is optional. If you only need to verify the dependency exists and is ready, you can use WithAfterReconcile for post-resolution actions without storing the full resource.

Example:

type MyContextData struct {
	DatabaseSecret *corev1.Secret
}

dep := NewDependencyBuilder(ctx, &corev1.Secret{}).
	WithName("database-creds").
	WithOutput(ctx.Data.DatabaseSecret). // Store resolved secret here
	Build()

This allows you to access ctx.Data.DatabaseSecret later in your reconciliation logic.

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithReadinessCondition

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithReadinessCondition(f func(obj DependencyType) bool) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithReadinessCondition is an alias for WithIsReadyFunc that defines custom readiness logic.

This method provides the same functionality as WithIsReadyFunc but with a more descriptive name. Use whichever method name feels more natural in your context.

See WithIsReadyFunc for detailed documentation and examples.

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithUserIdentifier

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithUserIdentifier(identifier string) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithUserIdentifier assigns a custom identifier for this dependency.

This identifier is used for logging, debugging, and distinguishing between multiple dependencies of the same type. If not provided, a default identifier will be generated based on the resource type and name.

Useful for:

  • Debugging dependency resolution issues
  • Providing meaningful names in logs
  • Distinguishing between similar dependencies

Example:

.WithUserIdentifier("database-connection-secret") // Custom name for logs

func (*DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithWaitForReady

func (b *DependencyBuilder[CustomResourceType, ContextType, DependencyType]) WithWaitForReady(waitForReady bool) *DependencyBuilder[CustomResourceType, ContextType, DependencyType]

WithWaitForReady determines whether reconciliation should wait for this dependency to become ready before proceeding.

When true (recommended), reconciliation will requeue if the dependency exists but is not yet ready according to the readiness function. When false, the dependency is only checked for existence.

This is particularly useful for:

  • Resources that need initialization time
  • External services that may be temporarily unavailable
  • Custom resources with complex readiness conditions

Example:

.WithWaitForReady(true) // Wait for dependency to be ready, don't just check existence

type FinalizingFunc

type FinalizingFunc func(ctx context.Context, logger logr.Logger, req ctrl.Request) (done bool, err error)

type GenericDependency

type GenericDependency[CustomResourceType client.Object, ContextType Context[CustomResourceType]] interface {
	ID() string
	New() client.Object
	Key() types.NamespacedName
	Set(obj client.Object)
	Get() client.Object
	ShouldWaitForReady() bool
	ShouldAddManagedByAnnotation() bool
	IsReady() bool
	IsOptional() bool
	Kind() string

	// Hooks
	BeforeReconcile(ctx ContextType) error
	AfterReconcile(ctx ContextType, resource client.Object) error
}

type GenericResource

type GenericResource[CustomResource client.Object, ContextType Context[CustomResource]] interface {
	ID() string
	ObjectMetaGenerator() (obj client.Object, delete bool, err error)
	ShouldDeleteNow() bool
	GetMutator(obj client.Object) func() error
	Set(obj client.Object)
	Get() client.Object
	Kind() string
	IsReady(obj client.Object) bool
	RequiresManualDeletion(obj client.Object) bool
	CanBePaused() bool

	// Hooks
	BeforeReconcile(ctx ContextType) error
	AfterReconcile(ctx ContextType, resource client.Object) error
	OnCreate(ctx ContextType, resource client.Object) error
	OnUpdate(ctx ContextType, resource client.Object) error
	OnDelete(ctx ContextType, resource client.Object) error
	OnFinalize(ctx ContextType, resource client.Object) error
}

type ImplementsCustomResource

type ImplementsCustomResource[K client.Object] interface {
	GetCleanCustomResource() K
	GetCustomResource() K
	SetCustomResource(key K)
}

type ManagedBy

type ManagedBy struct {
	Name      string                  `json:"name"`
	Namespace string                  `json:"namespace"`
	GVK       schema.GroupVersionKind `json:"gvk"`
}

ManagedBy represents a reference to a controller managing a resource and it includes the controller's name, namespace, and GroupVersionKind (GVK) information.

func GetManagedBy

func GetManagedBy(obj client.Object) ([]ManagedBy, error)

GetManagedBy retrieves the list of ManagedBy references from the specified object's annotations. If the annotation is not present, it returns an empty slice. If there is an error during unmarshalling, it returns the error.

type Mutator

type Mutator[ResourceType client.Object] func(resource ResourceType) error

type NotPausedPredicate added in v1.1.0

type NotPausedPredicate = TypedNotPausedPredicate[client.Object]

NotPausedPredicate is a predicate that filters out paused resources from reconciliation. Resources with the ctrlfwk.com/pause label will not trigger reconciliation events.

type Reconciler

type Reconciler[ControllerResourceType ControllerCustomResource] interface {
	client.Client

	For(ControllerResourceType)
}

type ReconcilerWithDependencies

type ReconcilerWithDependencies[ControllerResourceType ControllerCustomResource, ContextType Context[ControllerResourceType]] interface {
	Reconciler[ControllerResourceType]

	GetDependencies(ctx ContextType, req ctrl.Request) ([]GenericDependency[ControllerResourceType, ContextType], error)
}

type ReconcilerWithEventRecorder

type ReconcilerWithEventRecorder[ControllerResourceType ControllerCustomResource] interface {
	Reconciler[ControllerResourceType]

	record.EventRecorder
}

type ReconcilerWithResources

type ReconcilerWithResources[ControllerResourceType ControllerCustomResource, ContextType Context[ControllerResourceType]] interface {
	Reconciler[ControllerResourceType]

	GetResources(ctx ContextType, req ctrl.Request) ([]GenericResource[ControllerResourceType, ContextType], error)
}

type ReconcilerWithWatcher

type ReconcilerWithWatcher[ControllerResourceType ControllerCustomResource] interface {
	Reconciler[ControllerResourceType]

	Watcher
}

type Resource

type Resource[CustomResource client.Object, ContextType Context[CustomResource], ResourceType client.Object] struct {
	// contains filtered or unexported fields
}

func (*Resource[CustomResource, ContextType, ResourceType]) AfterReconcile

func (c *Resource[CustomResource, ContextType, ResourceType]) AfterReconcile(ctx ContextType, resource client.Object) error

func (*Resource[CustomResource, ContextType, ResourceType]) BeforeReconcile

func (c *Resource[CustomResource, ContextType, ResourceType]) BeforeReconcile(ctx ContextType) error

func (*Resource[CustomResource, ContextType, ResourceType]) CanBePaused added in v1.1.0

func (c *Resource[CustomResource, ContextType, ResourceType]) CanBePaused() bool

func (*Resource[CustomResource, ContextType, ResourceType]) Get

func (c *Resource[CustomResource, ContextType, ResourceType]) Get() client.Object

func (*Resource[CustomResource, ContextType, ResourceType]) GetMutator

func (c *Resource[CustomResource, ContextType, ResourceType]) GetMutator(obj client.Object) func() error

func (*Resource[CustomResource, ContextType, ResourceType]) ID

func (c *Resource[CustomResource, ContextType, ResourceType]) ID() string

func (*Resource[CustomResource, ContextType, ResourceType]) IsReady

func (c *Resource[CustomResource, ContextType, ResourceType]) IsReady(obj client.Object) bool

func (*Resource[CustomResource, ContextType, ResourceType]) Kind

func (c *Resource[CustomResource, ContextType, ResourceType]) Kind() string

func (*Resource[CustomResource, ContextType, ResourceType]) ObjectMetaGenerator

func (c *Resource[CustomResource, ContextType, ResourceType]) ObjectMetaGenerator() (obj client.Object, skip bool, err error)

func (*Resource[CustomResource, ContextType, ResourceType]) OnCreate

func (c *Resource[CustomResource, ContextType, ResourceType]) OnCreate(ctx ContextType, resource client.Object) error

func (*Resource[CustomResource, ContextType, ResourceType]) OnDelete

func (c *Resource[CustomResource, ContextType, ResourceType]) OnDelete(ctx ContextType, resource client.Object) error

func (*Resource[CustomResource, ContextType, ResourceType]) OnFinalize

func (c *Resource[CustomResource, ContextType, ResourceType]) OnFinalize(ctx ContextType, resource client.Object) error

func (*Resource[CustomResource, ContextType, ResourceType]) OnUpdate

func (c *Resource[CustomResource, ContextType, ResourceType]) OnUpdate(ctx ContextType, resource client.Object) error

func (*Resource[CustomResource, ContextType, ResourceType]) RequiresManualDeletion

func (c *Resource[CustomResource, ContextType, ResourceType]) RequiresManualDeletion(obj client.Object) bool

func (*Resource[CustomResource, ContextType, ResourceType]) Set

func (c *Resource[CustomResource, ContextType, ResourceType]) Set(obj client.Object)

func (*Resource[CustomResource, ContextType, ResourceType]) ShouldDeleteNow

func (c *Resource[CustomResource, ContextType, ResourceType]) ShouldDeleteNow() bool

type ResourceBuilder

type ResourceBuilder[CustomResource client.Object, ContextType Context[CustomResource], ResourceType client.Object] struct {
	// contains filtered or unexported fields
}

ResourceBuilder provides a fluent builder pattern for creating Resource instances that your custom resource controller manages during reconciliation.

Resources represent Kubernetes objects that your controller creates, updates, and manages as part of achieving the desired state for your custom resource. Unlike dependencies, resources are owned by your custom resource and are created/managed by your controller.

Type parameters:

  • CustomResource: The custom resource that owns and manages this resource
  • ContextType: The context type containing the custom resource and additional data
  • ResourceType: The Kubernetes resource type this builder creates (e.g., Deployment, Service)

Common use cases:

  • Creating Deployments for your application custom resource
  • Managing Services, ConfigMaps, and Secrets
  • Setting up RBAC resources (Roles, RoleBindings)
  • Creating PersistentVolumeClaims for stateful applications

Example:

// Create a Deployment resource for your custom resource
deployment := NewResourceBuilder(ctx, &appsv1.Deployment{}).
	WithKeyFunc(func() types.NamespacedName {
		return types.NamespacedName{
			Name:      ctx.GetCustomResource().Name + "-deployment",
			Namespace: ctx.GetCustomResource().Namespace,
		}
	}).
	WithMutator(func(deployment *appsv1.Deployment) error {
		// Configure deployment spec based on custom resource
		deployment.Spec.Replicas = ctx.GetCustomResource().Spec.Replicas
		return controllerutil.SetOwnerReference(ctx.GetCustomResource(), deployment, scheme)
	}).
	WithReadinessCondition(func(deployment *appsv1.Deployment) bool {
		return deployment.Status.ReadyReplicas == *deployment.Spec.Replicas
	}).
	Build()

func NewResourceBuilder

func NewResourceBuilder[CustomResource client.Object, ContextType Context[CustomResource], ResourceType client.Object](ctx ContextType, _ ResourceType) *ResourceBuilder[CustomResource, ContextType, ResourceType]

NewResourceBuilder creates a new ResourceBuilder for constructing managed Kubernetes resources.

Resources are Kubernetes objects that your controller creates and manages to implement the desired state defined by your custom resource. They are typically owned by your custom resource and have owner references set for garbage collection.

Parameters:

  • ctx: The context containing the custom resource and additional data
  • _: A zero-value instance used for type inference (e.g., &appsv1.Deployment{})

The resource will be reconciled when used with ReconcileResourcesStep or ReconcileResourceStep during the reconciliation process.

Key differences from dependencies:

  • Resources are CREATED and MANAGED by your controller
  • Dependencies are CONSUMED by your controller (external resources)
  • Resources typically have owner references to your custom resource
  • Resources are deleted when your custom resource is deleted

Common resource patterns:

  • Application deployments and services
  • Configuration resources (ConfigMaps, Secrets)
  • RBAC resources for your application
  • Storage resources (PVCs) for stateful applications

Example:

// Create a Service resource for your web application
service := NewResourceBuilder(ctx, &corev1.Service{}).
	WithKeyFunc(func() types.NamespacedName {
		return types.NamespacedName{
			Name:      ctx.GetCustomResource().Name + "-service",
			Namespace: ctx.GetCustomResource().Namespace,
		}
	}).
	WithMutator(func(svc *corev1.Service) error {
		svc.Spec.Selector = map[string]string{"app": ctx.GetCustomResource().Name}
		svc.Spec.Ports = []corev1.ServicePort{{
			Port:       80,
			TargetPort: intstr.FromInt(8080),
			Protocol:   corev1.ProtocolTCP,
		}}
		return controllerutil.SetOwnerReference(ctx.GetCustomResource(), svc, scheme)
	}).
	Build()

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) Build

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) Build() *Resource[CustomResource, ContextType, ResourceType]

Build constructs and returns the final Resource instance with all configured options.

This method finalizes the builder pattern and creates a resource that can be used in reconciliation steps. The returned resource contains all the configuration specified through the builder methods.

The resource must be used with appropriate reconciliation steps (such as ReconcileResourcesStep) to actually perform the resource management operations.

Validation:

  • At least one of WithKey or WithKeyFunc must be called before Build()
  • WithMutator is typically required for meaningful resource management

Returns a configured Resource instance ready for use in reconciliation.

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterCreate

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterCreate(f func(ctx ContextType, resource ResourceType) error) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithAfterCreate registers a hook function that executes only when a resource is newly created.

This function is called specifically when a resource is created for the first time, not when it's updated. It's useful for one-time initialization tasks, logging creation events, or triggering operations that should only happen on resource creation.

The function receives the newly created resource in its current state from the cluster, including any fields that were populated by Kubernetes (like UID, creation timestamp).

Common use cases:

  • Logging resource creation events
  • One-time initialization tasks
  • Sending creation notifications to external systems
  • Recording metrics for new resource creation
  • Triggering initial configuration workflows

Example:

.WithAfterCreate(func(ctx MyContext, deployment *appsv1.Deployment) error {
	logger := ctx.GetLogger()
	logger.Info("Deployment created successfully",
		"name", deployment.Name,
		"namespace", deployment.Namespace,
		"uid", deployment.UID,
		"replicas", *deployment.Spec.Replicas)

	// Send notification to monitoring system
	return ctx.NotifyExternalSystem("deployment_created", deployment.Name)
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterDelete

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterDelete(f func(ctx ContextType, resource ResourceType) error) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithAfterDelete registers a hook function that executes after a resource is deleted.

This function is called when a resource has been successfully deleted from the cluster, either due to a delete condition being met or during custom resource finalization. It can be used for cleanup tasks, logging deletion events, or notifying external systems.

The function receives the resource object as it existed just before deletion. At this point, the resource no longer exists in the cluster.

Common use cases:

  • Logging resource deletion events
  • Cleaning up external resources or dependencies
  • Notifying monitoring or auditing systems
  • Recording metrics for resource deletion
  • Updating custom resource status to reflect deletion

Example:

.WithAfterDelete(func(ctx MyContext, pvc *corev1.PersistentVolumeClaim) error {
	logger := ctx.GetLogger()
	logger.Info("PersistentVolumeClaim deleted",
		"name", pvc.Name,
		"namespace", pvc.Namespace,
		"storageClass", *pvc.Spec.StorageClassName)

	// Clean up any backup data associated with this PVC
	return ctx.CleanupBackupData(pvc.Name)
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterFinalize

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterFinalize(f func(ctx ContextType, resource ResourceType) error) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithAfterFinalize registers a hook function that executes during custom resource finalization.

This function is called when the custom resource is being deleted and the resource needs to be cleaned up as part of the finalization process. It's used for graceful shutdown procedures and cleanup of resources that require special handling.

The function should perform any necessary cleanup operations and ensure that external dependencies are properly handled before the custom resource is fully removed.

Common use cases:

  • Graceful shutdown of applications
  • Backup of important data before deletion
  • Cleanup of external resources or registrations
  • Notifying external systems of resource removal
  • Removing finalizers from dependent resources

Example:

.WithAfterFinalize(func(ctx MyContext, deployment *appsv1.Deployment) error {
	logger := ctx.GetLogger()

	// Gracefully scale down before deletion
	if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas > 0 {
		logger.Info("Scaling down deployment before finalization")
		zero := int32(0)
		deployment.Spec.Replicas = &zero
		return ctx.Update(deployment)
	}

	// Perform final cleanup
	return ctx.CleanupExternalResources(deployment.Name)
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterReconcile

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterReconcile(f func(ctx ContextType, resource ResourceType) error) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithAfterReconcile registers a hook function to execute after successful resource reconciliation.

This function is called after the resource has been successfully created, updated, or verified to exist with the correct configuration. It receives the current resource state from the cluster and can be used for post-processing, status updates, or triggering additional operations.

If the function returns an error, the reconciliation will fail even though the resource operation itself was successful.

Common use cases:

  • Updating custom resource status with resource information
  • Triggering external system notifications
  • Caching resource data for future use
  • Logging successful operations
  • Initiating dependent operations

Example:

.WithAfterReconcile(func(ctx MyContext, service *corev1.Service) error {
	cr := ctx.GetCustomResource()

	// Update custom resource status with service information
	if cr.Status.ServiceStatus == nil {
		cr.Status.ServiceStatus = &ServiceStatus{}
	}
	cr.Status.ServiceStatus.Name = service.Name
	cr.Status.ServiceStatus.ClusterIP = service.Spec.ClusterIP

	// Set ready condition
	meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{
		Type:   "ServiceReady",
		Status: metav1.ConditionTrue,
		Reason: "ServiceCreated",
	})

	return ctx.PatchStatus()
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterUpdate

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithAfterUpdate(f func(ctx ContextType, resource ResourceType) error) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithAfterUpdate registers a hook function that executes only when a resource is updated.

This function is called specifically when an existing resource is modified, not when it's initially created. It's useful for tracking changes, logging update events, or triggering operations that should only happen when configuration changes.

The function receives the updated resource in its current state from the cluster after the update operation has completed successfully.

Common use cases:

  • Logging configuration changes
  • Triggering rolling updates or restarts
  • Notifying external systems of changes
  • Recording metrics for resource modifications
  • Validating update results

Example:

.WithAfterUpdate(func(ctx MyContext, deployment *appsv1.Deployment) error {
	logger := ctx.GetLogger()
	cr := ctx.GetCustomResource()

	logger.Info("Deployment updated successfully",
		"name", deployment.Name,
		"generation", deployment.Generation,
		"replicas", *deployment.Spec.Replicas)

	// Update custom resource status with latest generation
	cr.Status.DeploymentGeneration = deployment.Generation
	return ctx.PatchStatus()
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithBeforeReconcile

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithBeforeReconcile(f func(ctx ContextType) error) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithBeforeReconcile registers a hook function to execute before resource reconciliation.

This function is called before any resource operations (create, update, or delete) are performed. It can be used for setup tasks, validation, or preparation work. If the function returns an error, resource reconciliation will be aborted.

Common use cases:

  • Validating preconditions for resource creation
  • Setting up external dependencies
  • Performing cleanup of old resources
  • Logging reconciliation attempts
  • Initializing shared state

Example:

.WithBeforeReconcile(func(ctx MyContext) error {
	logger := ctx.GetLogger()
	cr := ctx.GetCustomResource()

	logger.Info("Reconciling application deployment", "version", cr.Spec.Version)

	// Validate configuration before proceeding
	if cr.Spec.Replicas < 0 {
		return fmt.Errorf("replica count cannot be negative: %d", cr.Spec.Replicas)
	}

	return nil
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithCanBePaused added in v1.1.0

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithCanBePaused(canBePaused bool) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithCanBePaused specifies whether this resource supports pausing reconciliation.

When set to true, the resource will respect the paused state of the custom resource. If the custom resource is marked as paused (e.g., via a label), reconciliation for this resource will be skipped until the pause is lifted.

This is useful for scenarios where you want to temporarily halt changes to certain resources without deleting them or affecting other parts of the system.

Common use cases:

  • Temporarily halting updates during maintenance windows
  • Pausing non-critical resources while troubleshooting issues
  • Allowing manual intervention before resuming automated management

Example:

.WithCanBePaused(true) // Enable pausing for this resource

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithCanBePausedFunc added in v1.1.0

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithCanBePausedFunc(f func() bool) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithCanBePausedFunc specifies a function to determine if this resource supports pausing reconciliation.

The provided function is called during reconciliation to check if the resource should respect the paused state of the custom resource. If it returns true, reconciliation for this resource will be skipped when the custom resource is paused.

This allows for dynamic control over which resources can be paused based on the current state or configuration of the custom resource.

Example:

.WithCanBePausedFunc(func() bool {
    // Custom logic to determine if the resource can be paused
    return someCondition
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithKey

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithKey(name types.NamespacedName) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithKey specifies a static NamespacedName for the resource.

This is useful when the resource name and namespace are known at build time and don't need to be computed dynamically based on the custom resource state.

For dynamic naming based on custom resource properties, use WithKeyFunc instead.

Example:

.WithKey(types.NamespacedName{
	Name:      "my-app-service",
	Namespace: "default",
}) // Static name for the service

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithKeyFunc

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithKeyFunc(f func() types.NamespacedName) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithKeyFunc specifies a function that dynamically determines the resource's NamespacedName.

This function is called during reconciliation to determine where the resource should be created or found. It's evaluated each time the resource is reconciled, allowing for dynamic naming based on the current state of the custom resource.

The function typically derives the name from the custom resource's metadata or spec, and should return a consistent result for the same custom resource state.

Common patterns:

  • Prefixing with custom resource name: ctx.GetCustomResource().Name + "-suffix"
  • Using custom resource namespace: ctx.GetCustomResource().Namespace
  • Conditional naming based on custom resource spec
  • When the name is stored in the spec, you might wanna refer to the status when the spec field is updated or disappears

Example:

.WithKeyFunc(func() types.NamespacedName {
	cr := ctx.GetCustomResource()
	return types.NamespacedName{
		Name:      cr.Name + "-" + cr.Spec.Component, // Dynamic based on spec
		Namespace: cr.Namespace,                        // Same namespace as CR
	}
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithMutator

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithMutator(f Mutator[ResourceType]) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithMutator specifies the function that configures the resource's desired state.

The mutator function is called whenever the resource needs to be created or updated. It receives the resource object and should configure all necessary fields to match the desired state defined by your custom resource.

The mutator should:

  • Set all required fields on the resource
  • Configure the resource based on custom resource spec
  • Set owner references for garbage collection
  • Apply labels, annotations, and other metadata
  • Return an error if configuration fails

The mutator is called for both CREATE and UPDATE operations, so it should be idempotent and handle both scenarios gracefully.

Example:

.WithMutator(func(deployment *appsv1.Deployment) error {
	cr := ctx.GetCustomResource()

	// Configure deployment based on custom resource
	deployment.Spec.Replicas = cr.Spec.Replicas
	deployment.Spec.Selector = &metav1.LabelSelector{
		MatchLabels: map[string]string{"app": cr.Name},
	}
	deployment.Spec.Template.Spec.Containers = []corev1.Container{{
		Name:  "app",
		Image: cr.Spec.Image,
	}}

	// Set owner reference for garbage collection
	return controllerutil.SetOwnerReference(cr, deployment, ctx.GetScheme())
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithOutput

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithOutput(obj ResourceType) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithOutput specifies where to store the reconciled resource after successful operations.

The provided object will be populated with the resource's current state from the cluster after reconciliation completes. This allows other parts of your controller logic to access the resource's runtime state, such as generated fields, status information, or cluster-assigned values.

The output object should be a field in your context's data structure to ensure it's accessible throughout the reconciliation process.

Common use cases:

  • Accessing service ClusterIP after creation
  • Reading generated secret data
  • Getting deployment status for custom resource status updates
  • Obtaining persistent volume claim details

Example:

type MyContextData struct {
	AppService *corev1.Service
}

service := NewResourceBuilder(ctx, &corev1.Service{}).
	// ... other configuration ...
	WithOutput(ctx.Data.AppService). // Store reconciled service here
	Build()

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithReadinessCondition

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithReadinessCondition(f func(obj ResourceType) bool) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithReadinessCondition defines custom logic to determine when the resource is ready.

The provided function is called with the current resource state and should return true if the resource has reached the desired operational state. This affects when the overall custom resource is considered ready and can influence reconciliation flow.

If no readiness condition is provided, the resource is considered ready as soon as it exists and has been successfully created or updated.

Common readiness patterns:

  • Deployments: Check that ReadyReplicas == DesiredReplicas
  • Services: Verify endpoints are populated
  • Jobs: Check for successful completion
  • Custom resources: Examine status conditions

Example:

.WithReadinessCondition(func(deployment *appsv1.Deployment) bool {
	// Deployment is ready when all replicas are ready
	if deployment.Spec.Replicas == nil {
		return deployment.Status.ReadyReplicas > 0
	}
	return deployment.Status.ReadyReplicas == *deployment.Spec.Replicas &&
	       deployment.Status.UpdatedReplicas == *deployment.Spec.Replicas
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithRequireManualDeletionForFinalize

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithRequireManualDeletionForFinalize(f func(obj ResourceType) bool) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithRequireManualDeletionForFinalize specifies when a resource requires manual cleanup during custom resource finalization.

When the custom resource is being deleted, this function is called to determine if the resource requires special handling before the custom resource can be fully removed. If the function returns true, the resource must be manually cleaned up before finalization can complete.

This is typically used for resources that:

  • Have external dependencies that need cleanup
  • Store important data that needs backup
  • Have complex deletion procedures
  • Need graceful shutdown processes

The function receives the current resource state to make decisions based on the resource's current condition or configuration.

Example:

.WithRequireManualDeletionForFinalize(func(pvc *corev1.PersistentVolumeClaim) bool {
	// Require manual deletion for PVCs that contain important data
	if important, exists := pvc.Annotations["data.important"]; exists {
		return important == "true"
	}
	return false
})

// Another example: based on resource state
.WithRequireManualDeletionForFinalize(func(deployment *appsv1.Deployment) bool {
	// Require manual deletion if deployment is still running
	return deployment.Status.Replicas > 0
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithSkipAndDeleteOnCondition

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithSkipAndDeleteOnCondition(f func() bool) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithSkipAndDeleteOnCondition specifies when to skip creating or delete an existing resource.

The provided function is evaluated during reconciliation. When it returns true:

  • If the resource doesn't exist, it will be skipped (not created)
  • If the resource exists, it will be deleted

This is useful for conditional resource management based on custom resource configuration, feature flags, or environmental conditions.

The condition function is called each reconciliation cycle, so resources can be dynamically created or removed based on changing conditions.

Common use cases:

  • Feature toggles that enable/disable optional components
  • Environment-specific resources (dev vs prod)
  • Conditional scaling or resource allocation
  • Migration scenarios where resources are phased out

Example:

.WithSkipAndDeleteOnCondition(func() bool {
	cr := ctx.GetCustomResource()
	// Only create monitoring service if monitoring is enabled
	return !cr.Spec.Monitoring.Enabled
})

// Another example: conditional based on environment
.WithSkipAndDeleteOnCondition(func() bool {
	// Skip expensive resources in development environment
	return ctx.GetCustomResource().Spec.Environment == "development"
})

func (*ResourceBuilder[CustomResource, ContextType, ResourceType]) WithUserIdentifier

func (b *ResourceBuilder[CustomResource, ContextType, ResourceType]) WithUserIdentifier(identifier string) *ResourceBuilder[CustomResource, ContextType, ResourceType]

WithUserIdentifier assigns a custom identifier for this resource.

This identifier is used for logging, debugging, and distinguishing between multiple resources of the same type within your controller. If not provided, a default identifier will be generated based on the resource type.

The identifier appears in logs and error messages, making it easier to track specific resources during debugging and troubleshooting.

Useful for:

  • Distinguishing between multiple deployments (e.g., "frontend", "backend")
  • Providing meaningful names for different services
  • Creating clear audit trails in logs
  • Simplifying debugging of complex resource hierarchies

Example:

.WithUserIdentifier("frontend-deployment") // Clear name for logs
.WithUserIdentifier("database-service")    // Easy to identify in debugging

type ResourceVersionChangedPredicate

type ResourceVersionChangedPredicate struct {
	predicate.Funcs
}

func (ResourceVersionChangedPredicate) Create

func (ResourceVersionChangedPredicate) Delete

func (ResourceVersionChangedPredicate) Generic

func (ResourceVersionChangedPredicate) Update

type Step

type Step[K client.Object, C Context[K]] struct {
	// Name is the name of the step
	Name string

	// Step is the function to execute
	Step func(ctx C, logger logr.Logger, req ctrl.Request) StepResult
}

func NewAddFinalizerStep

func NewAddFinalizerStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler Reconciler[ControllerResourceType],
	finalizerName string,
) Step[ControllerResourceType, ContextType]

func NewEndStep

func NewEndStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler Reconciler[ControllerResourceType],
	setReadyCondF func(ControllerResourceType) (bool, error),
) Step[ControllerResourceType, ContextType]

func NewExecuteFinalizerStep

func NewExecuteFinalizerStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler Reconciler[ControllerResourceType],
	finalizerName string,
	finalizerFunc FinalizingFunc,
) Step[ControllerResourceType, ContextType]

func NewFindControllerCustomResourceStep

func NewFindControllerCustomResourceStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler Reconciler[ControllerResourceType],
) Step[ControllerResourceType, ContextType]

func NewReconcileResourceStep

func NewReconcileResourceStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler Reconciler[ControllerResourceType],
	resource GenericResource[ControllerResourceType, ContextType],
) Step[ControllerResourceType, ContextType]

func NewReconcileResourcesStep

func NewReconcileResourcesStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler ReconcilerWithResources[ControllerResourceType, ContextType],
) Step[ControllerResourceType, ContextType]

func NewResolveDependencyStep

func NewResolveDependencyStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler Reconciler[ControllerResourceType],
	dependency GenericDependency[ControllerResourceType, ContextType],
) Step[ControllerResourceType, ContextType]

func NewResolveDynamicDependenciesStep

func NewResolveDynamicDependenciesStep[
	ControllerResourceType ControllerCustomResource,
	ContextType Context[ControllerResourceType],
](
	_ ContextType,
	reconciler ReconcilerWithDependencies[ControllerResourceType, ContextType],
) Step[ControllerResourceType, ContextType]

func NewStep

func NewStep[K client.Object, C Context[K]](name string, step func(ctx C, logger logr.Logger, req ctrl.Request) StepResult) Step[K, C]

type StepResult

type StepResult struct {
	// contains filtered or unexported fields
}

func ResultEarlyReturn

func ResultEarlyReturn() StepResult

func ResultInError

func ResultInError(err error) StepResult

func ResultRequeueIn

func ResultRequeueIn(result time.Duration) StepResult

func ResultSuccess

func ResultSuccess() StepResult

func (StepResult) FromSubStep

func (result StepResult) FromSubStep() StepResult

func (StepResult) Normal

func (result StepResult) Normal() (ctrl.Result, error)

func (StepResult) ShouldReturn

func (result StepResult) ShouldReturn() bool

type Stepper

type Stepper[K client.Object, C Context[K]] struct {
	// contains filtered or unexported fields
}

Stepper is a utility to execute a series of steps in a controller. It allows for easy chaining of steps and handling of errors and requeues. Each step can be a function that returns a StepResult, which indicates whether to continue, requeue, or return an error. The Stepper can be used in a controller's Reconcile function to manage the execution of multiple steps in a clean and organized manner.

func (*Stepper[K, C]) Execute

func (stepper *Stepper[K, C]) Execute(ctx C, req ctrl.Request) (ctrl.Result, error)

type StepperBuilder

type StepperBuilder[K client.Object, C Context[K]] struct {
	// contains filtered or unexported fields
}

func NewStepperFor

func NewStepperFor[K client.Object, C Context[K]](ctx C, logger logr.Logger) *StepperBuilder[K, C]

func (*StepperBuilder[K, C]) Build

func (s *StepperBuilder[K, C]) Build() *Stepper[K, C]

WithLogger sets the logger for the Stepper.

func (*StepperBuilder[K, C]) WithStep

func (s *StepperBuilder[K, C]) WithStep(step Step[K, C]) *StepperBuilder[K, C]

WithLogger sets the logger for the Stepper.

type TypedNotPausedPredicate added in v1.1.0

type TypedNotPausedPredicate[object client.Object] struct{}

TypedNotPausedPredicate filters reconciliation events for resources marked as paused. When applied to a controller, it prevents the controller from queuing reconciliation requests for resources that have the pause label set.

func (TypedNotPausedPredicate[object]) Create added in v1.1.0

func (p TypedNotPausedPredicate[object]) Create(e event.TypedCreateEvent[object]) bool

func (TypedNotPausedPredicate[object]) Delete added in v1.1.0

func (p TypedNotPausedPredicate[object]) Delete(e event.TypedDeleteEvent[object]) bool

func (TypedNotPausedPredicate[object]) Generic added in v1.1.0

func (p TypedNotPausedPredicate[object]) Generic(e event.TypedGenericEvent[object]) bool

func (TypedNotPausedPredicate[object]) Update added in v1.1.0

func (p TypedNotPausedPredicate[object]) Update(e event.TypedUpdateEvent[object]) bool

type UntypedDependency

type UntypedDependency[CustomResourceType client.Object, ContextType Context[CustomResourceType]] struct {
	*Dependency[CustomResourceType, ContextType, *unstructured.Unstructured]
	// contains filtered or unexported fields
}

func (*UntypedDependency[CustomResourceType, ContextType]) Kind

func (c *UntypedDependency[CustomResourceType, ContextType]) Kind() string

func (*UntypedDependency[CustomResourceType, ContextType]) New

func (c *UntypedDependency[CustomResourceType, ContextType]) New() client.Object

func (*UntypedDependency[CustomResourceType, ContextType]) Set

func (c *UntypedDependency[CustomResourceType, ContextType]) Set(obj client.Object)

type UntypedDependencyBuilder

type UntypedDependencyBuilder[CustomResourceType client.Object, ContextType Context[CustomResourceType]] struct {
	// contains filtered or unexported fields
}

UntypedDependencyBuilder provides a fluent builder pattern for creating dependencies on resources that are not known at compile time or don't have Go type definitions.

This builder is useful when working with:

  • Custom Resource Definitions (CRDs) not included in your Go code
  • Third-party resources without Go client types
  • Dynamic resource types determined at runtime
  • Resources from different API groups or versions

Type parameters:

  • CustomResourceType: The custom resource that owns this dependency
  • ContextType: The context type containing the custom resource and additional data

The builder works with unstructured.Unstructured objects, which can represent any Kubernetes resource dynamically.

Example:

// Depend on a custom resource defined by a CRD
gvk := schema.GroupVersionKind{
	Group:   "example.com",
	Version: "v1",
	Kind:    "Database",
}
dep := NewUntypedDependencyBuilder(ctx, gvk).
	WithName("my-database").
	WithNamespace("default").
	WithIsReadyFunc(func(obj *unstructured.Unstructured) bool {
		// Check custom readiness condition
		status, found, _ := unstructured.NestedString(obj.Object, "status", "phase")
		return found && status == "Ready"
	}).
	Build()

func NewUntypedDependencyBuilder

func NewUntypedDependencyBuilder[CustomResourceType client.Object, ContextType Context[CustomResourceType]](ctx ContextType, gvk schema.GroupVersionKind) *UntypedDependencyBuilder[CustomResourceType, ContextType]

NewUntypedDependencyBuilder creates a new UntypedDependencyBuilder for constructing dependencies on Kubernetes resources that don't have compile-time Go types.

This is particularly useful for:

  • Custom Resource Definitions (CRDs) defined in YAML but not in Go
  • Third-party resources from operators you don't control
  • Resources from different API versions or groups
  • Dynamic resource types determined at runtime

Parameters:

  • ctx: The context containing the custom resource and additional data
  • gvk: GroupVersionKind specifying the exact resource type to depend on

The GroupVersionKind must exactly match the target resource's type information. The dependency will be resolved as an unstructured.Unstructured object.

Example:

// Depend on a Prometheus ServiceMonitor resource
gvk := schema.GroupVersionKind{
	Group:   "monitoring.coreos.com",
	Version: "v1",
	Kind:    "ServiceMonitor",
}
dep := NewUntypedDependencyBuilder(ctx, gvk).
	WithName("my-app-metrics").
	WithNamespace(ctx.GetCustomResource().Namespace).
	WithOptional(true). // Don't fail if Prometheus operator not installed
	Build()

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) Build

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) Build() *UntypedDependency[CustomResourceType, ContextType]

Build constructs and returns the final UntypedDependency instance with all configured options.

This method finalizes the builder pattern and creates an untyped dependency that can be used in reconciliation steps. The returned dependency contains all the configuration specified through the builder methods and will work with unstructured.Unstructured objects.

The dependency must be used with appropriate reconciliation steps (such as ResolveDynamicDependenciesStep) to actually perform the dependency resolution.

Returns a configured UntypedDependency instance ready for use in reconciliation.

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithAddManagedByAnnotation

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithAddManagedByAnnotation(add bool) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithAddManagedByAnnotation controls whether to add a "managed-by" annotation to the untyped dependency resource.

When enabled, this adds metadata to help identify which controller is managing or depending on this resource. This is especially useful for untyped dependencies since the relationship between controllers and third-party resources may not be obvious.

The annotation typically follows the format:

"app.kubernetes.io/managed-by": "<controller-name>"

Example:

.WithAddManagedByAnnotation(true) // Mark dependency relationship for debugging

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithAfterReconcile

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithAfterReconcile(f func(ctx ContextType, resource *unstructured.Unstructured) error) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithAfterReconcile registers a hook function to execute after successful dependency resolution.

This function is called with the resolved dependency as an unstructured.Unstructured object and can be used for post-processing, validation, or updating your custom resource's status. If the function returns an error, the reconciliation will fail.

Working with unstructured objects requires using helper functions to access nested fields:

Example:

.WithAfterReconcile(func(ctx MyContext, obj *unstructured.Unstructured) error {
	// Extract status from custom resource
	status, found, err := unstructured.NestedString(obj.Object, "status", "endpoint")
	if err != nil {
		return err
	}
	if found {
		ctx.Data.DatabaseEndpoint = status
	}
	return nil
})

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithBeforeReconcile

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithBeforeReconcile(f func(ctx ContextType) error) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithBeforeReconcile registers a hook function to execute before dependency resolution.

This function is called before attempting to resolve the untyped dependency and can be used for setup tasks, validation, or logging. If the function returns an error, dependency resolution will be aborted.

Common use cases:

  • Validating that the target CRD is installed
  • Setting up authentication for third-party resources
  • Logging dependency resolution attempts for debugging

Example:

.WithBeforeReconcile(func(ctx MyContext) error {
	logger := ctx.GetLogger()
	logger.Info("Resolving third-party resource dependency", "gvk", gvk)
	return nil
})

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithIsReadyFunc

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithIsReadyFunc(f func(obj *unstructured.Unstructured) bool) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithIsReadyFunc defines custom logic to determine if the untyped dependency is ready for use.

The provided function is called with the resolved dependency as an unstructured.Unstructured object and should return true if the dependency meets your readiness criteria.

Working with unstructured objects requires using helper functions to access nested fields. Common patterns include checking status conditions, phase fields, or custom markers.

Example:

.WithIsReadyFunc(func(obj *unstructured.Unstructured) bool {
	// Check if a custom resource has reached "Ready" state
	phase, found, _ := unstructured.NestedString(obj.Object, "status", "phase")
	if !found {
		return false
	}
	return phase == "Ready" || phase == "Running"
})

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithName

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithName(name string) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithName specifies the name of the Kubernetes resource to depend on.

This is the metadata.name field of the target resource. The name is required for dependency resolution and should match exactly with the existing resource.

Example:

.WithName("my-database-instance") // Look for resource named "my-database-instance"

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithNamespace

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithNamespace(namespace string) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithNamespace specifies the namespace where the dependency resource is located.

This is the metadata.namespace field of the target resource. If not specified, the dependency will be looked up in the same namespace as the custom resource.

For cluster-scoped resources, this field is ignored.

Example:

.WithNamespace("monitoring") // Look in monitoring namespace
.WithNamespace(ctx.GetCustomResource().Namespace) // Same namespace as custom resource

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithOptional

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithOptional(optional bool) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithOptional configures whether this dependency is required for reconciliation.

When set to true, the dependency resolution will continue even if this dependency is missing or not ready. When false (default), missing or unready dependencies will cause reconciliation to requeue and wait.

This is particularly useful for untyped dependencies on optional operators or CRDs:

  • Prometheus monitoring resources (when Prometheus operator is optional)
  • Service mesh resources (when Istio/Linkerd might not be installed)
  • Third-party integrations that enhance but don't break functionality

Example:

.WithOptional(true) // Don't fail if the CRD isn't installed

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithOutput

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithOutput(obj *unstructured.Unstructured) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithOutput specifies where to store the resolved untyped dependency resource.

The provided unstructured.Unstructured object will be populated with the dependency's data after successful resolution. This allows other parts of your reconciliation logic to access the dependency's current state.

The output object should be a field in your context's data structure to ensure it's accessible throughout the reconciliation process.

Example:

type MyContextData struct {
	DatabaseInstance *unstructured.Unstructured
}

dep := NewUntypedDependencyBuilder(ctx, gvk).
	WithName("my-db").
	WithOutput(ctx.Data.DatabaseInstance). // Store resolved resource here
	Build()

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithReadinessCondition

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithReadinessCondition(f func(obj *unstructured.Unstructured) bool) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithReadinessCondition is an alias for WithIsReadyFunc that defines custom readiness logic.

This method provides the same functionality as WithIsReadyFunc but with a more descriptive name for untyped dependencies. Use whichever method name feels more natural in your context.

See WithIsReadyFunc for detailed documentation and examples of working with unstructured.Unstructured objects.

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithUserIdentifier

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithUserIdentifier(identifier string) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithUserIdentifier assigns a custom identifier for this untyped dependency.

This identifier is used for logging, debugging, and distinguishing between multiple untyped dependencies. If not provided, a default identifier will be generated based on the GroupVersionKind and resource name.

This is especially useful for untyped dependencies since the resource types may not be immediately obvious from logs.

Example:

.WithUserIdentifier("prometheus-servicemonitor") // Clear name for logs and debugging

func (*UntypedDependencyBuilder[CustomResourceType, ContextType]) WithWaitForReady

func (b *UntypedDependencyBuilder[CustomResourceType, ContextType]) WithWaitForReady(waitForReady bool) *UntypedDependencyBuilder[CustomResourceType, ContextType]

WithWaitForReady determines whether reconciliation should wait for this dependency to become ready before proceeding.

When true (recommended), reconciliation will requeue if the dependency exists but is not yet ready according to the readiness function. When false, the dependency is only checked for existence.

This is particularly important for untyped dependencies on custom resources that may have complex initialization or external dependencies.

Example:

.WithWaitForReady(true) // Wait for custom resource to be fully initialized

type UntypedResource

type UntypedResource[CustomResource client.Object, ContextType Context[CustomResource]] struct {
	*Resource[CustomResource, ContextType, *unstructured.Unstructured]
	// contains filtered or unexported fields
}

func (*UntypedResource[CustomResource, ContextType]) Kind

func (c *UntypedResource[CustomResource, ContextType]) Kind() string

func (*UntypedResource[CustomResource, ContextType]) ObjectMetaGenerator

func (c *UntypedResource[CustomResource, ContextType]) ObjectMetaGenerator() (obj client.Object, skip bool, err error)

type UntypedResourceBuilder

type UntypedResourceBuilder[CustomResource client.Object, ContextType Context[CustomResource]] struct {
	// contains filtered or unexported fields
}

UntypedResourceBuilder provides a fluent builder pattern for creating managed resources that are not known at compile time or don't have Go type definitions.

This builder is useful when your custom resource needs to manage:

  • Custom Resource Definitions (CRDs) not included in your Go code
  • Third-party resources from other operators
  • Resources from different API groups or versions
  • Dynamic resource types determined at runtime

Type parameters:

  • CustomResource: The custom resource that owns and manages this untyped resource
  • ContextType: The context type containing the custom resource and additional data

The builder works with unstructured.Unstructured objects, which can represent any Kubernetes resource dynamically.

Unlike untyped dependencies (which are external resources you consume), untyped resources are resources that your controller creates and manages as part of implementing your custom resource's desired state.

Common use cases:

  • Managing CRDs defined in YAML but not in Go
  • Creating third-party operator resources (e.g., Prometheus ServiceMonitors)
  • Managing resources from different API versions
  • Implementing operators that work with dynamic resource types

Example:

// Create a Prometheus ServiceMonitor for your application
gvk := schema.GroupVersionKind{
	Group:   "monitoring.coreos.com",
	Version: "v1",
	Kind:    "ServiceMonitor",
}
serviceMonitor := NewUntypedResourceBuilder(ctx, gvk).
	WithKeyFunc(func() types.NamespacedName {
		return types.NamespacedName{
			Name:      ctx.GetCustomResource().Name + "-metrics",
			Namespace: ctx.GetCustomResource().Namespace,
		}
	}).
	WithMutator(func(obj *unstructured.Unstructured) error {
		// Configure ServiceMonitor using unstructured helpers
		return unstructured.SetNestedField(obj.Object, "app", "spec", "selector", "matchLabels", "app")
	}).
	WithReadinessCondition(func(obj *unstructured.Unstructured) bool {
		// Check if ServiceMonitor is being scraped
		status, found, _ := unstructured.NestedString(obj.Object, "status", "phase")
		return found && status == "Active"
	}).
	Build()

func NewUntypedResourceBuilder

func NewUntypedResourceBuilder[CustomResource client.Object, ContextType Context[CustomResource]](ctx ContextType, gvk schema.GroupVersionKind) *UntypedResourceBuilder[CustomResource, ContextType]

NewUntypedResourceBuilder creates a new UntypedResourceBuilder for constructing managed Kubernetes resources that don't have compile-time Go types.

This is particularly useful for:

  • Managing Custom Resource Definitions (CRDs) defined in YAML but not in Go
  • Creating third-party resources from operators you don't control
  • Working with resources from different API versions or groups
  • Implementing dynamic resource management based on configuration

Parameters:

  • ctx: The context containing the custom resource and additional data
  • gvk: GroupVersionKind specifying the exact resource type to manage

The GroupVersionKind must exactly match the target resource's type information. The resource will be managed as an unstructured.Unstructured object with owner references to your custom resource for proper garbage collection.

Key differences from NewUntypedDependencyBuilder:

  • Resources are CREATED and MANAGED by your controller
  • Dependencies are CONSUMED by your controller (external resources)
  • Resources have owner references to your custom resource
  • Resources are deleted when your custom resource is deleted

Example:

// Manage a Grafana Dashboard resource
gvk := schema.GroupVersionKind{
	Group:   "grafana.integreatly.org",
	Version: "v1beta1",
	Kind:    "GrafanaDashboard",
}
dashboard := NewUntypedResourceBuilder(ctx, gvk).
	WithKeyFunc(func() types.NamespacedName {
		return types.NamespacedName{
			Name:      ctx.GetCustomResource().Name + "-dashboard",
			Namespace: ctx.GetCustomResource().Namespace,
		}
	}).
	WithMutator(func(obj *unstructured.Unstructured) error {
		// Configure dashboard JSON content
		dashboardJSON := generateDashboard(ctx.GetCustomResource())
		return unstructured.SetNestedField(obj.Object, dashboardJSON, "spec", "json")
	}).
	Build()

func (*UntypedResourceBuilder[CustomResource, ContextType]) Build

func (b *UntypedResourceBuilder[CustomResource, ContextType]) Build() *UntypedResource[CustomResource, ContextType]

Build constructs and returns the final UntypedResource instance with all configured options.

This method finalizes the builder pattern and creates an untyped resource that can be used in reconciliation steps. The returned resource contains all the configuration specified through the builder methods and will work with unstructured.Unstructured objects.

The resource must be used with appropriate reconciliation steps (such as ReconcileResourcesStep) to actually perform the resource management operations.

Validation:

  • At least one of WithKey or WithKeyFunc must be called before Build()
  • WithMutator is typically required for meaningful resource management

Returns a configured UntypedResource instance ready for use in reconciliation.

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithAfterCreate

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithAfterCreate(f func(ctx ContextType, resource *unstructured.Unstructured) error) *UntypedResourceBuilder[CustomResource, ContextType]

WithAfterCreate registers a hook function that executes only when an untyped resource is newly created.

This function is called specifically when a resource is created for the first time, not when it's updated. It's useful for one-time initialization tasks specific to untyped resources, such as setting up external integrations or logging creation events.

The function receives the newly created unstructured resource, including any fields populated by Kubernetes during creation.

Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithAfterCreate(func(ctx MyContext, obj *unstructured.Unstructured) error {
	// Log creation of custom resource
	logger := ctx.GetLogger()
	name, _, _ := unstructured.NestedString(obj.Object, "metadata", "name")
	logger.Info("Custom resource created", "name", name, "gvk", gvk)

	// Register with external monitoring system
	return ctx.RegisterWithExternalSystem(obj)
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithAfterDelete

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithAfterDelete(f func(ctx ContextType, resource *unstructured.Unstructured) error) *UntypedResourceBuilder[CustomResource, ContextType]

WithAfterDelete registers a hook function that executes after an untyped resource is deleted.

This function is called when a resource has been successfully deleted from the cluster, either due to a delete condition being met or during custom resource finalization. It can be used for cleanup tasks specific to untyped resources.

The function receives the resource object as it existed just before deletion. Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithAfterDelete(func(ctx MyContext, obj *unstructured.Unstructured) error {
	// Clean up external references
	name, _, _ := unstructured.NestedString(obj.Object, "metadata", "name")
	logger := ctx.GetLogger()
	logger.Info("Cleaning up external resources", "resource", name)

	// Remove from external monitoring system
	return ctx.UnregisterFromExternalSystem(name)
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithAfterFinalize

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithAfterFinalize(f func(ctx ContextType, resource *unstructured.Unstructured) error) *UntypedResourceBuilder[CustomResource, ContextType]

WithAfterFinalize registers a hook function that executes during custom resource finalization for untyped resources that require special cleanup handling.

This function is called when the custom resource is being deleted and the untyped resource needs to be cleaned up as part of the finalization process. It's particularly important for untyped resources that may have external dependencies or special deletion requirements.

Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithAfterFinalize(func(ctx MyContext, obj *unstructured.Unstructured) error {
	// Graceful cleanup for third-party resources
	logger := ctx.GetLogger()

	// Check if resource has special cleanup requirements
	cleanupMode, found, _ := unstructured.NestedString(obj.Object, "spec", "cleanupMode")
	if found && cleanupMode == "preserve" {
		logger.Info("Preserving resource during finalization")
		return nil
	}

	// Perform graceful shutdown
	return ctx.GracefullyShutdownExternalResource(obj)
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithAfterReconcile

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithAfterReconcile(f func(ctx ContextType, resource *unstructured.Unstructured) error) *UntypedResourceBuilder[CustomResource, ContextType]

WithAfterReconcile registers a hook function to execute after successful untyped resource reconciliation.

This function is called after the untyped resource has been successfully created, updated, or verified. It receives the current resource state as an unstructured.Unstructured object and can be used for post-processing, status updates, or triggering additional operations.

Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithAfterReconcile(func(ctx MyContext, obj *unstructured.Unstructured) error {
	cr := ctx.GetCustomResource()

	// Extract status information from the untyped resource
	status, found, err := unstructured.NestedString(obj.Object, "status", "phase")
	if err != nil {
		return err
	}

	// Update custom resource status
	if found {
		cr.Status.ExternalResourcePhase = status
		meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{
			Type:   "ExternalResourceReady",
			Status: metav1.ConditionTrue,
			Reason: "ResourceReconciled",
		})
	}

	return ctx.PatchStatus()
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithAfterUpdate

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithAfterUpdate(f func(ctx ContextType, resource *unstructured.Unstructured) error) *UntypedResourceBuilder[CustomResource, ContextType]

WithAfterUpdate registers a hook function that executes only when an untyped resource is updated.

This function is called specifically when an existing untyped resource is modified, not when it's initially created. It's useful for tracking changes to third-party resources and responding to configuration updates.

Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithAfterUpdate(func(ctx MyContext, obj *unstructured.Unstructured) error {
	logger := ctx.GetLogger()

	// Log the update with resource details
	name, _, _ := unstructured.NestedString(obj.Object, "metadata", "name")
	generation, _, _ := unstructured.NestedInt64(obj.Object, "metadata", "generation")

	logger.Info("External resource updated",
		"name", name,
		"generation", generation,
		"gvk", gvk)

	// Trigger external system update notification
	return ctx.NotifyExternalSystemOfUpdate(obj)
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithBeforeReconcile

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithBeforeReconcile(f func(ctx ContextType) error) *UntypedResourceBuilder[CustomResource, ContextType]

WithBeforeReconcile registers a hook function to execute before untyped resource reconciliation.

This function is called before any resource operations (create, update, or delete) are performed on the untyped resource. It's particularly useful for validating that the target CRD is installed and for preparing the environment.

Common use cases for untyped resources:

  • Validating that the target CRD exists in the cluster
  • Checking operator availability (e.g., Prometheus, Grafana operators)
  • Setting up authentication for third-party APIs
  • Performing environment-specific preparations

Example:

.WithBeforeReconcile(func(ctx MyContext) error {
	logger := ctx.GetLogger()
	client := ctx.GetClient()

	// Verify that the target CRD exists
	crdName := fmt.Sprintf("%ss.%s", strings.ToLower(gvk.Kind), gvk.Group)
	crd := &apiextensionsv1.CustomResourceDefinition{}
	err := client.Get(ctx, types.NamespacedName{Name: crdName}, crd)
	if err != nil {
		return fmt.Errorf("required CRD %s not found: %w", crdName, err)
	}

	logger.Info("Proceeding with untyped resource reconciliation", "gvk", gvk)
	return nil
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithCanBePaused added in v1.1.0

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithCanBePaused(canBePaused bool) *UntypedResourceBuilder[CustomResource, ContextType]

WithCanBePaused specifies whether this resource supports pausing reconciliation.

When set to true, the resource will respect the paused state of the custom resource. If the custom resource is marked as paused (e.g., via a label), reconciliation for this resource will be skipped until the pause is lifted.

This is useful for scenarios where you want to temporarily halt changes to certain resources without deleting them or affecting other parts of the system.

Common use cases:

  • Temporarily halting updates during maintenance windows
  • Pausing non-critical resources while troubleshooting issues
  • Allowing manual intervention before resuming automated management

Example:

.WithCanBePaused(true) // Enable pausing for this resource

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithCanBePausedFunc added in v1.1.0

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithCanBePausedFunc(f func() bool) *UntypedResourceBuilder[CustomResource, ContextType]

WithCanBePausedFunc specifies a function to determine if this resource supports pausing reconciliation.

The provided function is called during reconciliation to check if the resource should respect the paused state of the custom resource. If it returns true, reconciliation for this resource will be skipped when the custom resource is paused.

This allows for dynamic control over which resources can be paused based on the current state or configuration of the custom resource.

Example:

.WithCanBePausedFunc(func() bool {
    // Custom logic to determine if the resource can be paused
    return someCondition
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithKey

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithKey(name types.NamespacedName) *UntypedResourceBuilder[CustomResource, ContextType]

WithKey specifies a static NamespacedName for the untyped resource.

This is useful when the resource name and namespace are known at build time and don't need to be computed dynamically based on the custom resource state.

For dynamic naming based on custom resource properties, use WithKeyFunc instead.

Example:

.WithKey(types.NamespacedName{
	Name:      "monitoring-config",
	Namespace: "monitoring",
}) // Static name for the monitoring resource

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithKeyFunc

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithKeyFunc(f func() types.NamespacedName) *UntypedResourceBuilder[CustomResource, ContextType]

WithKeyFunc specifies a function that dynamically determines the untyped resource's NamespacedName.

This function is called during reconciliation to determine where the resource should be created or found. For untyped resources, this is particularly important as the naming often needs to be coordinated with external operators or systems.

Example:

.WithKeyFunc(func() types.NamespacedName {
	cr := ctx.GetCustomResource()
	return types.NamespacedName{
		// Name follows third-party operator conventions
		Name:      fmt.Sprintf("%s-%s-monitor", cr.Name, cr.Spec.Component),
		Namespace: cr.Namespace,
	}
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithMutator

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithMutator(f Mutator[*unstructured.Unstructured]) *UntypedResourceBuilder[CustomResource, ContextType]

WithMutator specifies the function that configures the untyped resource's desired state.

The mutator function receives an unstructured.Unstructured object and should configure all necessary fields using the unstructured helper functions. This is where you define how your custom resource's spec translates into the target third-party resource configuration.

Working with unstructured objects requires using helper functions like:

  • unstructured.SetNestedField() to set individual values
  • unstructured.SetNestedSlice() to set arrays
  • unstructured.SetNestedMap() to set objects

The mutator should be idempotent and handle both CREATE and UPDATE operations.

Example:

.WithMutator(func(obj *unstructured.Unstructured) error {
	cr := ctx.GetCustomResource()

	// Set basic metadata
	obj.SetName(cr.Name + "-servicemonitor")
	obj.SetNamespace(cr.Namespace)

	// Configure ServiceMonitor spec using unstructured helpers
	err := unstructured.SetNestedMap(obj.Object, map[string]any{
		"app": cr.Name,
	}, "spec", "selector", "matchLabels")
	if err != nil {
		return err
	}

	// Set endpoint configuration
	endpoints := []any{
		map[string]any{
			"port": "metrics",
			"path": "/metrics",
		},
	}
	err = unstructured.SetNestedSlice(obj.Object, endpoints, "spec", "endpoints")
	if err != nil {
		return err
	}

	// Set owner reference for garbage collection
	return controllerutil.SetOwnerReference(cr, obj, ctx.GetScheme())
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithOutput

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithOutput(obj *unstructured.Unstructured) *UntypedResourceBuilder[CustomResource, ContextType]

WithOutput specifies where to store the reconciled untyped resource after successful operations.

The provided unstructured.Unstructured object will be populated with the resource's current state from the cluster after reconciliation completes. This is particularly useful for untyped resources where you need to extract status information or other runtime values generated by third-party operators.

Example:

type MyContextData struct {
	ServiceMonitor *unstructured.Unstructured
}

resource := NewUntypedResourceBuilder(ctx, gvk).
	// ... other configuration ...
	WithOutput(ctx.Data.ServiceMonitor). // Store reconciled resource here
	Build()

// Later, access the reconciled resource
if ctx.Data.ServiceMonitor != nil {
	status, found, _ := unstructured.NestedString(
		ctx.Data.ServiceMonitor.Object, "status", "phase")
}

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithReadinessCondition

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithReadinessCondition(f func(obj *unstructured.Unstructured) bool) *UntypedResourceBuilder[CustomResource, ContextType]

WithReadinessCondition defines custom logic to determine when the untyped resource is ready.

The provided function is called with the current unstructured resource state and should return true if the resource has reached the desired operational state. This is particularly important for untyped resources that may have complex initialization processes managed by third-party operators.

Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithReadinessCondition(func(obj *unstructured.Unstructured) bool {
	// Check if Prometheus ServiceMonitor is being scraped
	status, found, _ := unstructured.NestedString(obj.Object, "status", "conditions")
	if !found {
		return false
	}

	// More complex readiness check
	lastScrape, found, _ := unstructured.NestedString(obj.Object, "status", "lastScrapeTime")
	if !found {
		return false
	}

	// Consider ready if scraped within last 5 minutes
	scrapeTime, err := time.Parse(time.RFC3339, lastScrape)
	return err == nil && time.Since(scrapeTime) < 5*time.Minute
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithRequireManualDeletionForFinalize

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithRequireManualDeletionForFinalize(f func(obj *unstructured.Unstructured) bool) *UntypedResourceBuilder[CustomResource, ContextType]

WithRequireManualDeletionForFinalize specifies when an untyped resource requires manual cleanup during custom resource finalization.

This is particularly important for untyped resources managed by third-party operators, as they may have complex deletion procedures or external dependencies that need special handling.

Working with unstructured objects requires using helper functions to access nested fields.

Example:

.WithRequireManualDeletionForFinalize(func(obj *unstructured.Unstructured) bool {
	// Check if resource has special deletion requirements
	deletionPolicy, found, _ := unstructured.NestedString(
		obj.Object, "spec", "deletionPolicy")
	if found && deletionPolicy == "Retain" {
		return true // Requires manual cleanup
	}

	// Check for external dependencies
	externalDeps, found, _ := unstructured.NestedSlice(
		obj.Object, "status", "externalDependencies")
	return found && len(externalDeps) > 0
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithSkipAndDeleteOnCondition

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithSkipAndDeleteOnCondition(f func() bool) *UntypedResourceBuilder[CustomResource, ContextType]

WithSkipAndDeleteOnCondition specifies when to skip creating or delete an existing untyped resource.

This is particularly useful for untyped resources that depend on optional third-party operators or should only be created under certain conditions. The function is evaluated during each reconciliation cycle.

Common use cases for untyped resources:

  • Optional monitoring resources (when Prometheus operator might not be installed)
  • Feature flag controlled third-party integrations
  • Environment-specific operator resources
  • Conditional third-party service configurations

Example:

.WithSkipAndDeleteOnCondition(func() bool {
	cr := ctx.GetCustomResource()

	// Only create monitoring resources if monitoring is enabled
	if !cr.Spec.Monitoring.Enabled {
		return true
	}

	// Check if the required operator is available
	client := ctx.GetClient()
	prometheusOperator := &appsv1.Deployment{}
	err := client.Get(ctx, types.NamespacedName{
		Name: "prometheus-operator",
		Namespace: "monitoring",
	}, prometheusOperator)

	// Skip if operator not found or not ready
	return err != nil || prometheusOperator.Status.ReadyReplicas == 0
})

func (*UntypedResourceBuilder[CustomResource, ContextType]) WithUserIdentifier

func (b *UntypedResourceBuilder[CustomResource, ContextType]) WithUserIdentifier(identifier string) *UntypedResourceBuilder[CustomResource, ContextType]

WithUserIdentifier assigns a custom identifier for this untyped resource.

This identifier is used for logging, debugging, and distinguishing between multiple untyped resources. It's especially important for untyped resources since the resource types may not be immediately obvious from logs, and you might be managing multiple third-party resources of different types.

The identifier should be descriptive and include both the resource purpose and type.

Example:

.WithUserIdentifier("prometheus-servicemonitor") // Clear purpose and type
.WithUserIdentifier("grafana-dashboard-app")     // Identifies both operator and purpose
.WithUserIdentifier("istio-virtualservice")     // Service mesh resource identifier

type WatchCache

type WatchCache struct {
	ctrl.Manager
	// contains filtered or unexported fields
}

func NewWatchCache

func NewWatchCache(mgr ctrl.Manager) WatchCache

func (*WatchCache) AddWatchSource

func (w *WatchCache) AddWatchSource(key WatchCacheKey)

func (*WatchCache) GetController

func (*WatchCache) IsWatchingSource

func (w *WatchCache) IsWatchingSource(key WatchCacheKey) bool

func (*WatchCache) SetController

func (w *WatchCache) SetController(ctrler controller.TypedController[reconcile.Request])

type WatchCacheKey

type WatchCacheKey string

func NewWatchKey

func NewWatchKey(gvk schema.GroupVersionKind, watchType WatchCacheType) WatchCacheKey

type WatchCacheType

type WatchCacheType string
const (
	CacheTypeEnqueueForOwner WatchCacheType = "enqueueForOwner"
)

type Watcher

type Watcher interface {
	ctrl.Manager

	// AddWatchSource adds a watch source to the cache
	AddWatchSource(key WatchCacheKey)
	// IsWatchSource checks if the key is a watch source
	IsWatchingSource(key WatchCacheKey) bool
	// GetController returns the controller for the watch cache
	GetController() controller.TypedController[reconcile.Request]
}

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL