Files
tiny-forge/plans/cloudflare-dns-management/phase-2-cloudflare-client.md
T
alexei.dolgolyov c730cfaa45 feat: Cloudflare DNS management with automatic record sync
Add flexible DNS management to Docker Watcher. By default, wildcard DNS
is assumed (current behavior). When disabled, users can configure a
Cloudflare DNS provider with API token and zone selection. DNS A records
are automatically created/updated/deleted in sync with proxy consumers
(deployed instances and standalone proxies).

- Settings: wildcard_dns toggle, dns_provider, cloudflare credentials
- Cloudflare client: Provider interface with EnsureRecord/DeleteRecord/ListRecords
- DNS lifecycle hooks in deployer and proxy manager (best-effort)
- Settings UI: DNS config section with provider picker, zone selector, test button
- DNS Records page at /dns with filtering, sync status, reconciliation
- Records visible in both wildcard and managed modes
- Cleanup on provider change: removes old records when switching modes
2026-04-02 14:49:21 +03:00

62 lines
2.6 KiB
Markdown

# Phase 2: Cloudflare DNS Client
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Create an `internal/dns` package with a `Provider` interface and a Cloudflare implementation
using the Cloudflare API v4 (direct HTTP, no SDK).
## Tasks
- [ ] Task 1: Define `Provider` interface in `internal/dns/provider.go`
- `EnsureRecord(ctx, fqdn, ip) error` — create or update A record
- `DeleteRecord(ctx, fqdn) error` — delete A record if exists
- `ListRecords(ctx) ([]Record, error)` — list all A records in the zone
- `Record` struct: ID, FQDN, Type, Content (IP), Proxied, TTL
- [ ] Task 2: Create `internal/dns/cloudflare.go` — Cloudflare implementation
- HTTP client with `Authorization: Bearer <token>` header
- Base URL: `https://api.cloudflare.com/client/v4`
- `EnsureRecord`: GET records by name, create if missing, update if IP differs
- `DeleteRecord`: GET record by name, DELETE if found
- `ListRecords`: GET all A records in zone
- `ListZones`: GET zones for the token (for zone picker)
- `TestConnection`: verify token works (GET /user/tokens/verify)
- [ ] Task 3: Create `internal/dns/dns.go` — factory function
- `NewProvider(providerName, config) (Provider, error)`
- Config struct with token, zoneID
- Returns `nil, nil` when providerName is empty (wildcard mode)
- [ ] Task 4: Wire DNS test/zones endpoints in `internal/api/settings.go`
- `POST /api/settings/dns/test` — create temp Cloudflare client, call TestConnection
- `GET /api/settings/dns/zones` — create temp client, call ListZones
## Files to Modify/Create
- `internal/dns/provider.go` — interface + Record type
- `internal/dns/cloudflare.go` — Cloudflare implementation
- `internal/dns/dns.go` — factory function
- `internal/api/settings.go` — wire test/zones endpoints to real client
## Acceptance Criteria
- Provider interface defined with EnsureRecord, DeleteRecord, ListRecords
- Cloudflare client makes correct API calls with proper auth headers
- EnsureRecord is idempotent (create if missing, update if changed, no-op if same)
- DeleteRecord is idempotent (no error if record doesn't exist)
- ListZones returns zone ID + name pairs
- TestConnection returns success/failure
## Notes
- Cloudflare API v4 docs: zones endpoint, dns_records endpoint
- Use `context.Context` for timeout control on all HTTP calls
- A records only (type "A"), TTL=1 (auto), proxied=false (DNS only, not CF proxy)
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->