Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_aigateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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())
})
Expand Down
30 changes: 27 additions & 3 deletions internal/controller/agent_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -100,13 +102,15 @@ 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
}

// Resolve Tools from ToolServer references early - fail fast if any cannot be resolved
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
}

Expand All @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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).
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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())
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_subagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/agent_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
Expand Down
Loading