Skip to content

Commit

Permalink
Make linter happy
Browse files Browse the repository at this point in the history
  • Loading branch information
amroessam committed Jan 11, 2025
1 parent b4c51d7 commit addbfed
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 65 deletions.
55 changes: 6 additions & 49 deletions internal/provider/providers/spaceship/createrecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,23 @@ import (
"fmt"
"net/http"
"net/url"

"github.com/qdm12/ddns-updater/internal/provider/errors"
)

func (p *Provider) createRecord(ctx context.Context, client *http.Client,
recordType, address string) error {

func (p *Provider) createRecord(ctx context.Context, client *http.Client, recordType, address string) error {
const defaultTTL = 3600

u := url.URL{
Scheme: "https",
Host: "spaceship.dev",
Path: fmt.Sprintf("/api/v1/dns/records/%s", p.domain),
Path: "/api/v1/dns/records/" + p.domain,
}

createData := struct {
Force bool `json:"force"`
Items []struct {
Type string `json:"type"`
Name string `json:"name"`
Address string `json:"address"`
TTL uint32 `json:"ttl"`
} `json:"items"`
Force bool `json:"force"`
Items []Record `json:"items"`
}{
Force: true,
Items: []struct {
Type string `json:"type"`
Name string `json:"name"`
Address string `json:"address"`
TTL uint32 `json:"ttl"`
}{{
Items: []Record{{
Type: recordType,
Name: p.owner,
Address: address,
Expand All @@ -62,36 +48,7 @@ func (p *Provider) createRecord(ctx context.Context, client *http.Client,
defer response.Body.Close()

if response.StatusCode != http.StatusNoContent {
var apiError APIError
if err := json.NewDecoder(response.Body).Decode(&apiError); err != nil {
return fmt.Errorf("%w: %d", errors.ErrHTTPStatusNotValid, response.StatusCode)
}

switch response.StatusCode {
case http.StatusUnauthorized:
return fmt.Errorf("%w: invalid API credentials", errors.ErrAuth)
case http.StatusNotFound:
if apiError.Detail == "SOA record for domain "+p.domain+" not found." {
return fmt.Errorf("%w: domain %s must be configured in Spaceship first",
errors.ErrDomainNotFound, p.domain)
}
return fmt.Errorf("%w: %s", errors.ErrDomainNotFound, apiError.Detail)
case http.StatusBadRequest:
var details string
for _, d := range apiError.Data {
if d.Field != "" {
details += fmt.Sprintf(" %s: %s;", d.Field, d.Details)
} else {
details += fmt.Sprintf(" %s;", d.Details)
}
}
return fmt.Errorf("%w:%s", errors.ErrBadRequest, details)
case http.StatusTooManyRequests:
return fmt.Errorf("%w: rate limit exceeded", errors.ErrRateLimit)
default:
return fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode, apiError.Detail)
}
return p.handleAPIError(response)
}

return nil
Expand Down
9 changes: 2 additions & 7 deletions internal/provider/providers/spaceship/deleterecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ import (
"fmt"
"net/http"
"net/url"

"github.com/qdm12/ddns-updater/internal/provider/errors"
"github.com/qdm12/ddns-updater/internal/provider/utils"
)

func (p *Provider) deleteRecord(ctx context.Context, client *http.Client, record Record) error {
u := url.URL{
Scheme: "https",
Host: "spaceship.dev",
Path: fmt.Sprintf("/api/v1/dns/records/%s", p.domain),
Path: "/api/v1/dns/records/" + p.domain,
}

deleteData := []Record{record}
Expand All @@ -39,9 +36,7 @@ func (p *Provider) deleteRecord(ctx context.Context, client *http.Client, record
defer response.Body.Close()

if response.StatusCode != http.StatusNoContent {
return fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode,
utils.BodyToSingleLine(response.Body))
return p.handleAPIError(response)
}

return nil
Expand Down
5 changes: 2 additions & 3 deletions internal/provider/providers/spaceship/getrecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import (
"github.com/qdm12/ddns-updater/internal/provider/errors"
)

func (p *Provider) getRecords(ctx context.Context, client *http.Client) (
records []Record, err error) {
func (p *Provider) getRecords(ctx context.Context, client *http.Client) (records []Record, err error) {
u := url.URL{
Scheme: "https",
Host: "spaceship.dev",
Path: fmt.Sprintf("/api/v1/dns/records/%s", p.domain),
Path: "/api/v1/dns/records/" + p.domain,
}

values := url.Values{}
Expand Down
65 changes: 61 additions & 4 deletions internal/provider/providers/spaceship/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"net/http"
"net/netip"

"github.com/qdm12/ddns-updater/internal/provider/constants"
"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/provider/constants"
"github.com/qdm12/ddns-updater/internal/provider/errors"
"github.com/qdm12/ddns-updater/internal/provider/headers"
"github.com/qdm12/ddns-updater/internal/provider/utils"
Expand Down Expand Up @@ -96,9 +96,12 @@ func (p *Provider) BuildDomainName() string {

func (p *Provider) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
Owner: p.Owner(),
Provider: fmt.Sprintf("<a href=\"https://www.spaceship.com/application/advanced-dns-application/manage/%s\">Spaceship</a>", p.domain),
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
Owner: p.Owner(),
Provider: fmt.Sprintf(
"<a href=\"https://www.spaceship.com/application/advanced-dns-application/manage/%s\">Spaceship</a>",
p.domain,
),
IPVersion: p.ipVersion.String(),
}
}
Expand All @@ -110,3 +113,57 @@ func (p *Provider) setHeaders(request *http.Request) {
request.Header.Set("X-Api-Key", p.apiKey)
request.Header.Set("X-Api-Secret", p.apiSecret)
}

func (p *Provider) handleAPIError(response *http.Response) error {
var apiError APIError
if err := json.NewDecoder(response.Body).Decode(&apiError); err != nil {
return fmt.Errorf("%w: %d", errors.ErrHTTPStatusNotValid, response.StatusCode)
}

// Extract error code from header if present
errorCode := response.Header.Get("spaceship-error-code")

switch response.StatusCode {
case http.StatusUnauthorized:
return fmt.Errorf("%w: invalid API credentials", errors.ErrAuth)
case http.StatusForbidden:
return fmt.Errorf("%w: missing required permission dnsRecords:write", errors.ErrAuth)
case http.StatusNotFound:
if apiError.Detail == "SOA record for domain "+p.domain+" not found." {
return fmt.Errorf("%w: domain %s must be configured in Spaceship first",
errors.ErrDomainNotFound, p.domain)
}
return fmt.Errorf("%w: %s", errors.ErrRecordResourceSetNotFound, apiError.Detail)
case http.StatusBadRequest:
var details string
for _, d := range apiError.Data {
if d.Field != "" {
details += fmt.Sprintf(" %s: %s;", d.Field, d.Details)
} else {
details += fmt.Sprintf(" %s;", d.Details)
}
}
// Add error code if present
if errorCode != "" {
details = fmt.Sprintf(" (code: %s)%s", errorCode, details)
}
return fmt.Errorf("%w:%s", errors.ErrBadRequest, details)
case http.StatusTooManyRequests:
// Rate limit is 300 requests within 300 seconds per user and domain
return fmt.Errorf("%w: rate limit exceeded (300 requests/300 seconds)", errors.ErrRateLimit)
case http.StatusInternalServerError:
if errorCode != "" {
return fmt.Errorf("%w: internal server error (code: %s): %s",
errors.ErrHTTPStatusNotValid, errorCode, apiError.Detail)
}
return fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode, apiError.Detail)
default:
if errorCode != "" {
return fmt.Errorf("%w: %d (code: %s): %s",
errors.ErrHTTPStatusNotValid, response.StatusCode, errorCode, apiError.Detail)
}
return fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode, apiError.Detail)
}
}
5 changes: 3 additions & 2 deletions internal/provider/providers/spaceship/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package spaceship

// APIError represents the Spaceship API error response
// APIError represents the Spaceship API error response.
type APIError struct {
Detail string `json:"detail"`
Data []struct {
Expand All @@ -9,9 +9,10 @@ type APIError struct {
} `json:"data"`
}

// Record represents a DNS record
// Record represents a DNS record.
type Record struct {
Type string `json:"type"`
Name string `json:"name"`
Address string `json:"address"`
TTL uint32 `json:"ttl,omitempty"`
}
1 change: 1 addition & 0 deletions internal/provider/providers/spaceship/updaterecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add

var existingRecord Record

// Check exact matches for both type and name
for _, record := range records {
if record.Type == recordType && record.Name == p.owner {
existingRecord = record
Expand Down

0 comments on commit addbfed

Please sign in to comment.