Skip to content

Commit

Permalink
Switch to more comprehensive logic to handle porkbun default domain r…
Browse files Browse the repository at this point in the history
…ecords.
  • Loading branch information
bentemple committed Aug 17, 2024
1 parent f678492 commit 8e8c50e
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 24 deletions.
6 changes: 4 additions & 2 deletions internal/provider/constants/ip.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package constants

const (
A = "A"
AAAA = "AAAA"
A = "A"
AAAA = "AAAA"
CNAME = "CNAME"
ALIAS = "ALIAS"
)
19 changes: 10 additions & 9 deletions internal/provider/providers/porkbun/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import (
"github.com/qdm12/ddns-updater/internal/provider/errors"
)

// Leaving unused values commented out to improve resilience to API changes.
type dnsRecord struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Content string `json:"content"`
TTL string `json:"ttl"`
Priority string `json:"prio"`
Notes string `json:"notes"`
ID string `json:"id"`
Name string `json:"name"`
//Type string `json:"type"`
Content string `json:"content"`
//TTL string `json:"ttl"`
//Priority string `json:"prio"`
//Notes string `json:"notes"`
}

// See https://porkbun.com/api/json/v3/documentation#DNS%20Retrieve%20Records%20by%20Domain,%20Subdomain%20and%20Type
Expand Down Expand Up @@ -176,15 +177,15 @@ func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
}

// See https://porkbun.com/api/json/v3/documentation#DNS%20Delete%20Records%20by%20Domain,%20Subdomain%20and%20Type
func (p *Provider) deleteAliasRecord(ctx context.Context, client *http.Client) (err error) {
func (p *Provider) deleteRecord(ctx context.Context, client *http.Client, recordType string) (err error) {
var subdomain string
if p.owner != "@" {
subdomain = p.owner
}
u := url.URL{
Scheme: "https",
Host: "porkbun.com",
Path: "/api/json/v3/dns/deleteByNameType/" + p.domain + "/ALIAS/" + subdomain,
Path: "/api/json/v3/dns/deleteByNameType/" + p.domain + "/" + recordType + "/" + subdomain,
}
postRecordsParams := struct {
SecretAPIKey string `json:"secretapikey"`
Expand Down
52 changes: 39 additions & 13 deletions internal/provider/providers/porkbun/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,25 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
}

if len(records) == 0 {
// ALIAS record needs to be deleted to allow creating an A record.
err = p.deleteALIASRecordIfNeeded(ctx, client)
if err != nil {
return netip.Addr{}, fmt.Errorf("deleting ALIAS record if needed: %w", err)
// For new domains, Porkbun Creates 2 Default DNS Records which point to their "parked" domain page:
// ALIAS domain.tld -> pixie.porkbun.com
// CNAME *.domain.tld -> pixie.porkbun.com
// ALIAS and CNAME records conflict with A and AAAA records, and attempting to create an A or AAAA record
// will return a 400 error if they aren't first deleted.
porkbunParkedDomain := "pixie.porkbun.com"
switch {
case p.owner == "@":
// Delete ALIAS domain.tld -> pixie.porkbun.com record
err = p.deleteMatchingRecord(ctx, client, constants.ALIAS, porkbunParkedDomain)
if err != nil {
return netip.Addr{}, err
}
case p.owner == "*":
// Delete CNAME *.domain.tld -> pixie.porkbun.com record
err = p.deleteMatchingRecord(ctx, client, constants.CNAME, porkbunParkedDomain)
if err != nil {
return netip.Addr{}, err
}
}

err = p.createRecord(ctx, client, recordType, ipStr)
Expand All @@ -154,17 +169,28 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
return ip, nil
}

func (p *Provider) deleteALIASRecordIfNeeded(ctx context.Context, client *http.Client) (err error) {
aliasRecordIDs, err := p.getRecords(ctx, client, "ALIAS")
// Deletes the matching record for a specific recordType iff the content matches the expected content value
// Returns an error if there is a matching record but an unexpected content value.
// If there is no matching record, there is nothing to do.
func (p *Provider) deleteMatchingRecord(
ctx context.Context,
client *http.Client,
recordType string,
expectedContent string,
) error {
records, err := p.getRecords(ctx, client, recordType)
if err != nil {
return fmt.Errorf("getting ALIAS record IDs: %w", err)
} else if len(aliasRecordIDs) == 0 {
return nil
return fmt.Errorf("getting %s records: %w", recordType, err)
}

err = p.deleteAliasRecord(ctx, client)
if err != nil {
return fmt.Errorf("deleting ALIAS record: %w", err)
if len(records) == 1 && records[0].Content == expectedContent {
err = p.deleteRecord(ctx, client, recordType)
if err != nil {
return fmt.Errorf("deleting %s record: %w", recordType, err)
}
} else if len(records) >= 1 {
// We have 1 or more matching records, but the expectedContent didn't match. Throw a conflicting record error.
return fmt.Errorf("deleting %s record: %w", recordType, errors.ErrConflictingRecord)
}
// No record found for recordType, nothing to do.
return nil
}

0 comments on commit 8e8c50e

Please sign in to comment.