Skip to content

Feat: add servercow provider #224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- OpenDNS
- OVH
- Selfhost.de
- Servercow.de
- Spdyn
- Strato.de
- Variomedia.de
Expand Down Expand Up @@ -172,6 +173,7 @@ Check the documentation for your DNS provider:
- [OpenDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/opendns.md)
- [OVH](https://github.com/qdm12/ddns-updater/blob/master/docs/ovh.md)
- [Selfhost.de](https://github.com/qdm12/ddns-updater/blob/master/docs/selfhost.de.md)
- [Servercow.de](https://github.com/qdm12/ddns-updater/blob/master/docs/servercow.md)
- [Spdyn](https://github.com/qdm12/ddns-updater/blob/master/docs/spdyn.md)
- [Strato.de](https://github.com/qdm12/ddns-updater/blob/master/docs/strato.md)
- [Variomedia.de](https://github.com/qdm12/ddns-updater/blob/master/docs/variomedia.md)
Expand Down
37 changes: 37 additions & 0 deletions docs/servercow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Servercow

## Configuration

### Example

```json
{
"settings": [
{
"provider": "servercow",
"domain": "domain.com",
"host": "",
"username": "servercow_username",
"password": "servercow_password",
"ttl": 600,
"ip_version": "ipv4"
}
]
}
```

### Compulsury parameters

- `"domain"`
- `"host"` is your host and can be `""`, a subdomain or `"*"` generally
- `"username"` is the username for your DNS API User
- `"password"` is the password for your DNS API User

### Optional parameters

- `"ttl"` can be set to an integer value for record TTL in seconds (if not set the default is 120)
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), and defaults to `ipv4 or ipv6`

## Domain setup

See [their article](https://cp.servercow.de/en/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/)
1 change: 1 addition & 0 deletions internal/settings/constants/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
OpenDNS models.Provider = "opendns"
OVH models.Provider = "ovh"
SelfhostDe models.Provider = "selfhost.de"
Servercow models.Provider = "servercow"
Spdyn models.Provider = "spdyn"
Strato models.Provider = "strato"
Variomedia models.Provider = "variomedia"
Expand Down
8 changes: 8 additions & 0 deletions internal/settings/headers/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ func SetOauth(request *http.Request, value string) {
func SetXFilter(request *http.Request, value string) {
request.Header.Set("X-Filter", value)
}

func SetXAuthUsername(request *http.Request, value string){
request.Header.Set("X-Auth-Username", value)
}

func SetXAuthPassword(request *http.Request, value string){
request.Header.Set("X-Auth-Password", value)
}
179 changes: 179 additions & 0 deletions internal/settings/providers/servercow/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package servercow

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"

"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/settings/constants"
"github.com/qdm12/ddns-updater/internal/settings/errors"
"github.com/qdm12/ddns-updater/internal/settings/headers"
"github.com/qdm12/ddns-updater/internal/settings/log"
"github.com/qdm12/ddns-updater/internal/settings/utils"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
)

type provider struct {
username string
host string
domain string
ipVersion ipversion.IPVersion
password string
useProviderIP bool
ttl uint
logger log.Logger
}

func New(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion, logger log.Logger) (p *provider, err error) {
extraSettings := struct {
Username string `json:"username"`
Password string `json:"password"`
Domain string `json:"domain"`
TTL uint `json:"ttl"`
UseProviderIP bool `json:"provider_ip"`
}{}
if err := json.Unmarshal(data, &extraSettings); err != nil {
return nil, err
}

p = &provider{
host: host,
ipVersion: ipVersion,
username: extraSettings.Username,
password: extraSettings.Password,
useProviderIP: extraSettings.UseProviderIP,
domain: extraSettings.Domain,
logger: logger,
ttl: extraSettings.TTL,
}
if err := p.isValid(); err != nil {
return nil, err
}
return p, nil
}

func (p *provider) isValid() error {
switch {
case p.username == "":
return errors.ErrEmptyUsername
case p.password == "":
return errors.ErrEmptyPassword
}
if strings.Contains(p.host, "*") {
return errors.ErrHostWildcard
}
return nil
}

func (p *provider) String() string {
return utils.ToString("servercow.de", p.host, constants.Servercow, p.ipVersion)
}

func (p *provider) Domain() string {
return p.domain
}

func (p *provider) Host() string {
return p.host
}

func (p *provider) IPVersion() ipversion.IPVersion {
return p.ipVersion
}

func (p *provider) Proxied() bool {
return false
}

func (p *provider) BuildDomainName() string {
return utils.BuildDomainName(p.host, p.domain)
}

func (p *provider) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName())),
Host: models.HTML(p.Host()),
Provider: "<a href=\"https://servercow.de\">Servercow</a>",
IPVersion: models.HTML(p.ipVersion.String()),
}
}

func (p *provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
recordType := constants.A
if ip.To4() == nil {
recordType = constants.AAAA
}
u := url.URL{
Scheme: "https",
Host: "api.servercow.de",
Path: "/dns/v1/domains/" + p.domain,
}

updateHost := p.host
if updateHost == "@" {
updateHost = ""
}

requestData := struct {
Type string `json:"type"` // constants.A or constants.AAAA depending on ip address given
Name string `json:"name"` // DNS record name (only the subdomain part)
Content string `json:"content"` // ip address
TTL uint `json:"ttl"`
}{
Type: recordType,
Name: updateHost,
Content: ip.String(),
TTL: p.ttl,
}

buffer := bytes.NewBuffer(nil)
encoder := json.NewEncoder(buffer)
if err := encoder.Encode(requestData); err != nil {
return nil, fmt.Errorf("%w: %s", errors.ErrRequestEncode, err)
}

p.logger.Debug("HTTP POST: " + u.String() + ": " + buffer.String())

request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
if err != nil {
return nil, err
}
headers.SetContentType(request, "application/json")
headers.SetXAuthUsername(request, p.username)
headers.SetXAuthPassword(request, p.password)
headers.SetUserAgent(request)

response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()

if response.StatusCode > http.StatusUnsupportedMediaType {
return nil, fmt.Errorf("%w: %d: %s",
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
}

decoder := json.NewDecoder(response.Body)

var parsedJSON struct {
Message string `json:"message"`
Error string `json:"error"`
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍


if err := decoder.Decode(&parsedJSON); err != nil {
return nil, fmt.Errorf("%w: %s", errors.ErrUnmarshalResponse, err)
}

if parsedJSON.Message != "ok" {
return nil, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, parsedJSON.Error)
}

return ip, nil
}
3 changes: 3 additions & 0 deletions internal/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/qdm12/ddns-updater/internal/settings/providers/opendns"
"github.com/qdm12/ddns-updater/internal/settings/providers/ovh"
"github.com/qdm12/ddns-updater/internal/settings/providers/selfhostde"
"github.com/qdm12/ddns-updater/internal/settings/providers/servercow"
"github.com/qdm12/ddns-updater/internal/settings/providers/spdyn"
"github.com/qdm12/ddns-updater/internal/settings/providers/strato"
"github.com/qdm12/ddns-updater/internal/settings/providers/variomedia"
Expand Down Expand Up @@ -110,6 +111,8 @@ func New(provider models.Provider, data json.RawMessage, domain, host string,
return ovh.New(data, domain, host, ipVersion, logger)
case constants.SelfhostDe:
return selfhostde.New(data, domain, host, ipVersion, logger)
case constants.Servercow:
return servercow.New(data, domain, host, ipVersion, logger)
case constants.Spdyn:
return spdyn.New(data, domain, host, ipVersion, logger)
case constants.Strato:
Expand Down