package ionosAPI import ( "bytes" "encoding/json" "errors" "io" "net" "net/http" "net/url" "realdnydns/model/common" ) /** * Docs: https://developer.hosting.ionos.com/docs/dns */ type IonosAPI interface { GetARecord(tld string, subdomain string) (*common.ARecord, error) SetARecord(tld string, subdomain string, ip net.IP, ttl int, prio int, disabled bool) (*common.ARecord, error) GetZoneId(tld string) (string, error) GetRecordId(zoneId string, tld string, subdomain string, recordType string) (string, error) HttpCall(method string, url string, body io.Reader, queryParams map[string]string) (*http.Response, error) } type IonosAPIImpl struct { APIKey string BaseURL string HTTPClient *http.Client } type ZonesResponse []struct { Name string `json:"name"` Id string `json:"id"` Type string `json:"type"` } type ZoneResponse struct { Id string `json:"id"` Name string `json:"name"` Type string `json:"type"` Records []RecordResponse `json:"records"` } type RecordResponse struct { Id string `json:"id"` Name string `json:"name"` RootName string `json:"rootName"` Type string `json:"type"` Content string `json:"content"` ChangeDate string `json:"changeDate"` TTL int `json:"ttl"` Prio int `json:"prio"` Disabled bool `json:"disabled"` } type ChangeRecord struct { Name string `json:"name"` Type string `json:"type"` Content string `json:"content"` TTL int `json:"ttl"` Prio int `json:"prio"` Disabled bool `json:"disabled"` } type ChangeRecordRequest struct { Content string `json:"content"` TTL int `json:"ttl"` Prio int `json:"prio"` Disabled bool `json:"disabled"` } func New(APIKey string, BaseURL string) IonosAPI { return &IonosAPIImpl{ APIKey: APIKey, BaseURL: BaseURL, HTTPClient: &http.Client{}, } } func (i *IonosAPIImpl) HttpCall(method string, path string, body io.Reader, queryParams map[string]string) (*http.Response, error) { requestUrl, _ := url.Parse(i.BaseURL + path) query := requestUrl.Query() for key, value := range queryParams { query.Add(key, value) } requestUrl.RawQuery = query.Encode() req, err := http.NewRequest(method, requestUrl.String(), body) if err != nil { return nil, err } req.Header.Add("X-API-Key", i.APIKey) req.Header.Add("Content-Type", "application/json") req.Header.Add("Accept", "application/json") return i.HTTPClient.Do(req) } func (i *IonosAPIImpl) GetZoneId(tld string) (string, error) { res, err := i.HttpCall("GET", "/v1/zones", nil, nil) if err != nil { return "", err } responseBody := make([]byte, res.ContentLength) res.Body.Read(responseBody) zones := []ZoneResponse{} json.Unmarshal(responseBody, &zones) for _, z := range zones { if z.Name == tld { return z.Id, nil } } return "", errors.New("zone not found") } func (i *IonosAPIImpl) GetRecordId(zoneId string, tld string, subdomain string, recordType string) (string, error) { var domain string if subdomain == "@" || subdomain == "" { domain = tld } else { domain = subdomain + "." + tld } res, err := i.HttpCall("GET", "/v1/zones/"+zoneId, nil, map[string]string{"recordName": domain, "recordType": recordType}) if err != nil { return "", err } responseBody := make([]byte, res.ContentLength) res.Body.Read(responseBody) zone := ZoneResponse{} json.Unmarshal(responseBody, &zone) for _, record := range zone.Records { if record.Type == recordType && record.Name == domain { return record.Id, nil } } return "", errors.New("record not found") } func (i *IonosAPIImpl) SetARecord(tld string, subdomain string, ip net.IP, ttl int, prio int, disabled bool) (*common.ARecord, error) { zoneId, err := i.GetZoneId(tld) if err != nil { return nil, err } recordId, err := i.GetRecordId(zoneId, tld, subdomain, "A") if err != nil { return nil, err } changeRecordRequest, err := json.Marshal(ChangeRecordRequest{ Content: ip.String(), TTL: ttl, Prio: prio, Disabled: false, }) if err != nil { return nil, err } res, err := i.HttpCall("PUT", "/v1/zones/"+zoneId+"/records/"+recordId, bytes.NewReader(changeRecordRequest), nil) if err != nil { return nil, err } responseBody := make([]byte, res.ContentLength) res.Body.Read(responseBody) if res.StatusCode != 200 { return nil, errors.New("error updating record") } changeRecord := ChangeRecord{} json.Unmarshal(responseBody, &changeRecord) return &common.ARecord{ Domain: changeRecord.Name, IP: changeRecord.Content, TTL: changeRecord.TTL, Prio: changeRecord.Prio, Disabled: changeRecord.Disabled, }, nil } func (ionos *IonosAPIImpl) GetARecord(tld string, subdomain string) (*common.ARecord, error) { zoneId, err := ionos.GetZoneId(tld) if err != nil { return nil, err } recordId, err := ionos.GetRecordId(zoneId, tld, subdomain, "A") if err != nil { return nil, err } res, err := ionos.HttpCall("GET", "/v1/zones/"+zoneId+"/records/"+recordId, nil, nil) if err != nil { return nil, err } responseBody := make([]byte, res.ContentLength) res.Body.Read(responseBody) record := RecordResponse{} json.Unmarshal(responseBody, &record) return &common.ARecord{ Domain: record.Name, IP: record.Content, TTL: record.TTL, Prio: record.Prio, Disabled: record.Disabled, }, nil }