2020-10-22 21:44:21 +08:00
|
|
|
package hetzner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/StackExchange/dnscontrol/v3/models"
|
2021-03-08 02:19:22 +08:00
|
|
|
"strings"
|
2020-10-22 21:44:21 +08:00
|
|
|
)
|
|
|
|
|
2020-11-02 08:30:16 +08:00
|
|
|
type bulkCreateRecordsRequest struct {
|
|
|
|
Records []record `json:"records"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type bulkUpdateRecordsRequest struct {
|
|
|
|
Records []record `json:"records"`
|
|
|
|
}
|
|
|
|
|
2020-10-22 21:44:21 +08:00
|
|
|
type createRecordRequest struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
TTL int `json:"ttl"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Value string `json:"value"`
|
|
|
|
ZoneID string `json:"zone_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type createZoneRequest struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type getAllRecordsResponse struct {
|
|
|
|
Records []record `json:"records"`
|
|
|
|
Meta struct {
|
|
|
|
Pagination struct {
|
|
|
|
LastPage int `json:"last_page"`
|
|
|
|
} `json:"pagination"`
|
|
|
|
} `json:"meta"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type getAllZonesResponse struct {
|
|
|
|
Zones []zone `json:"zones"`
|
|
|
|
Meta struct {
|
|
|
|
Pagination struct {
|
|
|
|
LastPage int `json:"last_page"`
|
|
|
|
} `json:"pagination"`
|
|
|
|
} `json:"meta"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type record struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
TTL *int `json:"ttl"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Value string `json:"value"`
|
|
|
|
ZoneID string `json:"zone_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type zone struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
NameServers []string `json:"ns"`
|
|
|
|
TTL int `json:"ttl"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func fromRecordConfig(in *models.RecordConfig, zone *zone) *record {
|
|
|
|
ttl := int(in.TTL)
|
|
|
|
record := &record{
|
|
|
|
Name: in.GetLabel(),
|
|
|
|
Type: in.Type,
|
2021-03-08 02:19:22 +08:00
|
|
|
Value: in.GetTargetCombined(),
|
2020-10-22 21:44:21 +08:00
|
|
|
TTL: &ttl,
|
|
|
|
ZoneID: zone.ID,
|
|
|
|
}
|
|
|
|
|
2021-03-08 02:19:22 +08:00
|
|
|
if record.Type == "TXT" && len(in.TxtStrings) == 1 {
|
|
|
|
// HACK: HETZNER rejects values that fit into 255 bytes w/o quotes,
|
|
|
|
// but do not fit w/ added quotes (via GetTargetCombined()).
|
|
|
|
// Sending the raw, non-quoted value works for the comprehensive
|
|
|
|
// suite of integrations tests.
|
|
|
|
// The HETZNER validation does not provide helpful error messages.
|
2020-10-22 21:44:21 +08:00
|
|
|
// {"error":{"message":"422 Unprocessable Entity: missing: ; ","code":422}}
|
2021-03-08 02:19:22 +08:00
|
|
|
valueNotQuoted := in.TxtStrings[0]
|
|
|
|
if len(valueNotQuoted) == 254 || len(valueNotQuoted) == 255 {
|
|
|
|
record.Value = valueNotQuoted
|
|
|
|
}
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return record
|
|
|
|
}
|
|
|
|
|
|
|
|
func toRecordConfig(domain string, record *record) *models.RecordConfig {
|
|
|
|
rc := &models.RecordConfig{
|
|
|
|
Type: record.Type,
|
|
|
|
TTL: uint32(*record.TTL),
|
|
|
|
Original: record,
|
|
|
|
}
|
|
|
|
rc.SetLabel(record.Name, domain)
|
|
|
|
|
2021-03-08 02:19:22 +08:00
|
|
|
value := record.Value
|
|
|
|
// HACK: Hetzner is inserting a trailing space after multiple, quoted values.
|
|
|
|
// NOTE: The actual DNS answer does not contain the space.
|
|
|
|
if record.Type == "TXT" && len(value) > 0 && value[len(value)-1] == ' ' {
|
|
|
|
// Per RFC 1035 spaces outside quoted values are irrelevant.
|
|
|
|
value = strings.TrimRight(value, " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = rc.PopulateFromString(record.Type, value, domain)
|
2020-10-22 21:44:21 +08:00
|
|
|
|
|
|
|
return rc
|
|
|
|
}
|