-
-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
485 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Ionos | ||
|
||
## Configuration | ||
|
||
### Example | ||
|
||
```json | ||
{ | ||
"settings": [ | ||
{ | ||
"provider": "ionos", | ||
"domain": "domain.com", | ||
"host": "@", | ||
"api_key": "api_key", | ||
"ip_version": "ipv4" | ||
} | ||
] | ||
} | ||
``` | ||
|
||
### Compulsory parameters | ||
|
||
- `"domain"` | ||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"` | ||
- `"api_key"` is your API key, obtained from [creating an API key](https://www.ionos.com/help/domains/configuring-your-ip-address/set-up-dynamic-dns-with-company-name/#c181598) | ||
|
||
### Optional parameters | ||
|
||
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package ionos | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/qdm12/ddns-updater/internal/provider/headers" | ||
) | ||
|
||
type apiZone struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
} | ||
|
||
type apiRecord struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
RootName string `json:"rootName"` | ||
Type string `json:"type"` | ||
Content string `json:"content"` | ||
TTL uint32 `json:"ttl"` | ||
Prio uint32 `json:"prio"` | ||
Disabled bool `json:"disabled"` | ||
} | ||
|
||
func (p *Provider) setHeaders(request *http.Request) { | ||
headers.SetUserAgent(request) | ||
headers.SetAccept(request, "application/json") | ||
headers.SetXAPIKey(request, p.apiKey) | ||
switch request.Method { | ||
case http.MethodPost, http.MethodPut: | ||
headers.SetContentType(request, "application/json") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package ionos | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/netip" | ||
"net/url" | ||
|
||
"github.com/qdm12/ddns-updater/internal/provider/constants" | ||
"github.com/qdm12/ddns-updater/internal/provider/errors" | ||
) | ||
|
||
func (p *Provider) createRecord(ctx context.Context, client *http.Client, | ||
zoneID string, ip netip.Addr) (err error) { | ||
recordType := constants.A | ||
if ip.Is6() { | ||
recordType = constants.AAAA | ||
} | ||
|
||
u := url.URL{ | ||
Scheme: "https", | ||
Host: "api.hosting.ionos.com", | ||
Path: "/dns/v1/zones/" + zoneID + "/records", | ||
} | ||
|
||
const defaultTTL = 3600 | ||
const defaultPrio = 0 | ||
recordsList := []apiRecord{ | ||
{ | ||
Name: p.BuildDomainName(), | ||
Type: recordType, | ||
Content: ip.String(), | ||
TTL: defaultTTL, | ||
Prio: defaultPrio, | ||
Disabled: false, | ||
}, | ||
} | ||
|
||
buffer := bytes.NewBuffer(nil) | ||
encoder := json.NewEncoder(buffer) | ||
err = encoder.Encode(recordsList) | ||
if err != nil { | ||
return fmt.Errorf("encoding request data to JSON: %w", err) | ||
} | ||
|
||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer) | ||
if err != nil { | ||
return fmt.Errorf("creating http request: %w", err) | ||
} | ||
p.setHeaders(request) | ||
|
||
response, err := client.Do(request) | ||
if err != nil { | ||
return fmt.Errorf("doing http request: %w", err) | ||
} | ||
defer response.Body.Close() | ||
|
||
switch response.StatusCode { | ||
case http.StatusCreated: | ||
err = response.Body.Close() | ||
if err != nil { | ||
return fmt.Errorf("closing response body: %w", err) | ||
} | ||
return nil | ||
case http.StatusBadRequest: | ||
return fmt.Errorf("%w: %s", errors.ErrBadRequest, | ||
decodeErrorMessage(response.Body)) | ||
case http.StatusUnauthorized: | ||
return fmt.Errorf("%w: %s", errors.ErrAuth, | ||
decodeErrorMessage(response.Body)) | ||
default: | ||
return fmt.Errorf("%w: %s: %s", errors.ErrHTTPStatusNotValid, | ||
response.Status, decodeErrorMessage(response.Body)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package ionos | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/qdm12/ddns-updater/internal/provider/errors" | ||
) | ||
|
||
func (p *Provider) getZones(ctx context.Context, client *http.Client) ( | ||
zones []apiZone, err error) { | ||
err = p.get(ctx, client, "/zones", nil, &zones) | ||
return zones, err | ||
} | ||
|
||
func (p *Provider) getRecords(ctx context.Context, client *http.Client, | ||
zoneID string, recordType string) (records []apiRecord, err error) { | ||
queryParams := url.Values{ | ||
"recordName": []string{p.BuildDomainName()}, | ||
"recordType": []string{recordType}, | ||
} | ||
var responseData struct { | ||
Records []apiRecord `json:"records"` | ||
} | ||
err = p.get(ctx, client, "/zones/"+zoneID, queryParams, &responseData) | ||
if err != nil { | ||
return nil, fmt.Errorf("for zone id %s and type %s: %w", | ||
zoneID, recordType, err) | ||
} | ||
|
||
return responseData.Records, nil | ||
} | ||
|
||
func (p *Provider) get(ctx context.Context, client *http.Client, | ||
subPath string, queryParams url.Values, responseJSONData any) (err error) { | ||
u := url.URL{ | ||
Scheme: "https", | ||
Host: "api.hosting.ionos.com", | ||
Path: filepath.Join("/dns/v1/", subPath), | ||
RawQuery: queryParams.Encode(), | ||
} | ||
|
||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) | ||
if err != nil { | ||
return fmt.Errorf("creating http request: %w", err) | ||
} | ||
p.setHeaders(request) | ||
|
||
response, err := client.Do(request) | ||
if err != nil { | ||
return fmt.Errorf("doing http request: %w", err) | ||
} | ||
defer response.Body.Close() | ||
|
||
switch response.StatusCode { | ||
case http.StatusOK: | ||
case http.StatusUnauthorized: | ||
return fmt.Errorf("%w: %s", errors.ErrAuth, | ||
decodeErrorMessage(response.Body)) | ||
default: | ||
return fmt.Errorf("%w: %s: %s", errors.ErrHTTPStatusNotValid, | ||
response.Status, decodeErrorMessage(response.Body)) | ||
} | ||
|
||
decoder := json.NewDecoder(response.Body) | ||
err = decoder.Decode(&responseJSONData) | ||
if err != nil { | ||
return fmt.Errorf("decoding JSON response: %w", err) | ||
} | ||
|
||
err = response.Body.Close() | ||
if err != nil { | ||
return fmt.Errorf("closing response body: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func decodeErrorMessage(body io.Reader) (message string) { | ||
b, err := io.ReadAll(body) | ||
if err != nil { | ||
return fmt.Sprintf("failed reading response body: %s", err) | ||
} | ||
|
||
if len(b) == 0 { | ||
return "" | ||
} | ||
|
||
var data []struct { | ||
Code string `json:"code"` | ||
Message string `json:"message"` | ||
} | ||
err = json.Unmarshal(b, &data) | ||
if err != nil { | ||
return "failed decoding the following: " + string(b) | ||
} | ||
if len(data) == 0 { | ||
return "no message found" | ||
} | ||
|
||
messages := make([]string, len(data)) | ||
for i, object := range data { | ||
if object.Message != "" { | ||
messages[i] = object.Message | ||
continue | ||
} | ||
messages[i] = fmt.Sprintf("code %q", object.Code) | ||
} | ||
return strings.Join(messages, "; ") | ||
} |
Oops, something went wrong.