package cloudflare import ( "context" "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(ctx context.Context, domain string) (string, error) { if c.debug { c.logger.Printf("[CF-DEBUG] GetZoneID: looking up zone for domain %q", domain) } // Try exact domain first (e.g., "demo-felhom.eu") if c.debug { c.logger.Printf("[CF-DEBUG] GetZoneID: trying exact domain %q", domain) } id, err := c.lookupZone(ctx, 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 } if c.debug { c.logger.Printf("[CF-DEBUG] GetZoneID: trying parent domain %q", parent) } id, err = c.lookupZone(ctx, parent) if err != nil { return "", err } if id != "" { return id, nil } } } c.logger.Printf("[WARN] [cloudflare] No zone found for domain %s", domain) 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(ctx context.Context, name string) (string, error) { path := "/zones?name=" + url.QueryEscape(name) + "&status=active" resp, err := c.do(ctx, "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 }