Skip to content

Commit 773dad7

Browse files
coderabbitai[bot]CodeRabbit
andauthored
fix: apply CodeRabbit auto-fixes
Fixed 8 file(s) based on 15 unresolved review comments. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
1 parent 70e585a commit 773dad7

8 files changed

Lines changed: 301 additions & 119 deletions

File tree

docs/agentrax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Phases 2, 3, and 5 are independent of each other and can run in parallel once Ph
4848

4949
The canary controller (`internal/rollout.Controller`) is a pure helper driven by the `AgentDeploymentReconciler` each reconcile cycle. When `spec.rollout.strategy: Canary` and `spec.image` differs from `status.stableVersion`, the reconciler transitions to `RolloutInProgress` and calls `Step()` on every subsequent reconcile.
5050

51-
**State machine**: `setWeight` steps create the canary Deployment and upsert a Gateway API `HTTPRoute` with the target weight split. `pause` steps query Prometheus for request count (sample gate), error rate, and p99 latency via `internal/metrics.Client`. If Prometheus is unreachable for longer than `--fail-safe-timeout` (default 60s), a fail-safe rollback fires. Sample-size gating extends the pause window up to `3×pause_duration` before forcing evaluation. Promotion updates the stable Deployment image and restores the HPA; rollback reverts everything and sets `phase=RolloutFailed`.
51+
**State machine**: `setWeight` steps create the canary Deployment and upsert a Gateway API `HTTPRoute` with the target weight split. `pause` steps query Prometheus for request count (sample gate), error rate, and p99 latency via `internal/metrics.Client`. If Prometheus is unreachable for longer than a fixed 60-second timeout, a fail-safe rollback fires. Sample-size gating extends the pause window up to the lesser of `3×pause_duration` or an absolute 15-minute maximum before forcing evaluation. Promotion updates the stable Deployment image and restores the HPA; rollback reverts everything and sets `phase=RolloutFailed`.
5252

5353
**New operator flags**: `--prometheus-url` (required for Canary), `--gateway-name`, `--gateway-namespace`.
5454

go.mod

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/gitcommitankit/agentrax
22

3-
go 1.22.0
3+
go 1.25.0
44

55
require (
66
github.com/go-logr/logr v1.4.2
@@ -72,14 +72,14 @@ require (
7272
go.uber.org/multierr v1.11.0 // indirect
7373
go.uber.org/zap v1.26.0 // indirect
7474
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
75-
golang.org/x/net v0.26.0 // indirect
76-
golang.org/x/oauth2 v0.21.0 // indirect
77-
golang.org/x/sync v0.7.0 // indirect
78-
golang.org/x/sys v0.21.0 // indirect
79-
golang.org/x/term v0.21.0 // indirect
80-
golang.org/x/text v0.16.0 // indirect
75+
golang.org/x/net v0.56.0 // indirect
76+
golang.org/x/oauth2 v0.27.0 // indirect
77+
golang.org/x/sync v0.21.0 // indirect
78+
golang.org/x/sys v0.46.0 // indirect
79+
golang.org/x/term v0.44.0 // indirect
80+
golang.org/x/text v0.38.0 // indirect
8181
golang.org/x/time v0.5.0 // indirect
82-
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
82+
golang.org/x/tools v0.45.0 // indirect
8383
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
8484
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
8585
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect

go.sum

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,34 +157,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
157157
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
158158
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
159159
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
160-
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
161-
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
162-
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
163-
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
160+
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
161+
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
162+
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
163+
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
164164
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
165165
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
166166
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
167-
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
168-
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
167+
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
168+
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
169169
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
170170
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
171171
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
172-
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
173-
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
174-
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
175-
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
172+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
173+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
174+
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
175+
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
176176
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
177177
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
178-
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
179-
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
178+
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
179+
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
180180
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
181181
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
182182
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
183183
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
184184
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
185185
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
186-
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
187-
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
186+
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
187+
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
188188
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
189189
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
190190
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

internal/controller/agentdeployment_controller.go

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -161,22 +161,9 @@ func (r *AgentDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ
161161
return ctrl.Result{}, nil
162162
}
163163

164-
// 3. Reconcile child Deployment.
165-
if err := r.reconcileDeployment(ctx, ad); err != nil {
166-
return ctrl.Result{}, fmt.Errorf("reconciling deployment: %w", err)
167-
}
168-
169-
// 4. Reconcile child Service.
170-
if err := r.reconcileService(ctx, ad); err != nil {
171-
return ctrl.Result{}, fmt.Errorf("reconciling service: %w", err)
172-
}
173-
174-
// 5. Reconcile ServiceMonitor when Prometheus Operator is present.
175-
if err := r.reconcileServiceMonitor(ctx, ad); err != nil {
176-
return ctrl.Result{}, fmt.Errorf("reconciling servicemonitor: %w", err)
177-
}
178-
179-
// 5.5. Handle canary rollout lifecycle.
164+
// 3. Handle canary rollout lifecycle before reconciling stable Deployment.
165+
// During rollout, the stable Deployment image must remain at status.stableVersion,
166+
// not spec.image. Canary handling determines which image to use for stable resources.
180167
if r.CanaryController != nil {
181168
// Manual abort: spec.rollout.abort=true triggers immediate rollback.
182169
if isAbortRequested(ad) {
@@ -203,15 +190,30 @@ func (r *AgentDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ
203190
}
204191
}
205192

206-
// 6. Reconcile the managed HPA (skip during active canary — Phase 4 owns it).
193+
// 4. Reconcile child Deployment.
194+
if err := r.reconcileDeployment(ctx, ad); err != nil {
195+
return ctrl.Result{}, fmt.Errorf("reconciling deployment: %w", err)
196+
}
197+
198+
// 5. Reconcile child Service.
199+
if err := r.reconcileService(ctx, ad); err != nil {
200+
return ctrl.Result{}, fmt.Errorf("reconciling service: %w", err)
201+
}
202+
203+
// 6. Reconcile ServiceMonitor when Prometheus Operator is present.
204+
if err := r.reconcileServiceMonitor(ctx, ad); err != nil {
205+
return ctrl.Result{}, fmt.Errorf("reconciling servicemonitor: %w", err)
206+
}
207+
208+
// 7. Reconcile the managed HPA (skip during active canary — Phase 4 owns it).
207209
// reconcileHPA also returns the quota evaluation state so updateStatus can
208210
// write the correct QuotaLimited condition onto the freshly re-fetched object.
209211
hpaResult, qs, err := r.reconcileHPA(ctx, ad)
210212
if err != nil {
211213
return ctrl.Result{}, fmt.Errorf("reconciling hpa: %w", err)
212214
}
213215

214-
// 7. Derive status from the live Deployment and update it — always last.
216+
// 8. Derive status from the live Deployment and update it — always last.
215217
// We continue into updateStatus even when hpaResult requests a requeue so
216218
// that the QuotaLimited condition is written in the same reconcile cycle.
217219
// Return the shorter of the two requeue intervals.
@@ -592,15 +594,19 @@ func (r *AgentDeploymentReconciler) detectImagePullFailure(ctx context.Context,
592594
// ── Desired-state builders ────────────────────────────────────────────────────
593595

594596
// agentLabels returns the canonical label set applied to all resources owned by ad.
597+
// For stable resources (Deployment, Service), this includes variant=stable.
595598
func agentLabels(ad *agentraxv1alpha1.AgentDeployment) map[string]string {
596599
return map[string]string{
597600
"app.kubernetes.io/name": ad.Name,
598601
"app.kubernetes.io/managed-by": "agentrax",
599602
"agentrax.io/tenant": ad.Spec.TenantRef,
603+
"agentrax.io/variant": "stable",
600604
}
601605
}
602606

603607
// desiredDeployment builds the Deployment spec the reconciler wants to exist.
608+
// During a canary rollout (PhaseRolloutInProgress), the stable Deployment image
609+
// is frozen at status.stableVersion; otherwise it tracks spec.image.
604610
func (r *AgentDeploymentReconciler) desiredDeployment(ad *agentraxv1alpha1.AgentDeployment) *appsv1.Deployment {
605611
port := ad.Spec.Port
606612
if port == 0 {
@@ -610,6 +616,12 @@ func (r *AgentDeploymentReconciler) desiredDeployment(ad *agentraxv1alpha1.Agent
610616
labels := agentLabels(ad)
611617
replicas := ad.Spec.Replicas.Min
612618

619+
// During rollout, freeze the stable Deployment at status.stableVersion.
620+
image := ad.Spec.Image
621+
if ad.Status.Phase == agentraxv1alpha1.PhaseRolloutInProgress && ad.Status.StableVersion != "" {
622+
image = ad.Status.StableVersion
623+
}
624+
613625
return &appsv1.Deployment{
614626
ObjectMeta: metav1.ObjectMeta{
615627
Name: ad.Name,
@@ -631,7 +643,7 @@ func (r *AgentDeploymentReconciler) desiredDeployment(ad *agentraxv1alpha1.Agent
631643
// Use a fixed container name that is always DNS-1035 compliant.
632644
// The AgentDeployment name is used at the object level, not container level.
633645
Name: "agent",
634-
Image: ad.Spec.Image,
646+
Image: image,
635647
Ports: []corev1.ContainerPort{
636648
{
637649
Name: "agent",
@@ -699,12 +711,13 @@ func (r *AgentDeploymentReconciler) desiredServiceMonitor(ad *agentraxv1alpha1.A
699711
Selector: metav1.LabelSelector{
700712
MatchLabels: labels,
701713
},
702-
// Carry the two labels used by the HPA ExternalMetric selector into
703-
// every scraped sample. Prometheus will sanitize the dots to underscores
704-
// during ingestion (app.kubernetes.io/nameapp_kubernetes_io_name).
714+
// Carry labels into every scraped sample. Prometheus will sanitize the dots
715+
// to underscores during ingestion (app.kubernetes.io/name → app_kubernetes_io_name,
716+
// agentrax.io/variantagentrax_io_variant).
705717
TargetLabels: []string{
706718
"app.kubernetes.io/name",
707719
"app.kubernetes.io/managed-by",
720+
"agentrax.io/variant",
708721
},
709722
Endpoints: []monitoringv1.Endpoint{
710723
{

0 commit comments

Comments
 (0)