Skip to content

fix(operator): reconcile dependents after deletion#483

Merged
flemzord merged 1 commit into
mainfrom
codex/reconcile-dependents-after-deletion
Jul 2, 2026
Merged

fix(operator): reconcile dependents after deletion#483
flemzord merged 1 commit into
mainfrom
codex/reconcile-dependents-after-deletion

Conversation

@flemzord

@flemzord flemzord commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • persist the stack label on dependent resources during reconciliation
  • resolve dependency watch stack context from spec, labels, annotations, or Stack owner references
  • ignore stack dependencies/modules that are already marked for deletion when reconciling active status
  • add Gateway controller regression coverage for deleted HTTPService removal

Root cause

The failing main CI run timed out in the 10-gatewayhttpapi-sync scenario on Kubernetes 1.32 after deleting chainsaw-httpapi-payments. The Service was gone, but Gateway.status.syncHTTPAPIs still listed ledger payments, which means the Gateway was not reconciled with the deleted HTTP API removed.

Delete events can arrive without reliable dependent spec data, so WatchDependents could miss the stack name and skip enqueueing the Gateway reconcile. Also, an object that is already marked for deletion can still appear in list results until finalizers complete, so active Gateway and Stack status must not keep it in generated routes/status.

Validation

  • just pre-commit
  • KUBEBUILDER_ASSETS="$(setup-envtest use 1.32.0 -p path)" go test ./internal/tests -run TestAPIs -ginkgo.focus 'StackController|GatewayController' -count=1\n- just tests\n- ./bin/chainsaw test tests/e2e/chainsaw/10-gatewayhttpapi-sync --config tests/e2e/chainsaw/.chainsaw.yaml --report-path /tmp/operator-ci-fix-132-artifacts on Kind kindest/node:v1.32.5\n

@flemzord flemzord requested a review from a team as a code owner July 2, 2026 08:24
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a stack-label enforcement step to dependent reconciliation via a new ensureStackLabel helper, generalizes WatchDependents to derive stack names from labels, annotations, or owner references instead of requiring the Dependent interface, and extends gateway controller tests for service deletion propagation.

Changes

Stack Label and Dependency Watch Resolution

Layer / File(s) Summary
Ensure stack label patching on dependents
internal/core/controllers.go
ForStackDependency calls new ensureStackLabel helper to patch dependent objects so their StackLabel matches the target stack name, returning early on empty name or matching label.
Stack name resolution for watched objects
internal/core/watch.go
WatchDependents now derives stackName via new stackNameFromObject helper, checking the Dependent interface, labels, annotations, and owner references, short-circuiting when unresolved.
Gateway controller deletion propagation tests
internal/tests/gateway_controller_test.go
Cleanup uses client.IgnoreNotFound for deletion, and a new test verifies Status.SyncHTTPAPIs, Service removal, and Caddyfile updates after deleting a service.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Watcher as WatchDependents
  participant Resolver as stackNameFromObject
  participant StackStore as GetAllStackDependencies

  Watcher->>Resolver: stackNameFromObject(object)
  Resolver->>Resolver: check Dependent interface
  Resolver->>Resolver: check labels[StackLabel]
  Resolver->>Resolver: check annotations[StackLabel]
  Resolver->>Resolver: check owner references
  Resolver-->>Watcher: stackName or ""
  alt stackName is empty
    Watcher-->>Watcher: return no requests
  else stackName resolved
    Watcher->>StackStore: GetAllStackDependencies(stackName)
    StackStore-->>Watcher: dependency requests
  end
Loading

Poem

A rabbit hops through labels and stacks,
Patching names on dependent tracks,
From owner refs to annotations sly,
No stack left unnamed, no watch gone dry,
Hop hop hooray, the tests all pass! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing dependent reconciliation after deletion.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly matches the code changes: stack label persistence, stack context resolution in watchers, deletion handling, and gateway regression coverage.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/reconcile-dependents-after-deletion

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@NumaryBot

NumaryBot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

✅ Approve — automated review

The PR adds stack-label enforcement on dependents via a new ensureStackLabel helper, generalizes WatchDependents to resolve stack names from labels, annotations, or owner references, and extends gateway controller tests for service deletion propagation. All prior automated and independent reviews found no correctness or behavioral issues, the one previously raised major finding was resolved in the inline thread, and no new credible issues have been surfaced. The changes are clean and ready to merge.

No findings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
internal/tests/gateway_controller_test.go (1)

269-293: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing .To(Succeed()) makes LoadResource assertions no-ops.

At Lines 271 and 283, g.Expect(LoadResource("", gateway.Name, gateway)) is never chained with a matcher (e.g., .To(Succeed())), so Gomega never actually asserts the load succeeded — any LoadResource error is silently swallowed and gateway may hold stale/zero-value data on the next line. This pattern already exists elsewhere in the file (e.g. Lines 91, 258) but is copied into this new regression test, which is specifically meant to catch missed-reconciliation bugs like the one described in the PR. A silent failure here could mask exactly the type of issue this test aims to prevent.

🐛 Proposed fix
 			It("Should remove deleted HTTPService from the gateway", func() {
 				Eventually(func(g Gomega) []string {
-					g.Expect(LoadResource("", gateway.Name, gateway))
+					g.Expect(LoadResource("", gateway.Name, gateway)).To(Succeed())
 
 					return gateway.Status.SyncHTTPAPIs
 				}).Should(ContainElements(httpAPI.Spec.Name, anotherHttpService.Spec.Name))
 
 				Expect(Delete(anotherHttpService)).To(Succeed())
 
 				Eventually(func() error {
 					return LoadResource(stack.Name, anotherHttpService.Spec.Name, &corev1.Service{})
 				}).Should(BeNotFound())
 
 				Eventually(func(g Gomega) []string {
-					g.Expect(LoadResource("", gateway.Name, gateway))
+					g.Expect(LoadResource("", gateway.Name, gateway)).To(Succeed())
 
 					return gateway.Status.SyncHTTPAPIs
 				}).Should(ConsistOf(httpAPI.Spec.Name))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tests/gateway_controller_test.go` around lines 269 - 293, The
gateway test is missing explicit success assertions on LoadResource, so failures
can be silently ignored and stale gateway state can be used. Update the two
LoadResource checks inside the relevant Eventually blocks in the gateway
controller test to chain an assertion like .To(Succeed()) via g.Expect, matching
the pattern used elsewhere in this file. Use the gateway.Status.SyncHTTPAPIs and
ConfigMap Caddyfile checks as the surrounding references when locating the
regression.
internal/core/watch.go (1)

38-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated label/annotation lookup and hardcoded "any" sentinel.

The labels and annotations blocks repeat identical logic, and the "any" exclusion string is duplicated. Consider factoring into a small helper and a named constant to avoid drift if this sentinel value changes elsewhere (e.g. SkipLabel).

♻️ Possible consolidation
+func lookupStackName(m map[string]string) string {
+	if v := m[v1beta1.StackLabel]; v != "" && v != "any" {
+		return v
+	}
+	return ""
+}
+
 func stackNameFromObject(object client.Object) string {
 	if dependent, ok := object.(v1beta1.Dependent); ok && dependent.GetStack() != "" {
 		return dependent.GetStack()
 	}
 
-	if labels := object.GetLabels(); labels != nil {
-		if stackName := labels[v1beta1.StackLabel]; stackName != "" && stackName != "any" {
-			return stackName
-		}
-	}
-
-	if annotations := object.GetAnnotations(); annotations != nil {
-		if stackName := annotations[v1beta1.StackLabel]; stackName != "" && stackName != "any" {
-			return stackName
-		}
-	}
+	if name := lookupStackName(object.GetLabels()); name != "" {
+		return name
+	}
+	if name := lookupStackName(object.GetAnnotations()); name != "" {
+		return name
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/core/watch.go` around lines 38 - 62, The stack name lookup in
stackNameFromObject repeats the same label/annotation filtering logic and
hardcodes the "any" sentinel in two places. Refactor the duplicated blocks into
a small helper used by stackNameFromObject, and replace the literal with a named
constant (for example SkipLabel) so the special value is defined once and stays
consistent across labels and annotations.
internal/core/controllers.go (1)

106-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No unit test coverage for the new ensureStackLabel helper.

This helper is the core fix for the reported reconciliation bug (labels must persist so stackNameFromObject in watch.go can resolve the stack on delete). A table-driven test covering: no-op when label already matches, patch when missing, patch when mismatched, and no-op when GetStack() is empty, would directly validate the root-cause fix rather than relying solely on the Chainsaw e2e scenario.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/core/controllers.go` around lines 106 - 125, Add unit tests for the
new ensureStackLabel helper in controllers.go. Cover the main behaviors: return
nil when GetStack() is empty, return nil when the existing StackLabel already
matches the stack name, and call ctx.GetClient().Patch when the label is missing
or mismatched. Use a table-driven test around ensureStackLabel with a fake
v1beta1.Dependent object so the label mutation and patch path are verified
directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/core/watch.go`:
- Around line 38-62: The Service creation path is missing stack identity
metadata, so `stackNameFromObject` in `watch.go` cannot map `corev1.Service`
events back to their stack. Update `internal/resources/services/services.go` so
the Service object carries the `v1beta1.StackLabel` and/or matching annotation
and includes a `Stack` owner reference in addition to the existing controller
reference. Use the same stack identity used by the dependent owner so
`WatchDependents` can resolve created and deleted Services correctly.

---

Nitpick comments:
In `@internal/core/controllers.go`:
- Around line 106-125: Add unit tests for the new ensureStackLabel helper in
controllers.go. Cover the main behaviors: return nil when GetStack() is empty,
return nil when the existing StackLabel already matches the stack name, and call
ctx.GetClient().Patch when the label is missing or mismatched. Use a
table-driven test around ensureStackLabel with a fake v1beta1.Dependent object
so the label mutation and patch path are verified directly.

In `@internal/core/watch.go`:
- Around line 38-62: The stack name lookup in stackNameFromObject repeats the
same label/annotation filtering logic and hardcodes the "any" sentinel in two
places. Refactor the duplicated blocks into a small helper used by
stackNameFromObject, and replace the literal with a named constant (for example
SkipLabel) so the special value is defined once and stays consistent across
labels and annotations.

In `@internal/tests/gateway_controller_test.go`:
- Around line 269-293: The gateway test is missing explicit success assertions
on LoadResource, so failures can be silently ignored and stale gateway state can
be used. Update the two LoadResource checks inside the relevant Eventually
blocks in the gateway controller test to chain an assertion like .To(Succeed())
via g.Expect, matching the pattern used elsewhere in this file. Use the
gateway.Status.SyncHTTPAPIs and ConfigMap Caddyfile checks as the surrounding
references when locating the regression.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6de35cc9-ede2-4f2c-bf71-428c7a0396b2

📥 Commits

Reviewing files that changed from the base of the PR and between 419e394 and 4f05eb6.

📒 Files selected for processing (3)
  • internal/core/controllers.go
  • internal/core/watch.go
  • internal/tests/gateway_controller_test.go

Comment thread internal/core/watch.go
@flemzord flemzord force-pushed the codex/reconcile-dependents-after-deletion branch 2 times, most recently from 9676190 to 10462f5 Compare July 2, 2026 08:42
@flemzord flemzord force-pushed the codex/reconcile-dependents-after-deletion branch from 10462f5 to e96a375 Compare July 2, 2026 08:47
@flemzord flemzord merged commit c45c8de into main Jul 2, 2026
12 checks passed
@flemzord flemzord deleted the codex/reconcile-dependents-after-deletion branch July 2, 2026 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants