Reconciler Logic

by grafana1ccacf29049fApache-2.0279 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated today

Implement reconcilers and watchers for grafana-app-sdk apps — write `TypedReconciler[*MyKind]` reconcile functions, apply generation-based skip patterns, do conflict-safe status updates via `resource.UpdateObject`, configure `BasicReconcileOptions` (namespace, label/field filters, finalizer management), use `Watcher` for event-style handling, reconcile `UnmanagedKinds` (resources your app doesn't own), and register the whole thing in `app.go`. Use when writing a reconciler, implementing the reconcile loop, adding async business logic, handling create/update/delete events, processing resource state changes, scheduling periodic resyncs with `RequeueAfter`, picking between Watcher and Reconciler, or wiring a controller into `app.go` — even when the user says "process this resource", "handle X events", or "write a controller" without saying "reconciler".

Instructions onlySoftware Development
AI-generated overview

Guides implementing reconcilers and watchers for grafana-app-sdk apps, including typed reconcile functions, status updates and app.go registration.

What it does
This skill provides instructions for writing the asynchronous business-logic layer of a grafana-app-sdk application. It covers TypedReconciler reconcile functions, generation-based skip patterns, conflict-safe status updates via resource.UpdateObject, BasicReconcileOptions such as namespace and label or field filters, finalizer management, Watcher event handling, UnmanagedKinds, and registration in app.go. It produces Go reconciler code and wiring guidance rather than runnable scripts.
When to use it
Use it when writing a reconciler or controller, implementing a reconcile loop, adding async business logic, handling create, update or delete events, scheduling periodic resyncs with RequeueAfter, choosing between a Watcher and a Reconciler, or wiring a controller into app.go.
Requirements
Requires the grafana-app-sdk and a Go toolchain; the skill itself ships no scripts, only reference documents. It references the grafana-app-sdk GitHub repository and operator package documentation.

Reconciler Logic

Reconcilers are the async business-logic layer of a grafana-app-sdk app. The SDK enqueues a reconcile event when a resource is created, updated, or deleted; the reconciler observes the current state and drives the system toward the desired state.

Common Workflows

Implementing a new reconciler end-to-end

bash
# 1. Generate operator stubs for a standalone appgrafana-app-sdk project component add operator
# 2. Implement the ReconcileFunc — see § TypedReconciler below for the pattern
# 3. Register the reconciler in app.go (see references/registration.md)
# 4. Generate, build, and verify it runsgrafana-app-sdk generatego build ./...go run ./cmd/operator   # tail logs — reconcile entries should appear when you kubectl-apply a resource

If the operator starts but no reconcile events fire when you create a resource:

  • Check BasicReconcileOptions.Namespace matches the resource's namespace
  • Check BasicReconcileOptions.LabelFilters / FieldSelectors — most "no events" issues are filter mismatches (kubectl get <resource> -o yaml to see labels)
  • Confirm the reconciler was attached to the right (latest) version of the kind

TypedReconciler — preferred pattern

operator.TypedReconciler handles type assertion and provides a strongly-typed ReconcileFunc:

go
type MyKindReconciler struct {    operator.TypedReconciler[*v1alpha1.MyKind]    client resource.Client}
func NewMyKindReconciler(client resource.Client) *MyKindReconciler {    r := &MyKindReconciler{client: client}    r.ReconcileFunc = r.reconcile  // wire the typed func    return r}
func (r *MyKindReconciler) reconcile(    ctx context.Context,    req operator.TypedReconcileRequest[*v1alpha1.MyKind],) (operator.ReconcileResult, error) {    obj := req.Object
    // Skip if already reconciled this generation    if obj.GetGeneration() == obj.Status.LastObservedGeneration &&       req.Action != operator.ReconcileActionDeleted {        return operator.ReconcileResult{}, nil    }
    log := logging.FromContext(ctx).With("name", obj.GetName(), "namespace", obj.GetNamespace())    log.Info("reconciling", "action", operator.ResourceActionFromReconcileAction(req.Action))
    if req.Action == operator.ReconcileActionDeleted {        return operator.ReconcileResult{}, nil    }
    // ... business logic ...
    // Atomic status update — see § Status updates below    _, err := resource.UpdateObject(ctx, r.client, obj.GetStaticMetadata().Identifier(),        func(obj *v1alpha1.MyKind, _ bool) (*v1alpha1.MyKind, error) {            obj.Status.LastObservedGeneration = obj.GetGeneration()            obj.Status.State = "Ready"            return obj, nil        },        resource.UpdateOptions{Subresource: "status"},    )    return operator.ReconcileResult{}, err}

ReconcileAction values: ReconcileActionCreated, ReconcileActionUpdated, ReconcileActionDeleted, ReconcileActionResynced.

To requeue after a delay (e.g. polling an external system):

go
return operator.ReconcileResult{RequeueAfter: 10 * time.Second}, nil

Status updates with resource.UpdateObject

Always use resource.UpdateObject for status writes — it fetches the latest version before applying your update function, avoiding 409 Conflict errors when multiple reconcile events race:

go
_, err := resource.UpdateObject(ctx, r.client, identifier,    func(obj *v1alpha1.MyKind, exists bool) (*v1alpha1.MyKind, error) {        obj.Status.LastObservedGeneration = obj.GetGeneration()        obj.Status.State = "Ready"        obj.Status.Message = ""        return obj, nil    },    resource.UpdateOptions{Subresource: "status"},)

Do not use client.Update for status — it sends the full object and races with spec changes made by users.

Generation-based skip

Check LastObservedGeneration at the top of the reconcile function to avoid re-processing unchanged resources:

go
if obj.GetGeneration() == obj.Status.LastObservedGeneration {    return operator.ReconcileResult{}, nil}

ReconcileOptions

Control informer behavior via BasicReconcileOptions on the AppManagedKind entry:

go
{    Kind:       mykindv1alpha1.MyKindKind(),    Reconciler: reconciler,    ReconcileOptions: simple.BasicReconcileOptions{        Namespace:      "my-namespace",          // watch one namespace; default is all        LabelFilters:   []string{"env=prod"},    // only reconcile matching resources        FieldSelectors: []string{"status.phase=Running"},        UsePlain:       false,                   // false = wrap in OpinionatedReconciler (default; manages finalizers)    },},

UsePlain: false (the default) wraps your reconciler in OpinionatedReconciler, which manages finalizers automatically so the SDK can guarantee clean deletion.

References

  • references/watchers.md [blocked] — Watcher alternative (event-style Add/Update/Delete callbacks) + decision matrix for watcher vs reconciler
  • references/unmanaged-kinds.md [blocked] — UnmanagedKinds for reconciling resources your app doesn't own, with UseOpinionated: false guidance and common failure modes
  • references/registration.md [blocked] — full app.go wiring (client setup, multi-version registration, ValidateManifest) + common failure modes

External resources

Source and attribution

Source:grafana/skillsinskills/grafana-app-sdk/reconciler-logicat commit1ccacf2

License: Apache-2.0

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from grafana/skills

React 19 Plugin Migration

grafana

Guides migration of a Grafana plugin to React 19 compatibility through ordered build, dependency and source-code steps.

Software Development279updated today

Plugin Bundle Size

grafana

Guides optimisation of Grafana app plugin bundle size using React.lazy, Suspense and webpack code splitting.

Software Development279updated today

Grafana Scenes

grafana

Builds Grafana plugin pages with the @grafana/scenes framework, covering scenes, panels, variables and drilldowns.

Software Development279updated today

Check Npm

grafana

Read-only audit of npm, yarn, or pnpm configuration for supply-chain hardening in a JS/TS repository.

Security279updated today

Mimir

grafana

Guides standing up and operating Grafana Mimir for scalable, multi-tenant, long-term Prometheus and OTLP metrics storage.

DevOps & Cloud279updated today

K6 Trend Analysis

grafana

Analyze Grafana Cloud k6 test run trends over time. Detects slow metric drift (e.g., P95 latency creeping up while still passing thresholds), computes headroom to thresholds, flags anomalies, and recommends threshold tightening. Use when the user asks about test performance trends, wants to know if metrics are degrading, asks whether thresholds should be tightened, or wants a health check across recent runs for a specific test. Trigger on phrases like "how is my test trending", "is P95 getting worse", "check for performance regression", "should I tighten thresholds", "are my tests degrading", "show me trends for test X", "analyze my k6 test runs", or "is my test getting slower". Also trigger when a user asks to check all tests in a project -- run this skill once per test and synthesize.

Awaiting classification279updated today