添加SSL证书管理功能,包括安装、续期、列出、撤销和申请证书的命令,同时更新依赖项和修复磁盘使用情况计算逻辑。
This commit is contained in:
153
agent-wdd/cloudflare/Cloudflare.go
Normal file
153
agent-wdd/cloudflare/Cloudflare.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"agent-wdd/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// CloudflareAPIBaseURL is the base URL for Cloudflare API
|
||||
CloudflareAPIBaseURL = "https://api.cloudflare.com/client/v4"
|
||||
// DefaultTimeout is the default timeout for HTTP requests
|
||||
DefaultTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// CloudflareClient represents a client for interacting with Cloudflare API
|
||||
type CloudflareClient struct {
|
||||
apiToken string
|
||||
baseURL string
|
||||
client *http.Client
|
||||
userAgent string
|
||||
}
|
||||
|
||||
var (
|
||||
instance *CloudflareClient
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Response is the general response structure from Cloudflare API
|
||||
type Response struct {
|
||||
Result interface{} `json:"result"`
|
||||
ResultInfo *ResultInfo `json:"result_info,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Errors []Error `json:"errors"`
|
||||
Messages []interface{} `json:"messages"`
|
||||
}
|
||||
|
||||
// ResultInfo contains pagination information
|
||||
type ResultInfo struct {
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"per_page"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Count int `json:"count"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
// Error represents an error returned by the Cloudflare API
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
ErrorChain []Error `json:"error_chain,omitempty"`
|
||||
}
|
||||
|
||||
// NewClient creates a new instance of CloudflareClient with the given API token
|
||||
func NewClient(apiToken string) *CloudflareClient {
|
||||
log.Debug("Creating new Cloudflare client")
|
||||
return &CloudflareClient{
|
||||
apiToken: apiToken,
|
||||
baseURL: CloudflareAPIBaseURL,
|
||||
client: &http.Client{Timeout: DefaultTimeout},
|
||||
userAgent: "WddSuperAgent/1.0",
|
||||
}
|
||||
}
|
||||
|
||||
// GetInstance returns the singleton instance of CloudflareClient
|
||||
func GetInstance(apiToken string) *CloudflareClient {
|
||||
if instance == nil {
|
||||
once.Do(func() {
|
||||
instance = NewClient(apiToken)
|
||||
log.Info("Cloudflare client singleton instance created")
|
||||
})
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
// SetAPIToken updates the API token used for authentication
|
||||
func (c *CloudflareClient) SetAPIToken(apiToken string) {
|
||||
c.apiToken = apiToken
|
||||
log.Info("Cloudflare API token updated")
|
||||
}
|
||||
|
||||
// SetTimeout updates the HTTP client timeout
|
||||
func (c *CloudflareClient) SetTimeout(timeout time.Duration) {
|
||||
c.client.Timeout = timeout
|
||||
log.Info("Cloudflare client timeout updated to %v", timeout)
|
||||
}
|
||||
|
||||
// doRequest performs an HTTP request with the given method, URL, and body
|
||||
func (c *CloudflareClient) doRequest(method, url string, body interface{}) (*Response, error) {
|
||||
log.Debug("Performing %s request to %s", method, url)
|
||||
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal request body: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
reqBody = bytes.NewBuffer(jsonBody)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, reqBody)
|
||||
if err != nil {
|
||||
log.Error("Failed to create HTTP request: %v", err)
|
||||
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||
}
|
||||
|
||||
// Set headers
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
|
||||
// Perform request
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
log.Error("Failed to execute HTTP request: %v", err)
|
||||
return nil, fmt.Errorf("failed to execute HTTP request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error("Failed to read response body: %v", err)
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var cfResp Response
|
||||
if err := json.Unmarshal(respBody, &cfResp); err != nil {
|
||||
log.Error("Failed to unmarshal response: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
// Check for API errors
|
||||
if !cfResp.Success {
|
||||
errorMsg := "Cloudflare API error"
|
||||
if len(cfResp.Errors) > 0 {
|
||||
errorMsg = fmt.Sprintf("%s: %s (code: %d)", errorMsg, cfResp.Errors[0].Message, cfResp.Errors[0].Code)
|
||||
}
|
||||
log.Error(errorMsg)
|
||||
return &cfResp, fmt.Errorf(errorMsg)
|
||||
}
|
||||
|
||||
log.Debug("Request completed successfully")
|
||||
return &cfResp, nil
|
||||
}
|
||||
310
agent-wdd/cloudflare/Cloudflare_test.go
Normal file
310
agent-wdd/cloudflare/Cloudflare_test.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"agent-wdd/utils"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 测试前的设置函数,获取API token并检查环境设置
|
||||
func setupTest(t *testing.T) *CloudflareClient {
|
||||
// 从环境变量获取API令牌
|
||||
apiToken := "T7LxBemfe8SNGWkT9uz2XIc1e22ifAbBv_POJvDP"
|
||||
|
||||
// 创建Cloudflare客户端实例
|
||||
return GetInstance(apiToken)
|
||||
}
|
||||
|
||||
// TestClientCreation 测试客户端创建
|
||||
func TestClientCreation(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
if client == nil {
|
||||
t.Fatal("客户端创建失败")
|
||||
}
|
||||
}
|
||||
|
||||
// TestListZones 测试获取域名列表
|
||||
func TestListZones(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
|
||||
// 列出所有域名
|
||||
zones, err := client.ListZones(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名列表失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("找到 %d 个域名", len(zones))
|
||||
for _, zone := range zones {
|
||||
t.Logf("域名: %s (ID: %s, 状态: %s)", zone.Name, zone.ID, zone.Status)
|
||||
|
||||
utils.BeautifulPrint(zone)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetZone 测试获取单个域名详情
|
||||
func TestGetZone(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
|
||||
// 列出所有域名
|
||||
zones, err := client.ListZones(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果没有域名,则跳过测试
|
||||
if len(zones) == 0 {
|
||||
t.Skip("没有找到域名,跳过测试")
|
||||
}
|
||||
|
||||
// 使用第一个域名进行测试
|
||||
zoneID := zones[0].ID
|
||||
zoneName := zones[0].Name
|
||||
|
||||
// 获取特定域名的详细信息
|
||||
zone, err := client.GetZone(zoneID)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名详情失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("域名 %s 的详情:", zoneName)
|
||||
t.Logf(" - ID: %s", zone.ID)
|
||||
t.Logf(" - 状态: %s", zone.Status)
|
||||
t.Logf(" - 名称服务器: %v", zone.NameServers)
|
||||
t.Logf(" - 创建时间: %s", zone.CreatedOn)
|
||||
|
||||
utils.BeautifulPrint(zone)
|
||||
}
|
||||
|
||||
// TestListDNSRecords 测试获取DNS记录列表
|
||||
func TestListDNSRecords(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
|
||||
// 列出所有域名
|
||||
zones, err := client.ListZones(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果没有域名,则跳过测试
|
||||
if len(zones) == 0 {
|
||||
t.Skip("没有找到域名,跳过测试")
|
||||
}
|
||||
|
||||
// 使用第一个域名进行测试
|
||||
zoneID := zones[0].ID
|
||||
zoneName := zones[0].Name
|
||||
|
||||
// 列出域名下的所有DNS记录
|
||||
records, err := client.ListDNSRecords(zoneID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取DNS记录列表失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("在域名 %s 中找到 %d 条DNS记录", zoneName, len(records))
|
||||
for i, record := range records {
|
||||
if i < 5 { // 只打印前5条记录,避免输出过多
|
||||
t.Logf(" - 记录 %d: %s (类型: %s, 内容: %s)", i+1, record.Name, record.Type, record.Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateUpdateDeleteDNSRecord 测试DNS记录的创建、更新和删除
|
||||
func TestCreateUpdateDeleteDNSRecord(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
|
||||
// 列出所有域名
|
||||
zones, err := client.ListZones(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果没有域名,则跳过测试
|
||||
if len(zones) == 0 {
|
||||
t.Skip("没有找到域名,跳过测试")
|
||||
}
|
||||
|
||||
// 使用第一个域名进行测试
|
||||
zoneID := zones[0].ID
|
||||
zoneName := zones[0].Name
|
||||
|
||||
// 创建新的DNS记录
|
||||
testRecordName := fmt.Sprintf("test-api.%s", zoneName)
|
||||
newRecord := DNSRecord{
|
||||
Type: "A",
|
||||
Name: testRecordName,
|
||||
Content: "192.0.2.1", // 示例IP地址
|
||||
TTL: 3600, // 1小时
|
||||
Proxied: false, // 不使用Cloudflare代理
|
||||
Comment: "自动化测试记录",
|
||||
}
|
||||
|
||||
// 检查记录是否已存在
|
||||
existingRecord, err := client.FindDNSRecord(zoneID, newRecord.Name, newRecord.Type)
|
||||
if err != nil {
|
||||
t.Fatalf("检查已存在记录失败: %v", err)
|
||||
}
|
||||
|
||||
var recordID string
|
||||
|
||||
// 测试创建或更新记录
|
||||
if existingRecord != nil {
|
||||
// 记录已存在,更新它
|
||||
t.Logf("记录 %s 已存在,正在更新", newRecord.Name)
|
||||
updatedRecord, err := client.UpdateDNSRecord(zoneID, existingRecord.ID, newRecord)
|
||||
if err != nil {
|
||||
t.Fatalf("更新DNS记录失败: %v", err)
|
||||
}
|
||||
t.Logf("成功更新DNS记录: %s", updatedRecord.Name)
|
||||
recordID = updatedRecord.ID
|
||||
} else {
|
||||
// 创建新记录
|
||||
createdRecord, err := client.CreateDNSRecord(zoneID, newRecord)
|
||||
if err != nil {
|
||||
t.Fatalf("创建DNS记录失败: %v", err)
|
||||
}
|
||||
t.Logf("成功创建DNS记录: %s", createdRecord.Name)
|
||||
recordID = createdRecord.ID
|
||||
}
|
||||
|
||||
// 测试获取特定DNS记录详情
|
||||
record, err := client.GetDNSRecord(zoneID, recordID)
|
||||
if err != nil {
|
||||
t.Fatalf("获取DNS记录详情失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("DNS记录详情:")
|
||||
t.Logf(" - ID: %s", record.ID)
|
||||
t.Logf(" - 名称: %s", record.Name)
|
||||
t.Logf(" - 类型: %s", record.Type)
|
||||
t.Logf(" - 内容: %s", record.Content)
|
||||
t.Logf(" - TTL: %d", record.TTL)
|
||||
t.Logf(" - 已代理: %t", record.Proxied)
|
||||
|
||||
// 测试删除DNS记录
|
||||
success, err := client.DeleteDNSRecord(zoneID, recordID)
|
||||
if err != nil {
|
||||
t.Fatalf("删除DNS记录失败: %v", err)
|
||||
}
|
||||
|
||||
if success {
|
||||
t.Logf("成功删除DNS记录: %s", record.Name)
|
||||
} else {
|
||||
t.Errorf("删除DNS记录失败: %s", record.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetZoneIDByName 测试通过域名获取域名ID
|
||||
func TestGetZoneIDByName(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
|
||||
// 列出所有域名
|
||||
zones, err := client.ListZones(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果没有域名,则跳过测试
|
||||
if len(zones) == 0 {
|
||||
t.Skip("没有找到域名,跳过测试")
|
||||
}
|
||||
|
||||
// 使用第一个域名进行测试
|
||||
zoneName := zones[0].Name
|
||||
expectedID := zones[0].ID
|
||||
|
||||
// 测试辅助函数
|
||||
id, err := getZoneIDByName(client, zoneName)
|
||||
if err != nil {
|
||||
t.Fatalf("通过名称获取域名ID失败: %v", err)
|
||||
}
|
||||
|
||||
if id != expectedID {
|
||||
t.Errorf("获取的域名ID不匹配: 期望 %s, 实际 %s", expectedID, id)
|
||||
} else {
|
||||
t.Logf("成功通过名称获取域名ID: %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateOrUpdateDNSRecord 测试创建或更新DNS记录辅助函数
|
||||
func TestCreateOrUpdateDNSRecord(t *testing.T) {
|
||||
client := setupTest(t)
|
||||
|
||||
// 列出所有域名
|
||||
zones, err := client.ListZones(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("获取域名列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果没有域名,则跳过测试
|
||||
if len(zones) == 0 {
|
||||
t.Skip("没有找到域名,跳过测试")
|
||||
}
|
||||
|
||||
// 使用第一个域名进行测试
|
||||
zoneID := zones[0].ID
|
||||
zoneName := zones[0].Name
|
||||
|
||||
// 创建测试记录
|
||||
testRecordName := fmt.Sprintf("test-helper.%s", zoneName)
|
||||
record := DNSRecord{
|
||||
Type: "A",
|
||||
Name: testRecordName,
|
||||
Content: "192.0.2.2", // 示例IP地址
|
||||
TTL: 1800, // 30分钟
|
||||
Proxied: false,
|
||||
Comment: "测试辅助函数创建的记录",
|
||||
}
|
||||
|
||||
// 测试辅助函数
|
||||
updatedRecord, err := createOrUpdateDNSRecord(client, zoneID, record)
|
||||
if err != nil {
|
||||
t.Fatalf("创建或更新DNS记录失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("通过辅助函数成功创建或更新DNS记录: %s", updatedRecord.Name)
|
||||
|
||||
// 清理: 删除测试记录
|
||||
if updatedRecord != nil && updatedRecord.ID != "" {
|
||||
success, err := client.DeleteDNSRecord(zoneID, updatedRecord.ID)
|
||||
if err != nil {
|
||||
t.Logf("清理时删除DNS记录失败: %v", err)
|
||||
}
|
||||
|
||||
if success {
|
||||
t.Logf("清理: 成功删除测试DNS记录")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 以下是从Example.go转移过来的辅助函数,为了在测试中使用
|
||||
|
||||
// getZoneIDByName 通过域名获取对应的Zone ID
|
||||
func getZoneIDByName(client *CloudflareClient, name string) (string, error) {
|
||||
zones, err := client.ListZones(&ZoneFilter{Name: name})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(zones) == 0 {
|
||||
return "", fmt.Errorf("no zone found with name: %s", name)
|
||||
}
|
||||
|
||||
return zones[0].ID, nil
|
||||
}
|
||||
|
||||
// createOrUpdateDNSRecord 创建或更新DNS记录
|
||||
func createOrUpdateDNSRecord(client *CloudflareClient, zoneID string, record DNSRecord) (*DNSRecord, error) {
|
||||
// 查找是否已存在相同名称和类型的记录
|
||||
existingRecord, err := client.FindDNSRecord(zoneID, record.Name, record.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existingRecord != nil {
|
||||
// 更新现有记录
|
||||
return client.UpdateDNSRecord(zoneID, existingRecord.ID, record)
|
||||
}
|
||||
|
||||
// 创建新记录
|
||||
return client.CreateDNSRecord(zoneID, record)
|
||||
}
|
||||
304
agent-wdd/cloudflare/DNS.go
Normal file
304
agent-wdd/cloudflare/DNS.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"agent-wdd/log"
|
||||
)
|
||||
|
||||
// DNSRecord represents a DNS record in Cloudflare
|
||||
type DNSRecord struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Proxiable bool `json:"proxiable,omitempty"`
|
||||
Proxied bool `json:"proxied"`
|
||||
TTL int `json:"ttl"`
|
||||
Settings map[string]interface{} `json:"settings,omitempty"`
|
||||
Meta map[string]interface{} `json:"meta,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CreatedOn string `json:"created_on,omitempty"`
|
||||
ModifiedOn string `json:"modified_on,omitempty"`
|
||||
CommentModifiedOn string `json:"comment_modified_on,omitempty"`
|
||||
}
|
||||
|
||||
// DNSRecordFilter represents filters for listing DNS records
|
||||
type DNSRecordFilter struct {
|
||||
Type string
|
||||
Name string
|
||||
Content string
|
||||
Page int
|
||||
PerPage int
|
||||
Order string
|
||||
Direction string
|
||||
Match string
|
||||
}
|
||||
|
||||
// ListDNSRecords retrieves all DNS records for a zone
|
||||
//
|
||||
// Parameters:
|
||||
// - zoneID: The zone ID
|
||||
// - filter: Optional filter to apply to the DNS records list
|
||||
//
|
||||
// Returns:
|
||||
// - []DNSRecord: List of DNS records
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) ListDNSRecords(zoneID string, filter *DNSRecordFilter) ([]DNSRecord, error) {
|
||||
log.Debug("Listing DNS records for zone ID: %s with filter: %+v", zoneID, filter)
|
||||
|
||||
// Build URL and query parameters
|
||||
endpoint := fmt.Sprintf("%s/zones/%s/dns_records", c.baseURL, zoneID)
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
log.Error("Failed to parse URL: %v", err)
|
||||
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
if filter != nil {
|
||||
if filter.Type != "" {
|
||||
q.Add("type", filter.Type)
|
||||
}
|
||||
if filter.Name != "" {
|
||||
q.Add("name", filter.Name)
|
||||
}
|
||||
if filter.Content != "" {
|
||||
q.Add("content", filter.Content)
|
||||
}
|
||||
if filter.Page > 0 {
|
||||
q.Add("page", strconv.Itoa(filter.Page))
|
||||
}
|
||||
if filter.PerPage > 0 {
|
||||
q.Add("per_page", strconv.Itoa(filter.PerPage))
|
||||
}
|
||||
if filter.Order != "" {
|
||||
q.Add("order", filter.Order)
|
||||
}
|
||||
if filter.Direction != "" {
|
||||
q.Add("direction", filter.Direction)
|
||||
}
|
||||
if filter.Match != "" {
|
||||
q.Add("match", filter.Match)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to list DNS records: %v", err)
|
||||
return nil, fmt.Errorf("failed to list DNS records: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var records []DNSRecord
|
||||
result, err := json.Marshal(resp.Result)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal result: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(result, &records); err != nil {
|
||||
log.Error("Failed to unmarshal DNS records: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal DNS records: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Successfully retrieved %d DNS records for zone ID: %s", len(records), zoneID)
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// CreateDNSRecord creates a new DNS record in the specified zone
|
||||
//
|
||||
// Parameters:
|
||||
// - zoneID: The zone ID
|
||||
// - record: The DNS record to create
|
||||
//
|
||||
// Returns:
|
||||
// - *DNSRecord: The created DNS record
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) CreateDNSRecord(zoneID string, record DNSRecord) (*DNSRecord, error) {
|
||||
log.Debug("Creating DNS record in zone ID: %s with data: %+v", zoneID, record)
|
||||
|
||||
// Build URL
|
||||
endpoint := fmt.Sprintf("%s/zones/%s/dns_records", c.baseURL, zoneID)
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodPost, endpoint, record)
|
||||
if err != nil {
|
||||
log.Error("Failed to create DNS record: %v", err)
|
||||
return nil, fmt.Errorf("failed to create DNS record: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var createdRecord DNSRecord
|
||||
result, err := json.Marshal(resp.Result)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal result: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(result, &createdRecord); err != nil {
|
||||
log.Error("Failed to unmarshal created DNS record: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal created DNS record: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Successfully created DNS record: %s (%s) in zone ID: %s", createdRecord.Name, createdRecord.ID, zoneID)
|
||||
return &createdRecord, nil
|
||||
}
|
||||
|
||||
// GetDNSRecord retrieves a specific DNS record by ID
|
||||
//
|
||||
// Parameters:
|
||||
// - zoneID: The zone ID
|
||||
// - recordID: The DNS record ID
|
||||
//
|
||||
// Returns:
|
||||
// - *DNSRecord: The DNS record
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) GetDNSRecord(zoneID, recordID string) (*DNSRecord, error) {
|
||||
log.Debug("Getting DNS record ID: %s in zone ID: %s", recordID, zoneID)
|
||||
|
||||
// Build URL
|
||||
endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", c.baseURL, zoneID, recordID)
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to get DNS record: %v", err)
|
||||
return nil, fmt.Errorf("failed to get DNS record: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var record DNSRecord
|
||||
result, err := json.Marshal(resp.Result)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal result: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(result, &record); err != nil {
|
||||
log.Error("Failed to unmarshal DNS record: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal DNS record: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Successfully retrieved DNS record: %s (%s) from zone ID: %s", record.Name, record.ID, zoneID)
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// UpdateDNSRecord updates an existing DNS record
|
||||
//
|
||||
// Parameters:
|
||||
// - zoneID: The zone ID
|
||||
// - recordID: The DNS record ID
|
||||
// - record: The updated DNS record data
|
||||
//
|
||||
// Returns:
|
||||
// - *DNSRecord: The updated DNS record
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) UpdateDNSRecord(zoneID, recordID string, record DNSRecord) (*DNSRecord, error) {
|
||||
log.Debug("Updating DNS record ID: %s in zone ID: %s with data: %+v", recordID, zoneID, record)
|
||||
|
||||
// Build URL
|
||||
endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", c.baseURL, zoneID, recordID)
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodPut, endpoint, record)
|
||||
if err != nil {
|
||||
log.Error("Failed to update DNS record: %v", err)
|
||||
return nil, fmt.Errorf("failed to update DNS record: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var updatedRecord DNSRecord
|
||||
result, err := json.Marshal(resp.Result)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal result: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(result, &updatedRecord); err != nil {
|
||||
log.Error("Failed to unmarshal updated DNS record: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal updated DNS record: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Successfully updated DNS record: %s (%s) in zone ID: %s", updatedRecord.Name, updatedRecord.ID, zoneID)
|
||||
return &updatedRecord, nil
|
||||
}
|
||||
|
||||
// DeleteDNSRecord deletes a DNS record
|
||||
//
|
||||
// Parameters:
|
||||
// - zoneID: The zone ID
|
||||
// - recordID: The DNS record ID
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if the record was deleted successfully
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) DeleteDNSRecord(zoneID, recordID string) (bool, error) {
|
||||
log.Debug("Deleting DNS record ID: %s in zone ID: %s", recordID, zoneID)
|
||||
|
||||
// Build URL
|
||||
endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", c.baseURL, zoneID, recordID)
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodDelete, endpoint, nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete DNS record: %v", err)
|
||||
return false, fmt.Errorf("failed to delete DNS record: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
result, ok := resp.Result.(map[string]interface{})
|
||||
if !ok {
|
||||
log.Error("Unexpected response format for DNS record deletion")
|
||||
return false, fmt.Errorf("unexpected response format for DNS record deletion")
|
||||
}
|
||||
|
||||
id, ok := result["id"].(string)
|
||||
if !ok {
|
||||
log.Error("Failed to extract ID from deletion response")
|
||||
return false, fmt.Errorf("failed to extract ID from deletion response")
|
||||
}
|
||||
|
||||
log.Info("Successfully deleted DNS record ID: %s from zone ID: %s", id, zoneID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// FindDNSRecord finds a DNS record by name and type
|
||||
//
|
||||
// Parameters:
|
||||
// - zoneID: The zone ID
|
||||
// - name: The DNS record name
|
||||
// - recordType: The DNS record type (A, AAAA, CNAME, etc.)
|
||||
//
|
||||
// Returns:
|
||||
// - *DNSRecord: The found DNS record, or nil if not found
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) FindDNSRecord(zoneID, name, recordType string) (*DNSRecord, error) {
|
||||
log.Debug("Finding DNS record with name: %s and type: %s in zone ID: %s", name, recordType, zoneID)
|
||||
|
||||
filter := &DNSRecordFilter{
|
||||
Name: name,
|
||||
Type: recordType,
|
||||
}
|
||||
|
||||
records, err := c.ListDNSRecords(zoneID, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
log.Warning("No DNS record found with name: %s and type: %s in zone ID: %s", name, recordType, zoneID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Info("Found DNS record: %s (%s) with type: %s in zone ID: %s", records[0].Name, records[0].ID, records[0].Type, zoneID)
|
||||
return &records[0], nil
|
||||
}
|
||||
218
agent-wdd/cloudflare/Zone.go
Normal file
218
agent-wdd/cloudflare/Zone.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"agent-wdd/log"
|
||||
)
|
||||
|
||||
// Zone represents a Cloudflare zone (domain)
|
||||
type Zone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Paused bool `json:"paused"`
|
||||
Type string `json:"type"`
|
||||
DevelopmentMode int `json:"development_mode"`
|
||||
NameServers []string `json:"name_servers"`
|
||||
OriginalNameServers []string `json:"original_name_servers"`
|
||||
OriginalRegistrar interface{} `json:"original_registrar"`
|
||||
OriginalDNSHost interface{} `json:"original_dnshost"`
|
||||
ModifiedOn string `json:"modified_on"`
|
||||
CreatedOn string `json:"created_on"`
|
||||
ActivatedOn string `json:"activated_on"`
|
||||
Meta ZoneMeta `json:"meta"`
|
||||
Owner Owner `json:"owner"`
|
||||
Account Account `json:"account"`
|
||||
Tenant Tenant `json:"tenant"`
|
||||
TenantUnit TenantUnit `json:"tenant_unit"`
|
||||
Permissions []string `json:"permissions"`
|
||||
Plan Plan `json:"plan"`
|
||||
}
|
||||
|
||||
// ZoneMeta contains zone metadata
|
||||
type ZoneMeta struct {
|
||||
Step int `json:"step"`
|
||||
CustomCertificateQuota int `json:"custom_certificate_quota"`
|
||||
PageRuleQuota int `json:"page_rule_quota"`
|
||||
PhishingDetected bool `json:"phishing_detected"`
|
||||
}
|
||||
|
||||
// Owner represents the owner of a zone
|
||||
type Owner struct {
|
||||
ID interface{} `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Email interface{} `json:"email"`
|
||||
}
|
||||
|
||||
// Account represents a Cloudflare account
|
||||
type Account struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Tenant represents a tenant
|
||||
type Tenant struct {
|
||||
ID interface{} `json:"id"`
|
||||
Name interface{} `json:"name"`
|
||||
}
|
||||
|
||||
// TenantUnit represents a tenant unit
|
||||
type TenantUnit struct {
|
||||
ID interface{} `json:"id"`
|
||||
}
|
||||
|
||||
// Plan represents a zone plan
|
||||
type Plan struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price int `json:"price"`
|
||||
Currency string `json:"currency"`
|
||||
Frequency string `json:"frequency"`
|
||||
IsSubscribed bool `json:"is_subscribed"`
|
||||
CanSubscribe bool `json:"can_subscribe"`
|
||||
LegacyID string `json:"legacy_id"`
|
||||
LegacyDiscount bool `json:"legacy_discount"`
|
||||
ExternallyManaged bool `json:"externally_managed"`
|
||||
}
|
||||
|
||||
// ZoneFilter represents filters for listing zones
|
||||
type ZoneFilter struct {
|
||||
Name string
|
||||
Status string
|
||||
Page int
|
||||
PerPage int
|
||||
Direction string
|
||||
Match string
|
||||
}
|
||||
|
||||
// ListZones retrieves a list of zones from Cloudflare based on the provided filters
|
||||
//
|
||||
// Parameters:
|
||||
// - filter: Optional filter to apply to the zone list
|
||||
//
|
||||
// Returns:
|
||||
// - []Zone: List of zones
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) ListZones(filter *ZoneFilter) ([]Zone, error) {
|
||||
log.Debug("Listing zones with filter: %+v", filter)
|
||||
|
||||
// Build URL and query parameters
|
||||
endpoint := fmt.Sprintf("%s/zones", c.baseURL)
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
log.Error("Failed to parse URL: %v", err)
|
||||
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
if filter != nil {
|
||||
if filter.Name != "" {
|
||||
q.Add("name", filter.Name)
|
||||
}
|
||||
if filter.Status != "" {
|
||||
q.Add("status", filter.Status)
|
||||
}
|
||||
if filter.Page > 0 {
|
||||
q.Add("page", fmt.Sprintf("%d", filter.Page))
|
||||
}
|
||||
if filter.PerPage > 0 {
|
||||
q.Add("per_page", fmt.Sprintf("%d", filter.PerPage))
|
||||
}
|
||||
if filter.Direction != "" {
|
||||
q.Add("direction", filter.Direction)
|
||||
}
|
||||
if filter.Match != "" {
|
||||
q.Add("match", filter.Match)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to list zones: %v", err)
|
||||
return nil, fmt.Errorf("failed to list zones: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var zones []Zone
|
||||
result, err := json.Marshal(resp.Result)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal result: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(result, &zones); err != nil {
|
||||
log.Error("Failed to unmarshal zones: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal zones: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Successfully retrieved %d zones", len(zones))
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
// GetZone retrieves details of a specific zone by ID or name
|
||||
//
|
||||
// Parameters:
|
||||
// - identifier: The zone ID or domain name
|
||||
//
|
||||
// Returns:
|
||||
// - *Zone: The zone details
|
||||
// - error: Any errors that occurred during the request
|
||||
func (c *CloudflareClient) GetZone(identifier string) (*Zone, error) {
|
||||
log.Debug("Getting zone details for identifier: %s", identifier)
|
||||
|
||||
// Determine if identifier is an ID or a domain name
|
||||
isID := true
|
||||
for _, char := range identifier {
|
||||
if (char < '0' || char > '9') && (char < 'a' || char > 'f') && (char < 'A' || char > 'F') {
|
||||
isID = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var endpoint string
|
||||
if isID {
|
||||
endpoint = fmt.Sprintf("%s/zones/%s", c.baseURL, identifier)
|
||||
} else {
|
||||
// List zones with name filter
|
||||
zones, err := c.ListZones(&ZoneFilter{Name: identifier})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(zones) == 0 {
|
||||
log.Error("No zone found with name: %s", identifier)
|
||||
return nil, fmt.Errorf("no zone found with name: %s", identifier)
|
||||
}
|
||||
|
||||
endpoint = fmt.Sprintf("%s/zones/%s", c.baseURL, zones[0].ID)
|
||||
}
|
||||
|
||||
// Make request
|
||||
resp, err := c.doRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to get zone: %v", err)
|
||||
return nil, fmt.Errorf("failed to get zone: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var zone Zone
|
||||
result, err := json.Marshal(resp.Result)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal result: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(result, &zone); err != nil {
|
||||
log.Error("Failed to unmarshal zone: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal zone: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Successfully retrieved zone: %s (%s)", zone.Name, zone.ID)
|
||||
return &zone, nil
|
||||
}
|
||||
Reference in New Issue
Block a user