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

2.6 KiB

Phase 2: Cloudflare DNS Client

Status: Not Started Parent plan: 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