Skip to content

dns: add LookupTXT to Resolver interface#2101

Merged
edwardrf merged 2 commits intomainfrom
edw/dns-lookup-txt
May 7, 2026
Merged

dns: add LookupTXT to Resolver interface#2101
edwardrf merged 2 commits intomainfrom
edw/dns-lookup-txt

Conversation

@edwardrf
Copy link
Copy Markdown
Contributor

@edwardrf edwardrf commented May 7, 2026

Description

Adds LookupTXT(ctx, domain) ([]string, error) to dns.Resolver and implementations:

  • DirectResolver — issues a TypeTXT query, joins each record's wire-split strings into one entry per RR
  • RootResolver — delegates to its per-domain DirectResolver
  • MockResolver — reads from Records[{Type: \"TXT\"}]
  • Test stubs (cli.MockResolver, aws.CallbackMockResolver) gain matching LookupTXT methods to keep satisfying the interface

Why

Prereq for two follow-ups:

  1. defang-mvp ResolveTXT server handler — calls resolver.LookupTXT(ctx, req.Domain), mirroring the existing ResolveCNAME / ResolveNS server impls.
  2. Use fabric dns client for dns resolves #2069 (fabric resolver) — adds a fabric-backed Resolver impl; once this lands, that PR's FabricResolver gains a matching LookupTXT that calls the new ResolveTXT RPC (added in proto: add ResolveTXT RPC #2100).

The cert-validation flow on edw/azure-cert-gen already consumes these primitives (Azure managed-cert TXT polling), but its cert.go integration is intentionally not part of this PR — kept narrow so server-side and client-side teams can land in any order.

Linked issues

Builds on #2100 (proto: add ResolveTXT RPC).

Checklist

  • Self-review
  • Tests pass (go test -short ./pkg/dns/ ./pkg/cli/ ./pkg/cli/client/byoc/aws/)
  • No new tests — interface addition; behavior is exercised by integration tests on the consumers' branches

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • DNS resolver now supports TXT record lookups across resolver implementations, including CNAME-following behavior and proper TXT record assembly.
  • Tests

    • Test scaffolding and mock resolvers updated to support TXT record resolution, improving coverage for DNS- and certificate-related flows.

Mirrors LookupIPAddr / LookupCNAME / LookupNS:

- Resolver interface gains LookupTXT(ctx, domain) ([]string, error)
- DirectResolver.LookupTXT issues a TypeTXT query and joins each
  record's wire-split strings into one entry per RR
- RootResolver.LookupTXT delegates to its per-domain DirectResolver
- MockResolver.LookupTXT reads from Records[{Type:"TXT"}]
- Test mocks (cli.MockResolver, aws.CallbackMockResolver) add stubs
  to keep satisfying the interface

Prereq for the defang-mvp ResolveTXT server handler (which calls
LookupTXT on the resolver) and for the in-flight FabricResolver work
in #2069 (which adds a fabric-backed Resolver impl that will need a
LookupTXT method too).

Standalone from the cert-validation flow on edw/azure-cert-gen — that
branch's cert.go integration uses these primitives but isn't part of
this change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3fe97d7-0946-4583-8876-d14add791ad5

📥 Commits

Reviewing files that changed from the base of the PR and between 0f564f7 and 64c4688.

📒 Files selected for processing (1)
  • src/pkg/dns/resolver.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pkg/dns/resolver.go

📝 Walkthrough

Walkthrough

Adds DNS TXT lookup support: the Resolver interface gains LookupTXT; RootResolver and DirectResolver implement CNAME-following and TXT parsing; MockResolver provides TXT lookups; test mocks are updated to implement the method.

Changes

DNS TXT Record Lookup Support

Layer / File(s) Summary
Interface & Contract
src/pkg/dns/resolver.go
Adds LookupTXT(ctx context.Context, domain string) ([]string, error) to Resolver.
RootResolver CNAME-following
src/pkg/dns/resolver.go
RootResolver.LookupTXT delegates to underlying resolver and retries on ErrCNAMEFound up to a hop limit.
DirectResolver TXT Query
src/pkg/dns/resolver.go
DirectResolver.LookupTXT queries TXT RRs, concatenates fragments per RR into single strings, returns ErrCNAMEFound if only a CNAME is present, or ErrNoSuchHost when no TXT answers exist.
Mock Implementation
src/pkg/dns/mock.go
MockResolver.LookupTXT queries the Records map for type "TXT" and returns stored record strings and error.
Test Scaffolding
src/pkg/cli/cert_test.go, src/pkg/cli/client/byoc/aws/domain_test.go
Test mocks updated: cert test adds a counter-incrementing LookupTXT stub; domain test's CallbackMockResolver.LookupTXT invokes optional callback then delegates to MockResolver.LookupTXT.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RootResolver
  participant DirectResolver
  participant DNS
  Client->>RootResolver: LookupTXT(ctx, domain)
  RootResolver->>DirectResolver: LookupTXT(ctx, domain)
  DirectResolver->>DNS: Query TXT
  DNS-->>DirectResolver: TXT RRs or CNAME
  DirectResolver-->>RootResolver: TXT strings or ErrCNAMEFound/ErrNoSuchHost
  RootResolver-->>Client: TXT strings or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • jordanstephens
  • lionello

Poem

🐰 I hopped through names and CNAME vines,
Gathering TXT crumbs in tidy lines.
Mocked replies and real DNS songs,
Hops counted right where each string belongs.
Cheers — the lookup trail grows long!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately and clearly summarizes the main change: adding LookupTXT support to the Resolver interface.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 edw/dns-lookup-txt

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.1)

level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2."
level=warning msg="Suggested new configuration:\nlinters:\n enable:\n - gomodguard_v2\n"
level=warning msg="[linters_context] running gomodguard failed: unable to read module file go.mod: current working directory must have a go.mod file: if you are not using go modules it is suggested to disable this linter"
level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/pkg/cli/cert_test.go (1)

194-197: ⚡ Quick win

Prefer fail-fast behavior for unexpected TXT lookups in this test mock.

If LookupTXT is not expected in these tests, returning nil, nil can mask behavior drift. Panicking here makes misuse explicit.

Proposed change
 func (mr *MockResolver) LookupTXT(ctx context.Context, domain string) ([]string, error) {
 	mr.calls++
-	return nil, nil
+	panic("unexpected LookupTXT call")
 }
Based on learnings In Go test files (any _test.go under the Defang codebase), it's acceptable for mocks to panic to surface issues quickly during tests.
🤖 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 `@src/pkg/cli/cert_test.go` around lines 194 - 197, The mock method
MockResolver.LookupTXT should fail fast instead of quietly returning nil,nil;
modify LookupTXT (in the MockResolver type) to still increment mr.calls but
panic with a clear message (including the unexpected domain) so tests surface
unintended TXT lookups immediately rather than masking them.
🤖 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 `@src/pkg/dns/resolver.go`:
- Around line 187-189: The error return in the DNS query block ("if err != nil {
return nil, err }") should be wrapped with context so callers see the queried
name/type; replace the raw return with fmt.Errorf that includes the domain/name
and query type (e.g. fmt.Errorf("dns lookup %s %s: %w", name, qtype, err)) and
ensure fmt is imported; locate the error handling immediately after the DNS
query call in resolver.go and update it to wrap err using %w.

---

Nitpick comments:
In `@src/pkg/cli/cert_test.go`:
- Around line 194-197: The mock method MockResolver.LookupTXT should fail fast
instead of quietly returning nil,nil; modify LookupTXT (in the MockResolver
type) to still increment mr.calls but panic with a clear message (including the
unexpected domain) so tests surface unintended TXT lookups immediately rather
than masking them.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed5d48af-fd32-4949-a501-7a51a22a19d1

📥 Commits

Reviewing files that changed from the base of the PR and between 861b50c and 0f564f7.

📒 Files selected for processing (4)
  • src/pkg/cli/cert_test.go
  • src/pkg/cli/client/byoc/aws/domain_test.go
  • src/pkg/dns/mock.go
  • src/pkg/dns/resolver.go

Comment thread src/pkg/dns/resolver.go
Comment thread src/pkg/dns/resolver.go
A TXT query against a hostname that's actually a CNAME returns only
the CNAME RR — no TXT records — so RootResolver.LookupTXT must follow
the alias, matching how LookupIPAddr handles A/AAAA over CNAME.

DirectResolver.LookupTXT now captures any CNAME from the answer
section and returns ErrCNAMEFound when the result has no TXT records;
RootResolver.LookupTXT loops up to 10 times following each CNAME.

Without this, _dnsauth.<host> -> CNAME -> Azure-managed validation
record would silently 404, since the upstream wouldn't see the TXT
sitting at the CNAME target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@edwardrf
Copy link
Copy Markdown
Contributor Author

edwardrf commented May 7, 2026

@coderabbitai thanks. Applied the first one (CNAME-following on LookupTXT) — pushed 64c46889. RootResolver.LookupTXT now does the same 10-iteration loop as LookupIPAddr and DirectResolver.LookupTXT returns ErrCNAMEFound when the answer has only a CNAME and no TXT records. Real concern for _dnsauth.<host> → CNAME → Azure validation record cases.

Skipping the second one (wrap r.query errors with fmt.Errorf("%w")). The other three Lookup* methods (LookupIPAddr, LookupCNAME, LookupNS) all return the raw query error unwrapped today, so adding it just to LookupTXT would be inconsistent. If we want context wrapping it should be a separate sweep across all four.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@edwardrf, thanks for the update!

The CNAME-following implementation looks well-reasoned — _dnsauth.<host> → CNAME → Azure validation is exactly the kind of real-world indirection that would have silently broken without the loop + ErrCNAMEFound return.

The consistency argument for skipping error wrapping is solid. Wrapping only LookupTXT while the other three methods return raw errors would create an uneven API. Would you like me to open a follow-up issue to track the broader error-wrapping sweep across all four Lookup* methods (LookupIPAddr, LookupCNAME, LookupNS, LookupTXT) in src/pkg/dns/resolver.go?


✏️ Learnings added
Learnt from: edwardrf
URL: https://github.com/DefangLabs/defang/pull/2101

Timestamp: 2026-05-07T22:17:09.606Z
Learning: In `src/pkg/dns/resolver.go` (Go), the `Lookup*` methods on `DirectResolver` (`LookupIPAddr`, `LookupCNAME`, `LookupNS`, `LookupTXT`) all return raw, unwrapped errors from `r.query`. Do not suggest wrapping only one of them with `fmt.Errorf("%w")` — any error-context wrapping should be applied as a consistent sweep across all four methods.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@edwardrf edwardrf merged commit c8d86a1 into main May 7, 2026
13 of 14 checks passed
@edwardrf edwardrf deleted the edw/dns-lookup-txt branch May 7, 2026 22:23
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