dns.go
package aws
import (
"fmt"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/route53"
route53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
"github.com/readysite/readysite/pkg/platform"
)
// CreateDNSZone creates a Route 53 hosted zone
func (b *backend) CreateDNSZone(domain string) (*platform.DNSZone, error) {
input := &route53.CreateHostedZoneInput{
Name: aws.String(domain),
CallerReference: aws.String(fmt.Sprintf("%s-%d", domain, time.Now().Unix())),
}
result, err := b.route53.CreateHostedZone(b.ctx, input)
if err != nil {
return nil, fmt.Errorf("create hosted zone: %w", err)
}
return &platform.DNSZone{
Name: *result.HostedZone.Name,
TTL: 300,
}, nil
}
// GetDNSZone retrieves a Route 53 hosted zone
func (b *backend) GetDNSZone(domain string) (*platform.DNSZone, error) {
input := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(domain),
}
result, err := b.route53.ListHostedZonesByName(b.ctx, input)
if err != nil {
return nil, fmt.Errorf("list hosted zones: %w", err)
}
for _, zone := range result.HostedZones {
if strings.TrimSuffix(*zone.Name, ".") == domain {
return &platform.DNSZone{
Name: strings.TrimSuffix(*zone.Name, "."),
TTL: 300,
}, nil
}
}
return nil, platform.ErrNotFound
}
// AddDNSRecord adds a Route 53 record
func (b *backend) AddDNSRecord(zone string, record platform.DNSRecord) error {
zoneID, err := b.getZoneID(zone)
if err != nil {
return err
}
name := record.Name
if name == "@" {
name = zone
} else {
name = record.Name + "." + zone
}
input := &route53.ChangeResourceRecordSetsInput{
HostedZoneId: aws.String(zoneID),
ChangeBatch: &route53types.ChangeBatch{
Changes: []route53types.Change{
{
Action: route53types.ChangeActionUpsert,
ResourceRecordSet: &route53types.ResourceRecordSet{
Name: aws.String(name),
Type: route53types.RRType(record.Type),
TTL: aws.Int64(int64(record.TTL)),
ResourceRecords: []route53types.ResourceRecord{
{Value: aws.String(record.Value)},
},
},
},
},
},
}
_, err = b.route53.ChangeResourceRecordSets(b.ctx, input)
if err != nil {
return fmt.Errorf("change record sets: %w", err)
}
return nil
}
// DeleteDNSRecord removes a Route 53 record
func (b *backend) DeleteDNSRecord(zone, recordID string) error {
// recordID format: "TYPE:NAME:VALUE"
parts := strings.SplitN(recordID, ":", 3)
if len(parts) != 3 {
return fmt.Errorf("invalid record ID format")
}
zoneID, err := b.getZoneID(zone)
if err != nil {
return err
}
input := &route53.ChangeResourceRecordSetsInput{
HostedZoneId: aws.String(zoneID),
ChangeBatch: &route53types.ChangeBatch{
Changes: []route53types.Change{
{
Action: route53types.ChangeActionDelete,
ResourceRecordSet: &route53types.ResourceRecordSet{
Name: aws.String(parts[1]),
Type: route53types.RRType(parts[0]),
TTL: aws.Int64(300),
ResourceRecords: []route53types.ResourceRecord{
{Value: aws.String(parts[2])},
},
},
},
},
},
}
_, err = b.route53.ChangeResourceRecordSets(b.ctx, input)
if err != nil {
return fmt.Errorf("delete record: %w", err)
}
return nil
}
func (b *backend) getZoneID(domain string) (string, error) {
input := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(domain),
}
result, err := b.route53.ListHostedZonesByName(b.ctx, input)
if err != nil {
return "", fmt.Errorf("list hosted zones: %w", err)
}
for _, zone := range result.HostedZones {
if strings.TrimSuffix(*zone.Name, ".") == domain {
// Zone ID comes as /hostedzone/XXXXX, extract just the ID
return strings.TrimPrefix(*zone.Id, "/hostedzone/"), nil
}
}
return "", platform.ErrNotFound
}