package cloudflare import ( "encoding/json" "fmt" "net/url" ) // zone represents a Cloudflare zone (minimal fields). type zone struct { ID string `json:"id"` Name string `json:"name"` } // GetZoneID resolves the Cloudflare zone ID for a domain. // It tries the exact domain first, then strips subdomains progressively. func (c *Client) GetZoneID(domain string) (string, error) { // Try exact domain first (e.g., "demo-felhom.eu") id, err := c.lookupZone(domain) if err != nil { return "", err } if id != "" { return id, nil } // Try parent domains (e.g., "felhom.eu" from "demo.felhom.eu") for i := 0; i < len(domain); i++ { if domain[i] == '.' { parent := domain[i+1:] if parent == "" { break } id, err = c.lookupZone(parent) if err != nil { return "", err } if id != "" { return id, nil } } } return "", fmt.Errorf("no Cloudflare zone found for domain %q", domain) } // lookupZone queries the CF API for a zone by name. func (c *Client) lookupZone(name string) (string, error) { path := "/zones?name=" + url.QueryEscape(name) + "&status=active" resp, err := c.do("GET", path, nil) if err != nil { return "", fmt.Errorf("lookup zone %q: %w", name, err) } var zones []zone if err := json.Unmarshal(resp.Result, &zones); err != nil { return "", fmt.Errorf("decode zones: %w", err) } if len(zones) == 0 { return "", nil } c.logger.Printf("[CF] Resolved zone %q → %s", name, zones[0].ID) return zones[0].ID, nil }