1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00
Files
stackexchange-dnscontrol/providers/hostingde/api.go
Benjamin Altpeter 2cfd2f403b Hosting.de: Allow using as registrar only (#1307)
Previously, the provider would always try to fetch the zone config for
the domain. But that doesn't work if the domain's DNS is not managed
through Hosting.de.

With this patch, getDomainConfig() instead filters directly on the
domain name instead of re-fetching it from the zone.

Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
2021-11-27 13:44:28 -05:00

269 lines
5.6 KiB
Go

package hostingde
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
"golang.org/x/net/idna"
)
const endpoint = "%s/api/%s/v1/json/%s"
type hostingdeProvider struct {
authToken string
ownerAccountID string
baseURL string
}
func (hp *hostingdeProvider) getDomainConfig(domain string) (*domainConfig, error) {
params := request{
Filter: filter{
Field: "domainName",
Value: domain,
},
}
resp, err := hp.get("domain", "domainsFind", params)
if err != nil {
return nil, fmt.Errorf("error getting domain info: %w", err)
}
domainConf := []*domainConfig{}
if err := json.Unmarshal(resp.Data, &domainConf); err != nil {
return nil, fmt.Errorf("error parsing response: %w", err)
}
if len(domainConf) == 0 {
return nil, fmt.Errorf("could not get domain config: %s", domain)
}
return domainConf[0], nil
}
func (hp *hostingdeProvider) createZone(domain string) error {
t, err := idna.ToASCII(domain)
if err != nil {
return err
}
records := []*record{}
for _, ns := range defaultNameservers {
records = append(records, &record{
Name: domain,
Type: "NS",
Content: ns,
TTL: 86400,
})
}
params := request{
ZoneConfig: &zoneConfig{
Name: t,
Type: "NATIVE",
},
Records: records,
}
_, err = hp.get("dns", "zoneCreate", params)
if err != nil {
return fmt.Errorf("error creating zone: %w", err)
}
return nil
}
func (hp *hostingdeProvider) getNameservers(domain string) ([]string, error) {
t, err := idna.ToASCII(domain)
if err != nil {
return nil, err
}
domainConf, err := hp.getDomainConfig(t)
if err != nil {
return nil, fmt.Errorf("error getting domain config: %w", err)
}
nss := []string{}
for _, ns := range domainConf.Nameservers {
// Currently does not support glued IP addresses
if len(ns.IPs) > 0 {
return nil, fmt.Errorf("domain %s has glued IP addresses which are not supported", domain)
}
nss = append(nss, ns.Name)
}
return nss, nil
}
func (hp *hostingdeProvider) updateNameservers(nss []string, domain string) func() error {
return func() error {
domainConf, err := hp.getDomainConfig(domain)
if err != nil {
return err
}
nameservers := []nameserver{}
for _, ns := range nss {
nameservers = append(nameservers, nameserver{Name: ns})
}
domainConf.Nameservers = nameservers
params := request{
Domain: domainConf,
}
if _, err := hp.get("domain", "domainUpdate", params); err != nil {
return err
}
return nil
}
}
func (hp *hostingdeProvider) getRecords(domain string) ([]*record, error) {
zc, err := hp.getZoneConfig(domain)
if err != nil {
return nil, err
}
records := []*record{}
page := uint(1)
for {
params := request{
Filter: filter{
Field: "ZoneConfigId",
Value: zc.ID,
},
Limit: 1000,
Page: page,
}
resp, err := hp.get("dns", "recordsFind", params)
if err != nil {
return nil, err
}
newRecords := []*record{}
if err := json.Unmarshal(resp.Data, &newRecords); err != nil {
return nil, err
}
records = append(records, newRecords...)
if page >= resp.TotalPages {
break
}
page++
}
return records, nil
}
func (hp *hostingdeProvider) updateRecords(domain string, create, del, mod diff.Changeset) error {
zc, err := hp.getZoneConfig(domain)
if err != nil {
return err
}
toAdd := []*record{}
for _, c := range create {
r := recordToNative(c.Desired)
toAdd = append(toAdd, r)
}
toDelete := []*record{}
for _, d := range del {
r := recordToNative(d.Existing)
r.ID = d.Existing.Original.(*record).ID
toDelete = append(toDelete, r)
}
toModify := []*record{}
for _, m := range mod {
r := recordToNative(m.Desired)
r.ID = m.Existing.Original.(*record).ID
toModify = append(toModify, r)
}
params := request{
ZoneConfig: zc,
RecordsToAdd: toAdd,
RecordsToDelete: toDelete,
RecordsToModify: toModify,
}
_, err = hp.get("dns", "zoneUpdate", params)
if err != nil {
return err
}
return nil
}
func (hp *hostingdeProvider) getZoneConfig(domain string) (*zoneConfig, error) {
t, err := idna.ToASCII(domain)
if err != nil {
return nil, err
}
params := request{
Filter: filter{
Field: "ZoneName",
Value: t,
},
}
resp, err := hp.get("dns", "zoneConfigsFind", params)
if err != nil {
return nil, fmt.Errorf("could not get zone config: %w", err)
}
zc := []*zoneConfig{}
if err := json.Unmarshal(resp.Data, &zc); err != nil {
return nil, fmt.Errorf("could not parse response: %w", err)
}
if len(zc) == 0 {
return nil, errZoneNotFound
}
return zc[0], nil
}
func (hp *hostingdeProvider) get(service, method string, params request) (*responseData, error) {
params.AuthToken = hp.authToken
params.OwnerAccountID = hp.ownerAccountID
reqBody, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("could not marshal request body: %w", err)
}
url := fmt.Sprintf(endpoint, hp.baseURL, service, method)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(reqBody))
if err != nil {
return nil, fmt.Errorf("could not carry out request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("error occurred: %s", resp.Status)
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response body: %w", err)
}
respData := &response{}
if err := json.Unmarshal(bodyBytes, &respData); err != nil {
return nil, fmt.Errorf("could not unmarshal response body: %w", err)
}
if len(respData.Errors) > 0 && respData.Status == "error" {
return nil, fmt.Errorf("%+v", respData.Errors)
}
return respData.Response, nil
}