Update cloudflare_service.go

This commit is contained in:
Lizandro Guarnizo
2026-05-12 10:02:28 -05:00
parent 307829f6f0
commit f1d73ae63b
+148 -43
View File
@@ -79,8 +79,9 @@ func (c *CloudflareClient) get(path string, dest interface{}) error {
// ─── Tipos de error ──────────────────────────────────────────────────────────
type CFError struct {
Code int `json:"code"`
Message string `json:"message"`
Code int `json:"code"`
Message string `json:"message"`
DocumentationURL string `json:"documentation_url,omitempty"`
}
// ─── Tipos de respuesta ──────────────────────────────────────────────────────
@@ -94,18 +95,26 @@ type CFUser struct {
Username string `json:"username"`
}
// CFZoneAccount representa la cuenta propietaria de la zona.
type CFZoneAccount struct {
ID string `json:"id"`
Name string `json:"name"`
}
// CFZone representa una zona (dominio) en Cloudflare.
type CFZone struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Paused bool `json:"paused"`
Type string `json:"type"`
NameServers []string `json:"name_servers"`
OriginalNS []string `json:"original_name_servers"`
ModifiedOn string `json:"modified_on"`
ActivatedOn string `json:"activated_on"`
Plan CFPlan `json:"plan"`
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Paused bool `json:"paused"`
Type string `json:"type"`
NameServers []string `json:"name_servers"`
OriginalNS []string `json:"original_name_servers"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
ActivatedOn string `json:"activated_on"`
Account CFZoneAccount `json:"account"`
Plan CFPlan `json:"plan"`
}
// CFPlan representa el plan de una zona.
@@ -114,18 +123,30 @@ type CFPlan struct {
Name string `json:"name"`
}
// CFDNSRecordSettings configuraciones adicionales de un registro DNS.
type CFDNSRecordSettings struct {
IPv4Only bool `json:"ipv4_only,omitempty"`
IPv6Only bool `json:"ipv6_only,omitempty"`
}
// CFDNSRecord representa un registro DNS.
type CFDNSRecord struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
Priority int `json:"priority,omitempty"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Comment string `json:"comment,omitempty"`
Tags []string `json:"tags,omitempty"`
Settings CFDNSRecordSettings `json:"settings,omitempty"`
PrivateRouting bool `json:"private_routing,omitempty"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
Priority int `json:"priority,omitempty"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
CommentModifiedOn string `json:"comment_modified_on,omitempty"`
TagsModifiedOn string `json:"tags_modified_on,omitempty"`
}
// CFSSLStatus representa el estado SSL de una zona.
@@ -222,24 +243,105 @@ func (c *CloudflareClient) GetTokenDetail(tokenID string) (*CFTokenDetail, error
return &detail, nil
}
// GetZones lista todas las zonas de la cuenta.
func (c *CloudflareClient) GetZones() ([]CFZone, error) {
var zones []CFZone
// pagina 1, 50 por página (máximo)
if err := c.get("/zones?per_page=50&page=1", &zones); err != nil {
return nil, err
}
return zones, nil
// cfResultInfo contiene metadatos de paginación de la API de Cloudflare.
type cfResultInfo struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
Count int `json:"count"`
TotalCount int `json:"total_count"`
}
// GetDNSRecords devuelve los registros DNS de una zona.
func (c *CloudflareClient) GetDNSRecords(zoneID string) ([]CFDNSRecord, error) {
var records []CFDNSRecord
path := fmt.Sprintf("/zones/%s/dns_records?per_page=100&page=1", zoneID)
if err := c.get(path, &records); err != nil {
return nil, err
// getWithInfo realiza un GET y decodifica también result_info para paginación.
func (c *CloudflareClient) getWithInfo(path string, dest interface{}) (cfResultInfo, error) {
req, err := http.NewRequest(http.MethodGet, cloudflareBaseURL+path, nil)
if err != nil {
return cfResultInfo{}, fmt.Errorf("cloudflare: crear request: %w", err)
}
return records, nil
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return cfResultInfo{}, fmt.Errorf("cloudflare: ejecutar request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return cfResultInfo{}, fmt.Errorf("cloudflare: leer body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return cfResultInfo{}, fmt.Errorf("cloudflare: status %d %s", resp.StatusCode, string(body))
}
var wrapper struct {
Success bool `json:"success"`
Errors []CFError `json:"errors"`
Result json.RawMessage `json:"result"`
ResultInfo cfResultInfo `json:"result_info"`
}
if err := json.Unmarshal(body, &wrapper); err != nil {
return cfResultInfo{}, fmt.Errorf("cloudflare: decodificar envelope: %w", err)
}
if !wrapper.Success {
if len(wrapper.Errors) > 0 {
return cfResultInfo{}, fmt.Errorf("cloudflare API error %d: %s", wrapper.Errors[0].Code, wrapper.Errors[0].Message)
}
return cfResultInfo{}, fmt.Errorf("cloudflare: respuesta no exitosa")
}
if dest != nil && wrapper.Result != nil {
if err := json.Unmarshal(wrapper.Result, dest); err != nil {
return cfResultInfo{}, fmt.Errorf("cloudflare: decodificar result: %w", err)
}
}
return wrapper.ResultInfo, nil
}
// GetZones lista todas las zonas de la cuenta, iterando páginas si es necesario.
// Si el cliente fue creado con accountID, filtra por esa cuenta.
func (c *CloudflareClient) GetZones() ([]CFZone, error) {
const perPage = 50
var all []CFZone
accountFilter := ""
if c.accountID != "" {
accountFilter = "&account.id=" + c.accountID
}
for page := 1; ; page++ {
path := fmt.Sprintf("/zones?per_page=%d&page=%d%s", perPage, page, accountFilter)
var zones []CFZone
info, err := c.getWithInfo(path, &zones)
if err != nil {
return nil, err
}
all = append(all, zones...)
if page >= info.TotalPages || info.TotalPages == 0 {
break
}
}
return all, nil
}
// GetDNSRecords devuelve todos los registros DNS de una zona, paginando automáticamente.
func (c *CloudflareClient) GetDNSRecords(zoneID string) ([]CFDNSRecord, error) {
const perPage = 5000
var all []CFDNSRecord
for page := 1; ; page++ {
path := fmt.Sprintf("/zones/%s/dns_records?per_page=%d&page=%d", zoneID, perPage, page)
var records []CFDNSRecord
info, err := c.getWithInfo(path, &records)
if err != nil {
return nil, err
}
all = append(all, records...)
if page >= info.TotalPages || info.TotalPages == 0 {
break
}
}
return all, nil
}
// GetSSLCertificates devuelve los certificate packs de una zona.
@@ -328,12 +430,15 @@ func (c *CloudflareClient) doRequest(method, path string, payload interface{}, d
// CFDNSRecordInput es el payload para crear o actualizar un registro DNS.
type CFDNSRecordInput struct {
Type string `json:"type"` // A, AAAA, CNAME, TXT, MX, NS, SRV, CAA…
Name string `json:"name"` // Nombre del registro (ej. "www" o "@")
Content string `json:"content"` // Valor del registro
TTL int `json:"ttl"` // 1 = automático, o segundos (min 60)
Proxied bool `json:"proxied"` // true = nube naranja
Priority int `json:"priority,omitempty"` // Solo para MX / SRV
Type string `json:"type"` // A, AAAA, CNAME, TXT, MX, NS, SRV, CAA…
Name string `json:"name"` // Nombre del registro (ej. "www" o "@")
Content string `json:"content"` // Valor del registro
TTL int `json:"ttl"` // 1 = automático, o segundos (min 60)
Proxied bool `json:"proxied"` // true = nube naranja
Priority int `json:"priority,omitempty"` // Solo para MX / SRV
Comment string `json:"comment,omitempty"` // Comentario descriptivo
Tags []string `json:"tags,omitempty"` // Etiquetas (ej. ["owner:team"])
Settings *CFDNSRecordSettings `json:"settings,omitempty"` // Configuraciones adicionales
}
// ─── DNS CRUD ────────────────────────────────────────────────────────────────