> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Emit Events From Reconcile

> Explains adding controller-scoped Kubernetes events using an EventRecorder and controllerutil.CreateOrUpdate during reconciliation to record Deployment create and update actions

The WebApp custom resource already reports a numeric `status` value that reflects what the controller observes now. Events answer a related but distinct question: what did the controller just do? In this lesson we keep the existing status behavior and add Kubernetes events whenever the controller creates or updates the child `Deployment`.

Events are valuable because they appear in places Kubernetes users already inspect when debugging — for example, in `kubectl describe` output. If a WebApp was accepted by the API server but the controller changed something during reconciliation, an event explains the recent action without requiring users to tail controller logs.

Below we walk through the minimal changes required to emit informative, controller-scoped events from the reconciler.

<Callout icon="lightbulb" color="#1CB2FE">
  The manager owns the event broadcaster. Reconciler instances should accept an `EventRecorder` from the manager so events show which controller reported the action (don't create your own recorder directly).
</Callout>

## Overview — what you will change

* Add the `record` package to imports and add a `Recorder` field to the reconciler struct.
* Obtain a named `EventRecorder` from the manager in `cmd/main.go` and pass it to the reconciler.
* Replace the manual get-or-create `Deployment` flow with `controllerutil.CreateOrUpdate`, using the returned operation to emit events describing created/updated/unchanged state.

## Imports and Reconciler fields

Consolidate imports and include the Client-Go `record` package and controller util helpers. The reconciler accepts an `EventRecorder` so events are linked to the controller that reported them.

```go theme={null}
package controller

import (
    "context"

    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    apierrors "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/client-go/tools/record"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    logf "sigs.k8s.io/controller-runtime/pkg/log"
    "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

    webappv1 "github.com/kodekloud/webapp-operator/api/v1"
)

type WebAppReconciler struct {
    client.Client
    Scheme   *runtime.Scheme
    Recorder record.EventRecorder
}

// +kubebuilder:rbac:groups=webapp.kodekloud.com,resources=webapps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.kodekloud.com,resources=webapps/status,verbs=get;update;patch
```

## Wiring the recorder in cmd/main.go

Obtain an `EventRecorder` from the manager and pass it into the reconciler when you register it. This makes events annotated with the controller name and ensures they’re visible in standard debugging interfaces.

```go theme={null}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme: scheme,
    // ...
})
if err != nil {
    // handle err
}

recorder := mgr.GetEventRecorderFor("webapp-controller")

if err = (&controller.WebAppReconciler{
    Client:   mgr.GetClient(),
    Scheme:   mgr.GetScheme(),
    Recorder: recorder,
}).SetupWithManager(mgr); err != nil {
    // handle err
}
```

## Reconcile: use controllerutil.CreateOrUpdate and emit events

Use `controllerutil.CreateOrUpdate` to simplify the get-or-create logic for the Deployment. The function reads the live object into your pointer (if present), calls a mutate function to set desired state, and persists any changes. The returned `op` indicates whether the object was created, updated, or unchanged, which is ideal for emitting human-friendly events.

A concise, clarified `Reconcile` implementation:

```go theme={null}
func (r *WebAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := logf.FromContext(ctx)

    var webapp webappv1.WebApp
    if err := r.Get(ctx, req.NamespacedName, &webapp); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Build an empty Deployment object with just name + namespace.
    dep := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      webapp.Name,
            Namespace: webapp.Namespace,
        },
    }

    // Create or update the Deployment. The mutate function sets desired fields.
    op, err := controllerutil.CreateOrUpdate(ctx, r.Client, dep, func() error {
        // Desired deployment derived from the WebApp spec helper.
        desired := deploymentFor(&webapp)

        // Copy fields the controller owns.
        if dep.Labels == nil {
            dep.Labels = make(map[string]string)
        }
        for k, v := range desired.Labels {
            dep.Labels[k] = v
        }

        dep.Spec.Selector = desired.Spec.Selector
        dep.Spec.Replicas = desired.Spec.Replicas

        // If the Deployment has not been created yet, copy the full pod template.
        if dep.CreationTimestamp.IsZero() {
            dep.Spec.Template = desired.Spec.Template.DeepCopy()
        } else {
            // For existing deployments, only update fields we own on the pod template
            // (for example, labels or container image if owned). This keeps changes minimal.
            dep.Spec.Template.Labels = desired.Spec.Template.Labels
            dep.Spec.Template.Spec.Containers = desired.Spec.Template.Spec.Containers
        }

        // Ensure ownership is set so garbage collection works and events attach to the WebApp.
        if err := controllerutil.SetControllerReference(&webapp, dep, r.Scheme); err != nil {
            return err
        }
        return nil
    })
    if err != nil {
        return ctrl.Result{}, err
    }

    // Record an event describing what happened to the deployment.
    switch op {
    case controllerutil.OperationResultCreated:
        r.Recorder.Event(&webapp, corev1.EventTypeNormal, "Created", "Created Deployment for WebApp")
        log.Info("Deployment created", "webapp", webapp.Name)
    case controllerutil.OperationResultUpdated:
        r.Recorder.Event(&webapp, corev1.EventTypeNormal, "Updated", "Updated Deployment for WebApp")
        log.Info("Deployment updated", "webapp", webapp.Name)
    case controllerutil.OperationResultNone:
        // No change; optional event or skip.
        log.V(1).Info("Deployment already up-to-date", "webapp", webapp.Name)
    }

    // Service management (left as create-if-missing as before).
    svc := serviceFor(&webapp)
    if err := controllerutil.SetControllerReference(&webapp, svc, r.Scheme); err != nil {
        return ctrl.Result{}, err
    }
    if err := r.Create(ctx, svc); err != nil && !apierrors.IsAlreadyExists(err) {
        return ctrl.Result{}, err
    }

    // Optionally update status with current information from the Deployment.
    // Example: copy ReadyReplicas into status if desired.
    // (Status update logic omitted here for brevity; ensure you patch the status subresource.)

    return ctrl.Result{}, nil
}
```

### Key points about the mutate function

* controller-runtime performs the read (populate `dep` from the API server) and the write (persist changes) around your mutate function.
* The mutate function expresses which fields the operator owns and must maintain. In this example, labels, selector, and replicas are owned and synchronized from the WebApp spec.
* Use `CreationTimestamp.IsZero()` to detect initial creation; it’s safe to copy the entire pod template only on first create. On updates, prefer changing only owned fields to avoid overwriting user-managed configuration.

## Operation result → Event mapping

| Operation result         | Event type       | Message                                 |
| ------------------------ | ---------------- | --------------------------------------- |
| `OperationResultCreated` | `Normal`         | "Created Deployment for WebApp"         |
| `OperationResultUpdated` | `Normal`         | "Updated Deployment for WebApp"         |
| `OperationResultNone`    | (none / verbose) | No event, controller can log at V-level |

These events appear in `kubectl describe webapp <name>` and similar tooling, helping users understand recent controller actions without reading logs.

## Why events matter here

* `CreateOrUpdate` returns an `op` that precisely indicates whether the Deployment was created, updated, or unchanged. Emitting events based on `op` provides concise, human-readable context in the resource history.
* Events surface in standard Kubernetes debugging workflows, giving operators immediate insight into controller activity (for example, when a WebApp triggers a Deployment update).

## References

* [kubectl describe — Documentation](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#describe)
* controller-runtime: `controllerutil.CreateOrUpdate` and `SetControllerReference` methods
* client-go: `tools/record.EventRecorder` for emitting events

This demo continues from this point: the reconciler carefully updates only the fields it owns on the `Deployment` while leaving other fields stable, and emits events to make those actions visible to cluster users.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-operators/module/715ddc8b-3997-4878-8900-2f710183ee13/lesson/7bc676fd-b40a-4ab9-aeb1-a2cda22fd5bc" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-operators/module/715ddc8b-3997-4878-8900-2f710183ee13/lesson/7bb75dc4-045a-4a2c-a776-11137edeeb69" />
</CardGroup>
