246 lines
5.7 KiB
Go
246 lines
5.7 KiB
Go
package cloudflare
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"lambdamail/protocols/internal/application/port"
|
|
"lambdamail/protocols/internal/domain/entity"
|
|
)
|
|
|
|
type CloudflareAdapter struct {
|
|
apiToken string
|
|
baseURL string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewCloudflareAdapter(apiToken string) *CloudflareAdapter {
|
|
return &CloudflareAdapter{
|
|
apiToken: apiToken,
|
|
baseURL: "https://api.cloudflare.com/client/v4",
|
|
httpClient: &http.Client{},
|
|
}
|
|
}
|
|
|
|
type cfZoneResponse struct {
|
|
Success bool `json:"success"`
|
|
Result []struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
} `json:"result"`
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
} `json:"errors"`
|
|
}
|
|
|
|
type cfRecord struct {
|
|
ID string `json:"id,omitempty"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Content string `json:"content"`
|
|
TTL int `json:"ttl"`
|
|
Priority *int `json:"priority,omitempty"`
|
|
Proxied bool `json:"proxied,omitempty"`
|
|
Comment string `json:"comment,omitempty"`
|
|
}
|
|
|
|
type cfRecordResponse struct {
|
|
Success bool `json:"success"`
|
|
Result []cfRecord `json:"result"`
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
} `json:"errors"`
|
|
}
|
|
|
|
type cfSingleRecordResponse struct {
|
|
Success bool `json:"success"`
|
|
Result cfRecord `json:"result"`
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
} `json:"errors"`
|
|
}
|
|
|
|
func (a *CloudflareAdapter) GetZoneID(ctx context.Context, domainName string) (string, error) {
|
|
reqURL := fmt.Sprintf("%s/zones?name=%s", a.baseURL, url.QueryEscape(domainName))
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
a.setHeaders(req)
|
|
|
|
resp, err := a.httpClient.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var data cfZoneResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return "", fmt.Errorf("decode json: %w", err)
|
|
}
|
|
|
|
if !data.Success || len(data.Result) == 0 {
|
|
errMsg := "zone not found"
|
|
if len(data.Errors) > 0 {
|
|
errMsg = data.Errors[0].Message
|
|
}
|
|
return "", fmt.Errorf("cloudflare API error: %s", errMsg)
|
|
}
|
|
|
|
return data.Result[0].ID, nil
|
|
}
|
|
|
|
func (a *CloudflareAdapter) ListRecords(ctx context.Context, zoneID string) ([]entity.DnsRecord, error) {
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records?per_page=100", a.baseURL, zoneID)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a.setHeaders(req)
|
|
|
|
resp, err := a.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var data cfRecordResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return nil, fmt.Errorf("decode json: %w", err)
|
|
}
|
|
|
|
if !data.Success {
|
|
return nil, fmt.Errorf("cloudflare API error listing records")
|
|
}
|
|
|
|
var out []entity.DnsRecord
|
|
for _, r := range data.Result {
|
|
out = append(out, entity.DnsRecord{
|
|
ID: r.ID,
|
|
Type: r.Type,
|
|
Name: r.Name,
|
|
Value: r.Content,
|
|
TTL: r.TTL,
|
|
Priority: r.Priority,
|
|
Proxied: r.Proxied,
|
|
Comment: r.Comment,
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (a *CloudflareAdapter) CreateRecord(ctx context.Context, zoneID string, record entity.DnsRecord) error {
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records", a.baseURL, zoneID)
|
|
cfBody := cfRecord{
|
|
Type: record.Type,
|
|
Name: record.Name,
|
|
Content: record.Value,
|
|
TTL: record.TTL,
|
|
Priority: record.Priority,
|
|
Proxied: record.Proxied,
|
|
Comment: record.Comment,
|
|
}
|
|
|
|
payload, err := json.Marshal(cfBody)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewBuffer(payload))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.setHeaders(req)
|
|
|
|
resp, err := a.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var data cfSingleRecordResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !data.Success {
|
|
errMsg := "create record failed"
|
|
if len(data.Errors) > 0 {
|
|
errMsg = data.Errors[0].Message
|
|
}
|
|
return fmt.Errorf("cloudflare API error: %s", errMsg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *CloudflareAdapter) UpdateRecord(ctx context.Context, zoneID string, record entity.DnsRecord) error {
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records/%s", a.baseURL, zoneID, record.ID)
|
|
cfBody := cfRecord{
|
|
Type: record.Type,
|
|
Name: record.Name,
|
|
Content: record.Value,
|
|
TTL: record.TTL,
|
|
Priority: record.Priority,
|
|
Proxied: record.Proxied,
|
|
Comment: record.Comment,
|
|
}
|
|
|
|
payload, err := json.Marshal(cfBody)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, reqURL, bytes.NewBuffer(payload))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.setHeaders(req)
|
|
|
|
resp, err := a.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var data cfSingleRecordResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !data.Success {
|
|
return fmt.Errorf("cloudflare API error updating record")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *CloudflareAdapter) DeleteRecord(ctx context.Context, zoneID string, recordID string) error {
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records/%s", a.baseURL, zoneID, recordID)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, reqURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.setHeaders(req)
|
|
|
|
resp, err := a.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *CloudflareAdapter) setHeaders(req *http.Request) {
|
|
req.Header.Set("Authorization", "Bearer "+a.apiToken)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
|
|
// Ensure port interface compliance
|
|
var _ port.DnsProvider = (*CloudflareAdapter)(nil)
|