From 3e6d93419808cfacc24e41244c60f1613954d87b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:01:30 +0000 Subject: [PATCH 1/2] Initial plan From 0c4d8fc651372a0573e1936631fb78f8e7394204 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:17:44 +0000 Subject: [PATCH 2/2] Add event recording to AgentReconciler - Add Recorder events.EventRecorder field to AgentReconciler struct - Emit Normal/Created and Normal/Updated events from ensureDeployment and ensureService - Emit Normal/Updated event on successful reconciliation - Emit Warning/ReconcileFailed events on resolution/reconciliation failures - Wire recorder in cmd/main.go via mgr.GetEventRecorder - Update all 7 test files to supply fake recorder in BeforeEach Co-authored-by: fmallmann <30110193+fmallmann@users.noreply.github.com> --- cmd/main.go | 5 ++-- internal/controller/agent_aigateway_test.go | 6 ++-- internal/controller/agent_metadata_test.go | 6 ++-- internal/controller/agent_reconciler.go | 30 ++++++++++++++++++-- internal/controller/agent_reconciler_test.go | 6 ++-- internal/controller/agent_resources_test.go | 6 ++-- internal/controller/agent_status_test.go | 6 ++-- internal/controller/agent_subagent_test.go | 6 ++-- internal/controller/agent_tool_test.go | 6 ++-- 9 files changed, 58 insertions(+), 19 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 2f8f617..ae705aa 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -210,8 +210,9 @@ func main() { } if err := (&controller.AgentReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorder("agent-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Agent") os.Exit(1) diff --git a/internal/controller/agent_aigateway_test.go b/internal/controller/agent_aigateway_test.go index acb2a67..8fa4b3f 100644 --- a/internal/controller/agent_aigateway_test.go +++ b/internal/controller/agent_aigateway_test.go @@ -24,6 +24,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" runtimev1alpha1 "github.com/agentic-layer/agent-runtime-operator/api/v1alpha1" @@ -45,8 +46,9 @@ var _ = Describe("Agent AiGateway Resolution", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } // Set POD_NAMESPACE for tests Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed()) diff --git a/internal/controller/agent_metadata_test.go b/internal/controller/agent_metadata_test.go index 84f6dfb..bb43531 100644 --- a/internal/controller/agent_metadata_test.go +++ b/internal/controller/agent_metadata_test.go @@ -26,6 +26,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -38,8 +39,9 @@ var _ = Describe("Agent Metadata", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed()) }) diff --git a/internal/controller/agent_reconciler.go b/internal/controller/agent_reconciler.go index 0ae7729..ab8091d 100644 --- a/internal/controller/agent_reconciler.go +++ b/internal/controller/agent_reconciler.go @@ -32,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/tools/events" "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -53,7 +54,8 @@ const ( // AgentReconciler reconciles a Agent object type AgentReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder events.EventRecorder } // +kubebuilder:rbac:groups=runtime.agentic-layer.ai,resources=agents,verbs=get;list;watch;create;update;patch;delete @@ -100,6 +102,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl resolvedSubAgents, err := r.resolveAllSubAgents(ctx, &agent) if err != nil { log.Error(err, "Failed to resolve subAgents") + r.emitEvent(&agent, corev1.EventTypeWarning, "ReconcileFailed", "ReconcileSubAgents", "Failed to resolve subAgents: %v", err) return ctrl.Result{}, err } @@ -107,6 +110,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl resolvedTools, err := r.resolveAllTools(ctx, &agent) if err != nil { log.Error(err, "Failed to resolve tools") + r.emitEvent(&agent, corev1.EventTypeWarning, "ReconcileFailed", "ResolveTools", "Failed to resolve tools: %v", err) return ctrl.Result{}, err } @@ -118,18 +122,21 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if statusErr := r.updateAgentStatusNotReady(ctx, &agent, "MissingAiGateway", err.Error()); statusErr != nil { log.Error(statusErr, "Failed to update status after AiGateway resolution failure") } + r.emitEvent(&agent, corev1.EventTypeWarning, "ReconcileFailed", "ResolveAiGateway", "Failed to resolve AiGateway: %v", err) return ctrl.Result{}, err } if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { return r.ensureDeployment(ctx, &agent, resolvedSubAgents, resolvedTools, aiGateway, runtimeConfig) }); err != nil { + r.emitEvent(&agent, corev1.EventTypeWarning, "ReconcileFailed", "EnsureDeployment", "Failed to reconcile Deployment: %v", err) return ctrl.Result{}, err } if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { return r.ensureService(ctx, &agent) }); err != nil { + r.emitEvent(&agent, corev1.EventTypeWarning, "ReconcileFailed", "EnsureService", "Failed to reconcile Service: %v", err) return ctrl.Result{}, err } @@ -139,6 +146,8 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return ctrl.Result{}, err } + r.emitEvent(&agent, corev1.EventTypeNormal, "Updated", "Reconciled", "Agent reconciled successfully") + log.V(1).Info("Reconciled Agent") return ctrl.Result{}, nil @@ -267,8 +276,12 @@ func (r *AgentReconciler) ensureDeployment(ctx context.Context, agent *runtimev1 return ctrl.SetControllerReference(agent, deployment, r.Scheme) }); err != nil { return err - } else if op != controllerutil.OperationResultNone { + } else if op == controllerutil.OperationResultCreated { + log.Info("Deployment reconciled", "operation", op, "obj", *deployment) + r.emitEvent(agent, corev1.EventTypeNormal, "Created", "CreateDeployment", "Deployment %s created", deployment.Name) + } else if op == controllerutil.OperationResultUpdated { log.Info("Deployment reconciled", "operation", op, "obj", *deployment) + r.emitEvent(agent, corev1.EventTypeNormal, "Updated", "UpdateDeployment", "Deployment %s updated", deployment.Name) } else { log.V(1).Info("Deployment up to date") } @@ -341,8 +354,12 @@ func (r *AgentReconciler) ensureService(ctx context.Context, agent *runtimev1alp return ctrl.SetControllerReference(agent, service, r.Scheme) }); err != nil { return err - } else if op != controllerutil.OperationResultNone { + } else if op == controllerutil.OperationResultCreated { + log.Info("Service reconciled", "operation", op, "obj", *service) + r.emitEvent(agent, corev1.EventTypeNormal, "Created", "CreateService", "Service %s created", service.Name) + } else if op == controllerutil.OperationResultUpdated { log.Info("Service reconciled", "operation", op, "obj", *service) + r.emitEvent(agent, corev1.EventTypeNormal, "Updated", "UpdateService", "Service %s updated", service.Name) } else { log.V(1).Info("Service up to date") } @@ -438,6 +455,13 @@ func getOrDefaultResourceRequirements(agent *runtimev1alpha1.Agent) corev1.Resou } } +// emitEvent records a Kubernetes event on the given object if a Recorder is configured. +func (r *AgentReconciler) emitEvent(regarding runtime.Object, eventtype, reason, action, note string, args ...interface{}) { + if r.Recorder != nil { + r.Recorder.Eventf(regarding, nil, eventtype, reason, action, note, args...) + } +} + // getAgentRuntimeConfiguration retrieves the agent runtime configuration from the operator's namespace. // It looks for any AgentRuntimeConfiguration resource in the same namespace as the controller. // Returns nil if no configuration is found (will use built-in defaults). diff --git a/internal/controller/agent_reconciler_test.go b/internal/controller/agent_reconciler_test.go index f9a506e..c404030 100644 --- a/internal/controller/agent_reconciler_test.go +++ b/internal/controller/agent_reconciler_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/reconcile" runtimev1alpha1 "github.com/agentic-layer/agent-runtime-operator/api/v1alpha1" @@ -38,8 +39,9 @@ var _ = Describe("Agent Controller", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } // Set POD_NAMESPACE for tests to use default namespace Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed()) diff --git a/internal/controller/agent_resources_test.go b/internal/controller/agent_resources_test.go index 5885cde..47d280b 100644 --- a/internal/controller/agent_resources_test.go +++ b/internal/controller/agent_resources_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -39,8 +40,9 @@ var _ = Describe("Agent Resources", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } // Set POD_NAMESPACE for tests Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed()) diff --git a/internal/controller/agent_status_test.go b/internal/controller/agent_status_test.go index 6aa9849..015cd0c 100644 --- a/internal/controller/agent_status_test.go +++ b/internal/controller/agent_status_test.go @@ -24,6 +24,7 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" runtimev1alpha1 "github.com/agentic-layer/agent-runtime-operator/api/v1alpha1" @@ -35,8 +36,9 @@ var _ = Describe("Agent Status", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } // Set POD_NAMESPACE for tests Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed()) diff --git a/internal/controller/agent_subagent_test.go b/internal/controller/agent_subagent_test.go index a65de48..97b2e89 100644 --- a/internal/controller/agent_subagent_test.go +++ b/internal/controller/agent_subagent_test.go @@ -24,6 +24,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" runtimev1alpha1 "github.com/agentic-layer/agent-runtime-operator/api/v1alpha1" @@ -35,8 +36,9 @@ var _ = Describe("Agent SubAgent", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } // Set POD_NAMESPACE for tests Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed()) diff --git a/internal/controller/agent_tool_test.go b/internal/controller/agent_tool_test.go index 8ee5378..29d20d2 100644 --- a/internal/controller/agent_tool_test.go +++ b/internal/controller/agent_tool_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" runtimev1alpha1 "github.com/agentic-layer/agent-runtime-operator/api/v1alpha1" @@ -148,8 +149,9 @@ var _ = Describe("Agent Tool", func() { BeforeEach(func() { reconciler = &AgentReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, } // Set POD_NAMESPACE for tests Expect(os.Setenv("POD_NAMESPACE", "default")).To(Succeed())