2020-10-22 21:44:21 +08:00
|
|
|
package hetzner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2020-11-02 08:30:16 +08:00
|
|
|
"strings"
|
2020-10-22 21:44:21 +08:00
|
|
|
|
2023-05-21 01:21:45 +08:00
|
|
|
"github.com/StackExchange/dnscontrol/v4/models"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/providers"
|
2020-10-22 21:44:21 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
var features = providers.DocumentationNotes{
|
2023-04-04 05:26:05 +08:00
|
|
|
providers.CanAutoDNSSEC: providers.Cannot(),
|
2020-10-22 21:44:21 +08:00
|
|
|
providers.CanGetZones: providers.Can(),
|
|
|
|
providers.CanUseAlias: providers.Cannot(),
|
|
|
|
providers.CanUseCAA: providers.Can(),
|
2023-04-04 05:26:05 +08:00
|
|
|
providers.CanUseDS: providers.Can(),
|
|
|
|
providers.CanUseDSForChildren: providers.Cannot(),
|
2023-03-17 02:04:20 +08:00
|
|
|
providers.CanUseLOC: providers.Cannot(),
|
2023-04-04 05:26:05 +08:00
|
|
|
providers.CanUseNAPTR: providers.Cannot(),
|
2020-10-22 21:44:21 +08:00
|
|
|
providers.CanUsePTR: providers.Cannot(),
|
2023-04-04 05:26:05 +08:00
|
|
|
providers.CanUseSOA: providers.Cannot(),
|
2020-10-22 21:44:21 +08:00
|
|
|
providers.CanUseSRV: providers.Can(),
|
|
|
|
providers.CanUseSSHFP: providers.Cannot(),
|
2023-03-15 21:44:18 +08:00
|
|
|
providers.CanUseTLSA: providers.Can(),
|
2022-03-03 00:19:15 +08:00
|
|
|
providers.DocCreateDomains: providers.Can(),
|
|
|
|
providers.DocDualHost: providers.Can(),
|
|
|
|
providers.DocOfficiallySupported: providers.Cannot(),
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2021-03-08 02:19:22 +08:00
|
|
|
fns := providers.DspFuncs{
|
2021-05-05 02:15:31 +08:00
|
|
|
Initializer: New,
|
2021-03-09 09:14:30 +08:00
|
|
|
RecordAuditor: AuditRecords,
|
2021-03-08 02:19:22 +08:00
|
|
|
}
|
|
|
|
providers.RegisterDomainServiceProviderType("HETZNER", fns, features)
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new API handle.
|
|
|
|
func New(settings map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
2023-04-04 05:26:05 +08:00
|
|
|
apiKey := settings["api_key"]
|
|
|
|
if apiKey == "" {
|
2020-10-22 21:44:21 +08:00
|
|
|
return nil, fmt.Errorf("missing HETZNER api_key")
|
|
|
|
}
|
|
|
|
|
2023-04-04 05:26:05 +08:00
|
|
|
return &hetznerProvider{
|
|
|
|
apiKey: apiKey,
|
|
|
|
}, nil
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
|
2023-02-07 20:22:49 +08:00
|
|
|
// EnsureZoneExists creates a zone if it does not exist
|
|
|
|
func (api *hetznerProvider) EnsureZoneExists(domain string) error {
|
2020-10-22 21:44:21 +08:00
|
|
|
domains, err := api.ListZones()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range domains {
|
|
|
|
if d == domain {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-02 02:44:17 +08:00
|
|
|
// reset zone cache
|
|
|
|
api.zones = nil
|
2020-10-22 21:44:21 +08:00
|
|
|
return api.createZone(domain)
|
|
|
|
}
|
|
|
|
|
2023-04-15 03:22:23 +08:00
|
|
|
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
|
|
|
func (api *hetznerProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) {
|
2020-10-22 21:44:21 +08:00
|
|
|
domain := dc.Name
|
|
|
|
|
2021-03-08 02:19:22 +08:00
|
|
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
2020-10-22 21:44:21 +08:00
|
|
|
|
2023-10-23 01:56:13 +08:00
|
|
|
toReport, create, del, modify, err := diff.NewCompat(dc).IncrementalDiff(existingRecords)
|
2023-01-02 21:27:33 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-23 01:56:13 +08:00
|
|
|
// Start corrections with the reports
|
|
|
|
corrections := diff.GenerateMessageCorrections(toReport)
|
2020-10-22 21:44:21 +08:00
|
|
|
|
2023-04-04 05:26:05 +08:00
|
|
|
z, err := api.getZone(domain)
|
2023-01-02 21:27:33 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-22 21:44:21 +08:00
|
|
|
|
2023-01-02 21:27:33 +08:00
|
|
|
for _, m := range del {
|
2023-04-04 05:26:05 +08:00
|
|
|
r := m.Existing.Original.(*record)
|
2023-01-02 21:27:33 +08:00
|
|
|
corr := &models.Correction{
|
|
|
|
Msg: m.String(),
|
|
|
|
F: func() error {
|
2023-04-04 05:26:05 +08:00
|
|
|
return api.deleteRecord(r)
|
2023-01-02 21:27:33 +08:00
|
|
|
},
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
2023-01-02 21:27:33 +08:00
|
|
|
corrections = append(corrections, corr)
|
|
|
|
}
|
2020-10-22 21:44:21 +08:00
|
|
|
|
2023-04-04 05:26:05 +08:00
|
|
|
createRecords := make([]record, len(create))
|
|
|
|
createDescription := make([]string, len(create)+1)
|
|
|
|
createDescription[0] = "Batch creation of records:"
|
|
|
|
for i, m := range create {
|
|
|
|
createRecords[i] = fromRecordConfig(m.Desired, z)
|
|
|
|
createDescription[i+1] = m.String()
|
2023-01-02 21:27:33 +08:00
|
|
|
}
|
|
|
|
if len(createRecords) > 0 {
|
|
|
|
corr := &models.Correction{
|
|
|
|
Msg: strings.Join(createDescription, "\n\t"),
|
|
|
|
F: func() error {
|
|
|
|
return api.bulkCreateRecords(createRecords)
|
|
|
|
},
|
2022-12-12 04:02:58 +08:00
|
|
|
}
|
2023-01-02 21:27:33 +08:00
|
|
|
corrections = append(corrections, corr)
|
|
|
|
}
|
2022-12-12 04:02:58 +08:00
|
|
|
|
2023-04-04 05:26:05 +08:00
|
|
|
modifyRecords := make([]record, len(modify))
|
|
|
|
modifyDescription := make([]string, len(modify)+1)
|
|
|
|
modifyDescription[0] = "Batch modification of records:"
|
|
|
|
for i, m := range modify {
|
|
|
|
r := fromRecordConfig(m.Desired, z)
|
|
|
|
r.ID = m.Existing.Original.(*record).ID
|
|
|
|
modifyRecords[i] = r
|
|
|
|
modifyDescription[i+1] = m.String()
|
2023-01-02 21:27:33 +08:00
|
|
|
}
|
|
|
|
if len(modifyRecords) > 0 {
|
|
|
|
corr := &models.Correction{
|
|
|
|
Msg: strings.Join(modifyDescription, "\n\t"),
|
|
|
|
F: func() error {
|
|
|
|
return api.bulkUpdateRecords(modifyRecords)
|
|
|
|
},
|
2022-12-12 04:02:58 +08:00
|
|
|
}
|
2023-01-02 21:27:33 +08:00
|
|
|
corrections = append(corrections, corr)
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return corrections, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNameservers returns the nameservers for a domain.
|
2020-10-26 21:25:30 +08:00
|
|
|
func (api *hetznerProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
2023-04-04 05:26:05 +08:00
|
|
|
z, err := api.getZone(domain)
|
2020-10-22 21:44:21 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-02 06:10:25 +08:00
|
|
|
|
|
|
|
return models.ToNameserversStripTD(z.NameServers)
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
2023-05-03 01:04:59 +08:00
|
|
|
func (api *hetznerProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
|
2020-10-22 21:44:21 +08:00
|
|
|
records, err := api.getAllRecords(domain)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
existingRecords := make([]*models.RecordConfig, len(records))
|
|
|
|
for i := range records {
|
2023-04-04 05:26:05 +08:00
|
|
|
existingRecords[i], err = toRecordConfig(domain, &records[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
return existingRecords, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListZones lists the zones on this account.
|
2020-10-26 21:25:30 +08:00
|
|
|
func (api *hetznerProvider) ListZones() ([]string, error) {
|
2020-10-22 21:44:21 +08:00
|
|
|
if err := api.getAllZones(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-04-04 05:26:05 +08:00
|
|
|
zones := make([]string, 0, len(api.zones))
|
|
|
|
for domain := range api.zones {
|
|
|
|
zones = append(zones, domain)
|
2020-10-22 21:44:21 +08:00
|
|
|
}
|
|
|
|
return zones, nil
|
|
|
|
}
|