mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW PROVIDER: HETZNER DNS Console (#904)
* HETZNER: implement the provider for Hetzner DNS Console Signed-off-by: Jakob Ackermann <das7pad@outlook.com> * HETZNER: apply review feedback - add domain into error messages - insert sub-strings using `%q` - insert sub-errors using `%w` - change api.getZone() signature to return a (potentially `nil`) Zone pointer instead of a (potentially empty) Zone value - sort imports and confirm with `$ goimports -w providers/hetzner/` - use exact 'api_key' term in error message of settings validation - add blank line for logic separation - drop internal record id from correction messages Co-Authored-By: Tom Limoncelli <tlimoncelli@stackoverflow.com> Signed-off-by: Jakob Ackermann <das7pad@outlook.com> * HETZNER: add request rate-limiting handling There are a limited number of data-points on how their rate-limiting works at this time. I deduce from my account to others and use a fixed/ constant backoff of 1s as the initial delay. Thereafter exponential increase with factor 2 (not needed at this time). Hetzner has not made any official statements on rate-limiting, so this is guesswork only. Signed-off-by: Jakob Ackermann <das7pad@outlook.com> * HETZNER: address golint complaints - baseUrl -> baseURL - mark Record as private -> record - mark Zone as private -> zone - mark RequestRateLimiter as private -> requestRateLimiter - capitalize Id fields as ID - keep delay logic on same level, move return out of branch Signed-off-by: Jakob Ackermann <das7pad@outlook.com> * HETZNER: rate_limited: init the response timestamp on requestRateLimiter Signed-off-by: Jakob Ackermann <das7pad@outlook.com> * HETZNER: requestRateLimiter: align local variable with struct name Signed-off-by: Jakob Ackermann <das7pad@outlook.com> Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
173
providers/hetzner/hetznerProvider.go
Normal file
173
providers/hetzner/hetznerProvider.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package hetzner
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||
)
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.DocCreateDomains: providers.Can(),
|
||||
providers.DocDualHost: providers.Can(),
|
||||
providers.DocOfficiallySupported: providers.Cannot(),
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Cannot(),
|
||||
providers.CanUseCAA: providers.Can(),
|
||||
providers.CanUseDS: providers.Cannot(),
|
||||
providers.CanUsePTR: providers.Cannot(),
|
||||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseSSHFP: providers.Cannot(),
|
||||
providers.CanUseTLSA: providers.Cannot(),
|
||||
providers.CanUseTXTMulti: providers.Cannot(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
providers.RegisterDomainServiceProviderType("HETZNER", New, features)
|
||||
}
|
||||
|
||||
// New creates a new API handle.
|
||||
func New(settings map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
if settings["api_key"] == "" {
|
||||
return nil, fmt.Errorf("missing HETZNER api_key")
|
||||
}
|
||||
|
||||
api := &api{}
|
||||
|
||||
api.apiKey = settings["api_key"]
|
||||
|
||||
if settings["rate_limited"] == "true" {
|
||||
api.startRateLimited()
|
||||
}
|
||||
|
||||
return api, nil
|
||||
}
|
||||
|
||||
// EnsureDomainExists creates the domain if it does not exist.
|
||||
func (api *api) EnsureDomainExists(domain string) error {
|
||||
domains, err := api.ListZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range domains {
|
||||
if d == domain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return api.createZone(domain)
|
||||
}
|
||||
|
||||
// GetDomainCorrections returns the corrections for a domain.
|
||||
func (api *api) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
dc, err := dc.Copy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dc.Punycode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain := dc.Name
|
||||
|
||||
// Get existing records
|
||||
existingRecords, err := api.GetZoneRecords(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Normalize
|
||||
models.PostProcessRecords(existingRecords)
|
||||
|
||||
differ := diff.New(dc)
|
||||
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var corrections []*models.Correction
|
||||
|
||||
zone, err := api.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range del {
|
||||
record := m.Existing.Original.(*record)
|
||||
corr := &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
return api.deleteRecord(*record)
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
|
||||
for _, m := range create {
|
||||
record := fromRecordConfig(m.Desired, zone)
|
||||
corr := &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
return api.createRecord(*record)
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
|
||||
for _, m := range modify {
|
||||
id := m.Existing.Original.(*record).ID
|
||||
record := fromRecordConfig(m.Desired, zone)
|
||||
record.ID = id
|
||||
corr := &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
return api.updateRecord(*record)
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
// GetNameservers returns the nameservers for a domain.
|
||||
func (api *api) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
zone, err := api.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameserver := make([]*models.Nameserver, len(zone.NameServers))
|
||||
for i := range zone.NameServers {
|
||||
nameserver[i] = &models.Nameserver{Name: zone.NameServers[i]}
|
||||
}
|
||||
return nameserver, nil
|
||||
}
|
||||
|
||||
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
||||
func (api *api) GetZoneRecords(domain string) (models.Records, error) {
|
||||
records, err := api.getAllRecords(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingRecords := make([]*models.RecordConfig, len(records))
|
||||
for i := range records {
|
||||
existingRecords[i] = toRecordConfig(domain, &records[i])
|
||||
}
|
||||
return existingRecords, nil
|
||||
}
|
||||
|
||||
// ListZones lists the zones on this account.
|
||||
func (api *api) ListZones() ([]string, error) {
|
||||
if err := api.getAllZones(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var zones []string
|
||||
for i := range api.zones {
|
||||
zones = append(zones, i)
|
||||
}
|
||||
return zones, nil
|
||||
}
|
Reference in New Issue
Block a user