1
0
Fork 0
mirror of https://github.com/imjasonh/client-go2 synced 2026-07-07 00:33:49 +00:00
client-go2/controller/README.md
Jason Hall 6c7409049e
Fix controller example to implement correct interface method (#6)
* Fix controller example to implement correct interface method

- Change ReconcileKind to Reconcile to match the Reconciler interface
- Add nil check for secretLister to handle initialization timing
- Fix OnError handler in e2e test to not fail immediately on expected errors

This allows the controller-owned-lister example to compile and run correctly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update examples/controller-owned-lister/main.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-28 19:57:12 +00:00

165 lines
5 KiB
Markdown

# Controller Package
The `controller` package provides a generic Kubernetes controller framework inspired by Knative's controller implementation. It simplifies building Kubernetes controllers with automatic update detection, conflict resolution, and owner reference support.
## Features
- **Type-safe generic controllers** - Build controllers for any Kubernetes resource type
- **Simple reconciler interface** - Just implement one method: `Reconcile` or pass a function.
- **Automatic update detection** - The framework detects and persists changes to your objects
- **Built-in conflict resolution** - Automatic retry with exponential backoff
- **Owner reference support** - Automatically reconcile owners when owned resources change
- **Error handling patterns** - Control reconciliation behavior with special error types
- **Context-aware logging** - Integrated with clog for structured logging
## Quick Start
```go
import (
"context"
"github.com/imjasonh/client-go2/controller"
"github.com/imjasonh/client-go2/generic"
corev1 "k8s.io/api/core/v1"
)
// Create a reconciler function
reconciler := controller.ReconcilerFunc[*corev1.ConfigMap](
func(ctx context.Context, cm *corev1.ConfigMap) error {
// Your reconciliation logic here
cm.Annotations["processed"] = "true"
return nil
})
// Create and run the controller
client, _ := generic.NewClient[*corev1.ConfigMap](config)
ctrl := controller.New(client, reconciler, nil)
ctrl.Run(ctx)
```
## Reconciler Interface
The core interface has just one method:
```go
type Reconciler[T runtime.Object] interface {
Reconcile(ctx context.Context, obj T) error
}
```
You can implement this interface directly or use `ReconcilerFunc` for simple cases.
## Automatic Updates
The controller automatically detects and persists changes made during reconciliation:
- **Metadata changes** - Updates to annotations, labels, and finalizers
- **Status updates** - Changes to the status subresource
- **Spec changes** - Logged as warnings (specs should not be modified in reconcilers)
Example:
```go
func (r *MyReconciler) Reconcile(ctx context.Context, pod *corev1.Pod) error {
// These changes will be automatically persisted
pod.Labels["processed"] = "true"
pod.Status.Phase = corev1.PodRunning
// Add a finalizer
pod.Finalizers = append(pod.Finalizers, "example.com/finalizer")
return nil
}
```
## Error Handling
Control reconciliation behavior by returning special errors:
```go
// Requeue after a specific duration
return controller.RequeueAfter(5 * time.Minute)
// Requeue immediately with rate limiting
return controller.RequeueImmediately()
// Don't retry this error
return controller.PermanentError(fmt.Errorf("unrecoverable error"))
// Normal errors are retried with exponential backoff
return fmt.Errorf("temporary error")
```
## Controller Options
Configure the controller behavior:
```go
opts := &controller.Options[*corev1.Pod]{
// Watch only a specific namespace
Namespace: "my-namespace",
// Number of concurrent workers
Concurrency: 5,
// Custom deep copy function (default uses JSON)
DeepCopyFunc: func(pod *corev1.Pod) *corev1.Pod {
return pod.DeepCopy()
},
// Watch owned resources
OwnedTypes: []controller.OwnedType{
{
Client: secretClient,
OwnerGVK: schema.GroupVersionKind{Version: "v1", Kind: "Pod"},
IsController: true,
},
},
}
ctrl := controller.New(client, reconciler, opts)
```
## Owner References
The controller can watch owned resources and reconcile owners when owned resources change:
```go
// In your reconciler, set owner references
func (r *MyReconciler) Reconcile(ctx context.Context, pod *corev1.Pod) error {
// Create a owned secret
secret := &corev1.Secret{...}
// Set owner reference
controller.SetOwnerReference(secret, pod, scheme.Scheme, true)
// Create the secret...
return nil
}
// Configure the controller to watch secrets
opts := &controller.Options[*corev1.Pod]{
OwnedTypes: []controller.OwnedType{
{
Client: secretClient,
OwnerGVK: schema.GroupVersionKind{Version: "v1", Kind: "Pod"},
IsController: true, // Only watch controller references
},
},
}
```
## Complete Example
See [examples/controller/main.go](../examples/controller/main.go) for a complete example that validates ConfigMaps.
## Best Practices
1. **Don't modify spec** - Reconcilers should only update metadata and status
2. **Use finalizers carefully** - Always check DeletionTimestamp before adding
3. **Handle conflicts** - The framework handles most conflicts, but design for idempotency
4. **Log with context** - Use clog for structured, context-aware logging
5. **Return appropriate errors** - Use RequeueAfter for known delays, PermanentError for unrecoverable failures
## Testing
The package includes comprehensive unit and e2e tests. See `controller_test.go` and `e2e_test.go` for examples of testing your controllers.