From a9ee623d12375d64a93b63fcef6114fa43c5e0a5 Mon Sep 17 00:00:00 2001 From: davidramiro Date: Sat, 30 Nov 2024 15:05:55 +0100 Subject: [PATCH] feat: allow omitting subdomain --- README.md | 2 ++ internal/api/api.go | 19 +++++----- services/cloudflare.go | 79 ++++++++++++++++++++++++++++++++---------- services/gandi.go | 4 +++ services/porkbun.go | 17 ++++++--- 5 files changed, 90 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 446b499..27f9496 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,10 @@ Also available as a HomeAssistant addon. - e.g. `yourdomain.com` - Replace `{SUBDOMAIN}` with your subdomain or comma separated subdomains - e.g. `subdomain` or `sudomain1,subdomain2` + - If you just want to use the base domain without subdomain, remove the `&subdomain={SUBDOMAIN}` parameter. - Enter the full domain in the `Domain Name` field - e.g. `subdomain.domain.com` (if you use multiple subdomains, just choose any of those) + - or `domain.com` if no subdomain parameter given - Enter the registrar name in the `Username` field, either `gandi`, `cloudflare` or `porkbun` - Enter any value in the `Password` field - Unused, but required by the FritzBox interface diff --git a/internal/api/api.go b/internal/api/api.go index 4202504..522677b 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -52,15 +52,12 @@ func (u *UpdateApi) HandleUpdateRequest(c echo.Context) error { subdomains := strings.Split(request.Subdomains, ",") - count := len(subdomains) - - if count == 0 || subdomains[0] == "" { - logger.Error().Err(ErrMissingParameter).Msg(ErrMissingParameter.Error()) - return c.String(http.StatusBadRequest, ErrMissingParameter.Error()) + if subdomains == nil { + subdomains = []string{""} } for i := range subdomains { - logger.Debug().Msgf("handling subdomain %d of %d", i+1, len(subdomains)) + logger.Debug().Msgf("handling request %d of %d", i+1, len(subdomains)) service, err := u.dnsServiceFactory.Find(services.Registrar(request.Registrar)) if err != nil { @@ -82,9 +79,15 @@ func (u *UpdateApi) HandleUpdateRequest(c echo.Context) error { } - logger.Info().Int("updates", count).Msg("successfully created") + logger.Info().Int("updates", len(subdomains)).Msg("successfully created") - return c.String(http.StatusOK, fmt.Sprintf("created %d entries for subdomains %s on %s: %s", count, request.Subdomains, request.Domain, request.IP)) + return c.String(http.StatusOK, + fmt.Sprintf("created %d entries for subdomains %s on %s: %s", + len(subdomains), + request.Subdomains, + request.Domain, + request.IP), + ) } func (u *UpdateApi) HandleStatusCheck(c echo.Context) error { diff --git a/services/cloudflare.go b/services/cloudflare.go index 5ad4301..e641400 100644 --- a/services/cloudflare.go +++ b/services/cloudflare.go @@ -46,10 +46,10 @@ func NewCloudflareDnsUpdateService(client HTTPClient) (*CloudflareDnsUpdateServi } type CloudflareApiRequest struct { - Subdomain string `json:"name"` - Type string `json:"type"` - TTL int `json:"ttl"` - IP string `json:"content"` + Name string `json:"name"` + Type string `json:"type"` + TTL int `json:"ttl"` + IP string `json:"content"` } type CloudflareQueryResponse struct { @@ -67,10 +67,20 @@ func (c *CloudflareDnsUpdateService) UpdateRecord(request *DynDnsRequest) error endpoint := fmt.Sprintf("%s/zones/%s/dns_records", c.baseUrl, c.zoneId) - logger := log.With().Str("func", "UpdateRecord").Str("registrar", "cloudflare").Str("endpoint", endpoint).Str("domain", request.Domain).Str("subdomain", request.Subdomain).Logger() + logger := log.With(). + Str("func", "UpdateRecord"). + Str("registrar", "cloudflare"). + Str("endpoint", endpoint). + Str("domain", request.Domain). + Str("subdomain", request.Subdomain).Logger() + logger.Debug().Msg("building update request") req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + logger.Error().Err(err).Msg(ErrBuildingRequest.Error()) + return ErrBuildingRequest + } var r CloudflareQueryResponse @@ -83,9 +93,13 @@ func (c *CloudflareDnsUpdateService) UpdateRecord(request *DynDnsRequest) error return ErrBuildingRequest } - b, _ := io.ReadAll(resp.Body) - err = json.Unmarshal(b, &r) + b, err := io.ReadAll(resp.Body) + if err != nil { + logger.Error().Err(err).Msg(ErrParsingResponse.Error()) + return err + } + err = json.Unmarshal(b, &r) if err != nil { logger.Error().Err(err).Msg(ErrParsingResponse.Error()) return err @@ -101,7 +115,14 @@ func (c *CloudflareDnsUpdateService) UpdateRecord(request *DynDnsRequest) error if len(r.Errors) == 0 && len(r.Result) > 0 { logger.Debug().Int("entries", len(r.Result)).Msg("comparing entries with update request") for _, e := range r.Result { - if e.Name == fmt.Sprintf("%s.%s", request.Subdomain, request.Domain) { + var match string + if request.Subdomain == "" { + match = request.Domain + } else { + match = fmt.Sprintf("%s.%s", request.Subdomain, request.Domain) + } + + if e.Name == match { id = e.Id } } @@ -117,17 +138,32 @@ func (c *CloudflareDnsUpdateService) UpdateRecord(request *DynDnsRequest) error } func (c *CloudflareDnsUpdateService) newRecord(request *DynDnsRequest) error { + + var name string + if request.Subdomain != "" { + name = fmt.Sprintf("%s.%s", request.Subdomain, request.Domain) + } else { + name = request.Domain + } + cloudflareRequest := &CloudflareApiRequest{ - Subdomain: fmt.Sprintf("%s.%s", request.Subdomain, request.Domain), - IP: request.IP, - TTL: c.ttl, - Type: "A", + Name: name, + IP: request.IP, + TTL: c.ttl, + Type: "A", } endpoint := fmt.Sprintf("%s/zones/%s/dns_records", c.baseUrl, c.zoneId) - logger := log.With().Str("func", "newRecord").Str("registrar", "cloudflare").Str("subdomain", cloudflareRequest.Subdomain).Str("endpoint", endpoint).Str("IP", cloudflareRequest.IP).Logger() + logger := log.With(). + Str("func", "newRecord"). + Str("registrar", "cloudflare"). + Str("fqdn", cloudflareRequest.Name). + Str("endpoint", endpoint). + Str("IP", cloudflareRequest.IP). + Logger() + logger.Debug().Msg("building new record request") body, err := json.Marshal(cloudflareRequest) @@ -164,17 +200,24 @@ func (c *CloudflareDnsUpdateService) newRecord(request *DynDnsRequest) error { } func (c *CloudflareDnsUpdateService) editExistingRecord(request *DynDnsRequest, id string) error { + var name string + if request.Subdomain != "" { + name = fmt.Sprintf("%s.%s", request.Subdomain, request.Domain) + } else { + name = request.Domain + } + cloudflareRequest := &CloudflareApiRequest{ - Subdomain: fmt.Sprintf("%s.%s", request.Subdomain, request.Domain), - IP: request.IP, - TTL: c.ttl, - Type: "A", + Name: name, + IP: request.IP, + TTL: c.ttl, + Type: "A", } endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", c.baseUrl, c.zoneId, id) - logger := log.With().Str("func", "editExistingRecord").Str("registrar", "cloudflare").Str("subdomain", cloudflareRequest.Subdomain).Str("endpoint", endpoint).Str("IP", cloudflareRequest.IP).Logger() + logger := log.With().Str("func", "editExistingRecord").Str("registrar", "cloudflare").Str("subdomain", cloudflareRequest.Name).Str("endpoint", endpoint).Str("IP", cloudflareRequest.IP).Logger() logger.Info().Msg("building request to edit record") body, err := json.Marshal(cloudflareRequest) diff --git a/services/gandi.go b/services/gandi.go index 0873e61..c0bc79a 100644 --- a/services/gandi.go +++ b/services/gandi.go @@ -50,6 +50,10 @@ type GandiApiRequest struct { func (g *GandiDnsUpdateService) UpdateRecord(request *DynDnsRequest) error { + if request.Subdomain == "" { + request.Subdomain = "@" + } + gandiRequest := &GandiApiRequest{ Subdomain: request.Subdomain, IPValues: []string{request.IP}, diff --git a/services/porkbun.go b/services/porkbun.go index 640f9d7..8280a06 100644 --- a/services/porkbun.go +++ b/services/porkbun.go @@ -45,7 +45,7 @@ func NewPorkbunDnsUpdateService(client HTTPClient) (*PorkbunDnsUpdateService, er } type PorkbunApiRequest struct { - Subdomain string `json:"name"` + Name string `json:"name"` Type string `json:"type"` TTL int `json:"ttl"` IP string `json:"content"` @@ -61,9 +61,8 @@ type PorkbunQueryResponse struct { } func (p *PorkbunDnsUpdateService) UpdateRecord(request *DynDnsRequest) error { - porkbunRequest := &PorkbunApiRequest{ - Subdomain: request.Subdomain, + Name: request.Subdomain, IP: request.IP, TTL: p.ttl, Type: "A", @@ -128,7 +127,15 @@ func (p *PorkbunDnsUpdateService) queryRecordExists(request *DynDnsRequest, pork if r.Status == "SUCCESS" && len(r.Records) > 0 { for _, e := range r.Records { logger.Info().Msg("record found") - if e.Name == fmt.Sprintf("%s.%s", request.Subdomain, request.Domain) { + + var match string + if request.Subdomain == "" { + match = request.Domain + } else { + match = fmt.Sprintf("%s.%s", request.Subdomain, request.Domain) + } + + if e.Name == match { return true, nil } } @@ -179,7 +186,7 @@ func (p *PorkbunDnsUpdateService) updateRecord(request *DynDnsRequest, porkbunRe } func (p *PorkbunDnsUpdateService) executeRequest(endpoint string, porkbunRequest *PorkbunApiRequest) (*http.Response, error) { - logger := log.With().Str("func", "executeRequest").Str("registrar", "porkbun").Str("endpoint", endpoint).Str("subdomain", porkbunRequest.Subdomain).Logger() + logger := log.With().Str("func", "executeRequest").Str("registrar", "porkbun").Str("endpoint", endpoint).Str("subdomain", porkbunRequest.Name).Logger() logger.Info().Msg("building update request") body, err := json.Marshal(porkbunRequest)