|  | 
|  | 1 | +package libdnsspaceship | 
|  | 2 | + | 
|  | 3 | +import ( | 
|  | 4 | +	"net/netip" | 
|  | 5 | +	"strconv" | 
|  | 6 | +	"strings" | 
|  | 7 | +	"time" | 
|  | 8 | + | 
|  | 9 | +	"github.com/libdns/libdns" | 
|  | 10 | +) | 
|  | 11 | + | 
|  | 12 | +// toLibdnsRR converts a spaceshipRecordUnion (API) to a libdns.Record | 
|  | 13 | +func (p *Provider) toLibdnsRR(sr spaceshipRecordUnion, zone string) libdns.Record { | 
|  | 14 | +	// normalize name relative to zone | 
|  | 15 | +	name := strings.TrimSuffix(sr.Name, "."+zone) | 
|  | 16 | +	name = strings.TrimSuffix(name, ".") | 
|  | 17 | +	if name == zone || sr.Name == zone { | 
|  | 18 | +		name = "" | 
|  | 19 | +	} | 
|  | 20 | +	ttl := time.Duration(sr.TTL) * time.Second | 
|  | 21 | + | 
|  | 22 | +	switch strings.ToUpper(sr.Type) { | 
|  | 23 | +	case "A", "AAAA": | 
|  | 24 | +		if sr.Address != "" { | 
|  | 25 | +			if ip, err := netip.ParseAddr(sr.Address); err == nil { | 
|  | 26 | +				return libdns.Address{Name: name, TTL: ttl, IP: ip, ProviderData: sr} | 
|  | 27 | +			} | 
|  | 28 | +		} | 
|  | 29 | +	case "TXT": | 
|  | 30 | +		return libdns.TXT{Name: name, TTL: ttl, Text: sr.Value, ProviderData: sr} | 
|  | 31 | +	case "CNAME": | 
|  | 32 | +		return libdns.CNAME{Name: name, TTL: ttl, Target: sr.Cname, ProviderData: sr} | 
|  | 33 | +	case "MX": | 
|  | 34 | +		return libdns.MX{Name: name, TTL: ttl, Target: sr.Exchange, Preference: uint16(sr.Preference), ProviderData: sr} | 
|  | 35 | +	case "SRV": | 
|  | 36 | +		// extract service/transport from name if present | 
|  | 37 | +		service, transport := "", "" | 
|  | 38 | +		if sr.Name != "" { | 
|  | 39 | +			labels := strings.Split(sr.Name, ".") | 
|  | 40 | +			if len(labels) >= 2 { | 
|  | 41 | +				service = strings.TrimPrefix(labels[0], "_") | 
|  | 42 | +				transport = strings.TrimPrefix(labels[1], "_") | 
|  | 43 | +			} | 
|  | 44 | +		} | 
|  | 45 | +		port := sr.PortInt | 
|  | 46 | +		if port == 0 { | 
|  | 47 | +			switch pv := sr.Port.(type) { | 
|  | 48 | +			case string: | 
|  | 49 | +				if v, err := strconv.Atoi(strings.TrimPrefix(pv, "_")); err == nil { | 
|  | 50 | +					port = v | 
|  | 51 | +				} | 
|  | 52 | +			case float64: | 
|  | 53 | +				port = int(pv) | 
|  | 54 | +			case int: | 
|  | 55 | +				port = pv | 
|  | 56 | +			} | 
|  | 57 | +		} | 
|  | 58 | +		return libdns.SRV{Name: name, TTL: ttl, Service: service, Transport: transport, Priority: uint16(sr.Priority), Weight: uint16(sr.Weight), Port: uint16(port), Target: sr.Target, ProviderData: sr} | 
|  | 59 | +	case "NS": | 
|  | 60 | +		// Use libdns.NS for nameserver records | 
|  | 61 | +		return libdns.NS{Name: name, TTL: ttl, Target: sr.Nameserver, ProviderData: sr} | 
|  | 62 | +	case "CAA": | 
|  | 63 | +		// Use libdns.CAA as the typed representation | 
|  | 64 | +		// convert stored union fields into a libdns.CAA value | 
|  | 65 | +		flag := 0 | 
|  | 66 | +		if sr.Flag != nil { | 
|  | 67 | +			flag = *sr.Flag | 
|  | 68 | +		} | 
|  | 69 | +		var f8 uint8 | 
|  | 70 | +		if flag < 0 { | 
|  | 71 | +			f8 = 0 | 
|  | 72 | +		} else if flag > 255 { | 
|  | 73 | +			f8 = 255 | 
|  | 74 | +		} else { | 
|  | 75 | +			f8 = uint8(flag) | 
|  | 76 | +		} | 
|  | 77 | +		return libdns.CAA{Name: name, TTL: ttl, Flags: f8, Tag: sr.Tag, Value: sr.Value, ProviderData: sr} | 
|  | 78 | +	case "HTTPS": | 
|  | 79 | +		// Convert to libdns.ServiceBinding with scheme "https" | 
|  | 80 | +		var params libdns.SvcParams | 
|  | 81 | +		if sr.SvcParams != "" { | 
|  | 82 | +			if p, err := libdns.ParseSvcParams(sr.SvcParams); err == nil { | 
|  | 83 | +				params = p | 
|  | 84 | +			} | 
|  | 85 | +		} | 
|  | 86 | +		target := sr.SvcTarget | 
|  | 87 | +		if target == "" { | 
|  | 88 | +			target = sr.TargetName | 
|  | 89 | +		} | 
|  | 90 | +		return libdns.ServiceBinding{ | 
|  | 91 | +			Name:     name, | 
|  | 92 | +			TTL:      ttl, | 
|  | 93 | +			Scheme:   "https", | 
|  | 94 | +			Priority: uint16(sr.SvcPriority), | 
|  | 95 | +			Target:   target, | 
|  | 96 | +			Params:   params, | 
|  | 97 | +			ProviderData: sr, | 
|  | 98 | +		} | 
|  | 99 | +	} | 
|  | 100 | +	// Return nil for unsupported record types (including PTR) - they will be filtered out | 
|  | 101 | +	return nil | 
|  | 102 | +} | 
|  | 103 | + | 
|  | 104 | +// fromLibdnsRR converts a libdns.Record into a spaceshipRecordUnion suitable for create/update | 
|  | 105 | +// Returns nil for unsupported record types | 
|  | 106 | +func (p *Provider) fromLibdnsRR(lr libdns.Record, zone string) *spaceshipRecordUnion { | 
|  | 107 | +	rr := lr.RR() | 
|  | 108 | +	name := rr.Name | 
|  | 109 | +	if name == "" { | 
|  | 110 | +		name = "@" | 
|  | 111 | +	} else { | 
|  | 112 | +		name = libdns.AbsoluteName(rr.Name, zone) | 
|  | 113 | +	} | 
|  | 114 | +	rec := spaceshipRecordUnion{ResourceRecordBase: ResourceRecordBase{Name: name, Type: strings.ToUpper(rr.Type), TTL: int(rr.TTL.Seconds())}} | 
|  | 115 | + | 
|  | 116 | +	// MX handled specially | 
|  | 117 | +	if mx, ok := lr.(libdns.MX); ok { | 
|  | 118 | +		rec.Exchange = mx.Target | 
|  | 119 | +		rec.Preference = int(mx.Preference) | 
|  | 120 | +		return &rec | 
|  | 121 | +	} | 
|  | 122 | + | 
|  | 123 | +	// Handle SRV records | 
|  | 124 | +	if srv, ok := lr.(libdns.SRV); ok { | 
|  | 125 | +		// map libdns.SRV fields into the spaceship payload | 
|  | 126 | +		rec.Service = "_" + strings.TrimPrefix(srv.Service, "_") | 
|  | 127 | +		rec.Protocol = "_" + strings.TrimPrefix(srv.Transport, "_") | 
|  | 128 | +		rec.Priority = int(srv.Priority) | 
|  | 129 | +		rec.Weight = int(srv.Weight) | 
|  | 130 | +		rec.Target = srv.Target | 
|  | 131 | +		rec.PortInt = int(srv.Port) | 
|  | 132 | +		if rec.PortInt != 0 { | 
|  | 133 | +			rec.Port = rec.PortInt | 
|  | 134 | +		} | 
|  | 135 | +		return &rec | 
|  | 136 | +	} | 
|  | 137 | + | 
|  | 138 | +	// Handle NS records | 
|  | 139 | +	if ns, ok := lr.(libdns.NS); ok { | 
|  | 140 | +		rec.Nameserver = ns.Target | 
|  | 141 | +		return &rec | 
|  | 142 | +	} | 
|  | 143 | + | 
|  | 144 | +	// Handle CAA records | 
|  | 145 | +	if caa, ok := lr.(libdns.CAA); ok { | 
|  | 146 | +		tmpFlag := new(int) | 
|  | 147 | +		*tmpFlag = int(caa.Flags) | 
|  | 148 | +		rec.Flag = tmpFlag | 
|  | 149 | +		rec.Tag = caa.Tag | 
|  | 150 | +		rec.Value = caa.Value | 
|  | 151 | +		return &rec | 
|  | 152 | +	} | 
|  | 153 | + | 
|  | 154 | +	// Handle ServiceBinding (HTTPS) records | 
|  | 155 | +	if svc, ok := lr.(libdns.ServiceBinding); ok { | 
|  | 156 | +		// Only handle HTTPS records (ServiceBinding with scheme "https") | 
|  | 157 | +		if strings.ToLower(svc.Scheme) == "https" { | 
|  | 158 | +			rec.Type = "HTTPS" | 
|  | 159 | +			rec.SvcPriority = int(svc.Priority) | 
|  | 160 | +			rec.TargetName = svc.Target  // Use TargetName for API compatibility | 
|  | 161 | +			rec.SvcParams = svc.Params.String() | 
|  | 162 | +			return &rec | 
|  | 163 | +		} | 
|  | 164 | +		// For non-HTTPS ServiceBinding records, return nil (unsupported) | 
|  | 165 | +		return nil | 
|  | 166 | +	} | 
|  | 167 | + | 
|  | 168 | +	switch v := lr.(type) { | 
|  | 169 | +	case libdns.Address: | 
|  | 170 | +		rec.Address = v.IP.String() | 
|  | 171 | +	case libdns.TXT: | 
|  | 172 | +		rec.Value = v.Text | 
|  | 173 | +	case libdns.CNAME: | 
|  | 174 | +		rec.Cname = v.Target | 
|  | 175 | +	case libdns.MX: | 
|  | 176 | +		// already handled | 
|  | 177 | +	default: | 
|  | 178 | +		// Unsupported record type (including libdns.RR) | 
|  | 179 | +		return nil | 
|  | 180 | +	} | 
|  | 181 | +	return &rec | 
|  | 182 | +} | 
|  | 183 | + | 
0 commit comments