Skip to content

fix(tunnel): distinguish transient Get errors from "no tunnel-targeted parent"#158

Open
kochj23 wants to merge 1 commit into
jacaudi:mainfrom
kochj23:fix/146-distinguish-transient-get-errors
Open

fix(tunnel): distinguish transient Get errors from "no tunnel-targeted parent"#158
kochj23 wants to merge 1 commit into
jacaudi:mainfrom
kochj23:fix/146-distinguish-transient-get-errors

Conversation

@kochj23

@kochj23 kochj23 commented Jul 1, 2026

Copy link
Copy Markdown

Summary

findTunnelTargetedParentRef (internal/controller/tunnel/attach.go) continued past a candidate parent on any Get failure — a single continue handled both apierrors.IsNotFound (parent definitively absent) and transient errors (apiserver glitch, cache resync hole, timeout). When every candidate parent fails to Get, the function returns (nil parent, nil err), which the reconcile reads as "no tunnel-targeted parent."

Before #145 this was harmless. After #145 added deactivation pruning at the HTTPRoute/TLSRoute parent == nil branch, a transient Get error on the parent Gateway (or its CloudflareTunnel) can spuriously delete every CloudflareDNSRecord CR the source ever emitted, triggering a delete-and-reemit churn that briefly tears down the Cloudflare-side DNS + TXT records (issue #146).

Fix

Branch on the error type at the two Get sites in findTunnelTargetedParentRef (the Gateway Get and the CloudflareTunnel Get):

  • apierrors.IsNotFound(err)continue (parent definitively doesn't exist).
  • any other error → return it, so the caller can requeue.

The HTTPRoute and TLSRoute reconcile paths already surface a non-nil error from findTunnelTargetedParent as return reconcile.Result{}, err, so returning the transient error here is sufficient to convert it into a requeue instead of falling into the deactivation prune. No change to the reconcile paths was required.

Root cause (from the issue)

Per the issue's field-evidence note: an operator reports a CR briefly disappearing without a deliberate annotation change; logs at the time would show the orphan-prune failed during deactivation sweep message absent (the prune call succeeded) followed by re-emission a moment later. The risk is observability churn (events, briefly-missing Cloudflare-side records), not data loss — but this fix removes the spurious-prune path entirely for transient errors.

Tests

Extended TestFindTunnelTargetedParentRef (fake-client unit tests, sigs.k8s.io/controller-runtime/pkg/client/fake + interceptor). Category coverage:

  1. Happy path — tunnel-targeted Gateway resolves all fields (existing).
  2. Negative / no-match — plain Gateway without the tunnel annotation; tunnel-annotated Gateway with a missing CloudflareTunnel CR (existing).
  3. Error handling — transient (non-NotFound) Get error on the Gateway propagates; same for the CloudflareTunnel Get (new, interceptor.Funcs).
  4. Edge / boundary — an injected synthetic NotFound on the Gateway still skips (returns all-nil, no error), proving the branch keys on apierrors.IsNotFound (new).
  5. Concurrency — N/A (placeholder comment): findTunnelTargetedParentRef is a pure per-call read with no shared mutable state; controller-runtime serializes reconciles per object via the workqueue.
  6. Security — N/A (placeholder comment): this path performs no auth/secret/tenant-boundary handling.
  7. Regression — the two "transient ... Get error propagates" subtests are the direct guard for Distinguish transient Get errors from 'no tunnel-targeted parent' in findTunnelTargetedParentRef #146.

go build ./..., go vet ./..., and go test ./... all pass locally (Go 1.26). The test/envtest integration suite passed in this environment; maintainer CI should of course re-run it.

Closes #146

🤖 Generated with Claude Code

…d parent"

findTunnelTargetedParentRef `continue`d past a candidate parent on ANY
Get failure — folding both a definitively-missing parent (NotFound) and
a transient apiserver glitch (cache resync hole, timeout) into the same
all-nil "no tunnel-targeted parent" result.

After jacaudi#145 added deactivation pruning at the HTTPRoute/TLSRoute
`parent == nil` branch, that conflation became harmful: a transient Get
error on the parent Gateway (or its CloudflareTunnel) could spuriously
delete every CloudflareDNSRecord CR the source ever emitted, briefly
tearing down the Cloudflare-side DNS + TXT records before re-emission.

Branch on the error type at the two Get sites in
findTunnelTargetedParentRef:
  - apierrors.IsNotFound(err) -> continue (parent definitively absent).
  - any other error           -> return it, so the reconcile requeues.

The HTTPRoute/TLSRoute reconcile paths already surface a non-nil error
from findTunnelTargetedParent as `return reconcile.Result{}, err`, so
returning the error here is sufficient to convert a transient failure
into a requeue instead of a spurious deactivation prune.

Closes jacaudi#146

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Distinguish transient Get errors from 'no tunnel-targeted parent' in findTunnelTargetedParentRef

1 participant