package dns import ( "context" "fmt" "net" v3 "github.com/exoscale/egoscale/v3" "github.com/exoscale/egoscale/v3/credentials" ) // ExoscaleDNS manages DNS records via Exoscale API type ExoscaleDNS struct { client *v3.Client } // DNSRecord represents a DNS record type DNSRecord struct { ID string Type string // A, AAAA, CNAME, etc. Name string // subdomain or @ for apex Content string // IP or target TTL int64 } // NewExoscaleDNS creates a new Exoscale DNS client func NewExoscaleDNS(apiKey, apiSecret string) (*ExoscaleDNS, error) { creds := credentials.NewStaticCredentials(apiKey, apiSecret) client, err := v3.NewClient(creds) if err != nil { return nil, fmt.Errorf("failed to create Exoscale client: %w", err) } return &ExoscaleDNS{client: client}, nil } // GetRecord gets a specific DNS record func (d *ExoscaleDNS) GetRecord(ctx context.Context, zone, recordType, name string) (*DNSRecord, error) { domains, err := d.client.ListDNSDomains(ctx) if err != nil { return nil, fmt.Errorf("failed to list domains: %w", err) } var domainID v3.UUID for _, domain := range domains.DNSDomains { if domain.UnicodeName == zone { domainID = domain.ID break } } if domainID == "" { return nil, fmt.Errorf("zone %s not found", zone) } records, err := d.client.ListDNSDomainRecords(ctx, domainID) if err != nil { return nil, fmt.Errorf("failed to list records: %w", err) } for _, rec := range records.DNSDomainRecords { if rec.Type == v3.DNSDomainRecordType(recordType) && rec.Name == name { return &DNSRecord{ ID: string(rec.ID), Type: string(rec.Type), Name: rec.Name, Content: rec.Content, TTL: rec.Ttl, }, nil } } return nil, fmt.Errorf("record %s.%s not found", name, zone) } // UpdateRecord updates a DNS record func (d *ExoscaleDNS) UpdateRecord(ctx context.Context, zone string, record *DNSRecord) error { domains, err := d.client.ListDNSDomains(ctx) if err != nil { return fmt.Errorf("failed to list domains: %w", err) } var domainID v3.UUID for _, domain := range domains.DNSDomains { if domain.UnicodeName == zone { domainID = domain.ID break } } if domainID == "" { return fmt.Errorf("zone %s not found", zone) } op, err := d.client.UpdateDNSDomainRecord(ctx, domainID, v3.UUID(record.ID), v3.UpdateDNSDomainRecordRequest{ Content: record.Content, Ttl: record.TTL, }) if err != nil { return fmt.Errorf("failed to update record: %w", err) } _, err = d.client.Wait(ctx, op, v3.OperationStateSuccess) if err != nil { return fmt.Errorf("update operation failed: %w", err) } return nil } // ListRecords lists all records in a zone func (d *ExoscaleDNS) ListRecords(ctx context.Context, zone string) ([]DNSRecord, error) { domains, err := d.client.ListDNSDomains(ctx) if err != nil { return nil, fmt.Errorf("failed to list domains: %w", err) } var domainID v3.UUID for _, domain := range domains.DNSDomains { if domain.UnicodeName == zone { domainID = domain.ID break } } if domainID == "" { return nil, fmt.Errorf("zone %s not found", zone) } records, err := d.client.ListDNSDomainRecords(ctx, domainID) if err != nil { return nil, fmt.Errorf("failed to list records: %w", err) } var result []DNSRecord for _, rec := range records.DNSDomainRecords { result = append(result, DNSRecord{ ID: string(rec.ID), Type: string(rec.Type), Name: rec.Name, Content: rec.Content, TTL: rec.Ttl, }) } return result, nil } // ListZones lists all DNS zones managed in Exoscale func (d *ExoscaleDNS) ListZones(ctx context.Context) ([]string, error) { domains, err := d.client.ListDNSDomains(ctx) if err != nil { return nil, fmt.Errorf("failed to list domains: %w", err) } var zones []string for _, domain := range domains.DNSDomains { zones = append(zones, domain.UnicodeName) } return zones, nil } // ResolveCurrentIP resolves the current IP for a hostname func ResolveCurrentIP(hostname string) (string, error) { ips, err := net.LookupIP(hostname) if err != nil { return "", err } for _, ip := range ips { if ipv4 := ip.To4(); ipv4 != nil { return ipv4.String(), nil } } return "", fmt.Errorf("no IPv4 address found for %s", hostname) }