mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-09-20 06:46:19 +08:00
PORKBUN: support URL Forward (#3064)
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
parent
dd2030e2cb
commit
04f34cf2e3
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
name: PORKBUN_URLFWD
|
||||
parameters:
|
||||
- name
|
||||
- target
|
||||
- modifiers...
|
||||
provider: PORKBUN
|
||||
parameter_types:
|
||||
name: string
|
||||
target: string
|
||||
"modifiers...": RecordModifier[]
|
||||
---
|
||||
|
||||
`PORKBUN_URLFWD` is a Porkbun-specific feature that maps to Porkbun's URL forwarding feature, which creates HTTP 301 (permanent) or 302 (temporary) redirects.
|
||||
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER),
|
||||
PORKBUN_URLFWD("urlfwd1", "http://example.com"),
|
||||
PORKBUN_URLFWD("urlfwd2", "http://example.org", {type: "permanent", includePath: "yes", wildcard: "no"})
|
||||
);
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
The fields are:
|
||||
* name: the record name
|
||||
* target: where you'd like to forward the domain to
|
||||
* type: valid types are: `temporary` (302 / 307) or `permanent` (301), default to `temporary`
|
||||
* includePath: whether to include the URI path in the redirection. Valid options are `yes` or `no`, default to `no`
|
||||
* wildcard: forward all subdomains of the domain. Valid options are `yes` or `no`, default to `yes`
|
|
@ -764,6 +764,15 @@ func ns1Urlfwd(name, target string) *models.RecordConfig {
|
|||
return makeRec(name, target, "NS1_URLFWD")
|
||||
}
|
||||
|
||||
func porkbunUrlfwd(name, target, t, includePath, wildcard string) *models.RecordConfig {
|
||||
r := makeRec(name, target, "PORKBUN_URLFWD")
|
||||
r.Metadata = make(map[string]string)
|
||||
r.Metadata["type"] = t
|
||||
r.Metadata["includePath"] = includePath
|
||||
r.Metadata["wildcard"] = wildcard
|
||||
return r
|
||||
}
|
||||
|
||||
func clear() *TestCase {
|
||||
return tc("Empty")
|
||||
}
|
||||
|
@ -2280,6 +2289,15 @@ func makeTests() []*TestGroup {
|
|||
ovhdmarc("_dmarc", "v=DMARC1; p=none; rua=mailto:dmarc@example.com")),
|
||||
),
|
||||
|
||||
// PORKBUN features
|
||||
|
||||
testgroup("PORKBUN_URLFWD tests",
|
||||
only("PORKBUN"),
|
||||
tc("Add a urlfwd", porkbunUrlfwd("urlfwd1", "http://example.com", "", "", "")),
|
||||
tc("Update a urlfwd", porkbunUrlfwd("urlfwd1", "http://example.org", "", "", "")),
|
||||
tc("Update a urlfwd with metadata", porkbunUrlfwd("urlfwd1", "http://example.org", "permanent", "no", "no")),
|
||||
),
|
||||
|
||||
// This MUST be the last test.
|
||||
testgroup("final",
|
||||
tc("final", txt("final", `TestDNSProviders was successful!`)),
|
||||
|
|
|
@ -123,7 +123,7 @@ func (dc *DomainConfig) Punycode() error {
|
|||
|
||||
// Set the target:
|
||||
switch rec.Type { // #rtype_variations
|
||||
case "ALIAS", "MX", "NS", "CNAME", "DNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS", "NS1_URLFWD", "AKAMAICDN", "CLOUDNS_WR":
|
||||
case "ALIAS", "MX", "NS", "CNAME", "DNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS", "NS1_URLFWD", "AKAMAICDN", "CLOUDNS_WR", "PORKBUN_URLFWD":
|
||||
// These rtypes are hostnames, therefore need to be converted (unlike, for example, an AAAA record)
|
||||
t, err := idna.ToASCII(rec.GetTargetField())
|
||||
if err != nil {
|
||||
|
|
|
@ -47,6 +47,7 @@ import (
|
|||
// NO_PURGE
|
||||
// NS1_URLFWD
|
||||
// PAGE_RULE
|
||||
// PORKBUN_URLFWD
|
||||
// PURGE
|
||||
// URL
|
||||
// URL301
|
||||
|
|
|
@ -1260,6 +1260,7 @@ var URL301 = recordBuilder('URL301');
|
|||
var FRAME = recordBuilder('FRAME');
|
||||
var NS1_URLFWD = recordBuilder('NS1_URLFWD');
|
||||
var CLOUDNS_WR = recordBuilder('CLOUDNS_WR');
|
||||
var PORKBUN_URLFWD = recordBuilder('PORKBUN_URLFWD');
|
||||
|
||||
// LOC_BUILDER_DD takes an object:
|
||||
// label: The DNS label for the LOC record. (default: '@')
|
||||
|
|
|
@ -36,10 +36,16 @@ type domainRecord struct {
|
|||
Content string `json:"content"`
|
||||
TTL string `json:"ttl"`
|
||||
Prio string `json:"prio"`
|
||||
// Forwarding
|
||||
Subdomain string `json:"subdomain"`
|
||||
Location string `json:"location"`
|
||||
IncludePath string `json:"includePath"`
|
||||
Wildcard string `json:"wildcard"`
|
||||
}
|
||||
|
||||
type recordResponse struct {
|
||||
Records []domainRecord `json:"records"`
|
||||
Records []domainRecord `json:"records"`
|
||||
Forwards []domainRecord `json:"forwards"`
|
||||
}
|
||||
|
||||
type domainListRecord struct {
|
||||
|
@ -146,6 +152,51 @@ func (c *porkbunProvider) getRecords(domain string) ([]domainRecord, error) {
|
|||
return records, nil
|
||||
}
|
||||
|
||||
func (c *porkbunProvider) createUrlForwardingRecord(domain string, rec requestParams) error {
|
||||
if _, err := c.post("/domain/addUrlForward/"+domain, rec); err != nil {
|
||||
return fmt.Errorf("failed create url forwarding record (porkbun): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *porkbunProvider) deleteUrlForwardingRecord(domain string, recordID string) error {
|
||||
params := requestParams{}
|
||||
if _, err := c.post(fmt.Sprintf("/domain/deleteUrlForward/%s/%s", domain, recordID), params); err != nil {
|
||||
return fmt.Errorf("failed delete url forwarding record (porkbun): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *porkbunProvider) modifyUrlForwardingRecord(domain string, recordID string, rec requestParams) error {
|
||||
if err := c.deleteUrlForwardingRecord(domain, recordID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.createUrlForwardingRecord(domain, rec); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *porkbunProvider) getUrlForwardingRecords(domain string) ([]domainRecord, error) {
|
||||
params := requestParams{}
|
||||
var bodyString, err = c.post("/domain/getUrlForwarding/"+domain, params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed fetching url forwarding record list from porkbun: %w", err)
|
||||
}
|
||||
|
||||
var dr recordResponse
|
||||
err = json.Unmarshal(bodyString, &dr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing url forwarding record list from porkbun: %w", err)
|
||||
}
|
||||
|
||||
var records []domainRecord
|
||||
for _, rec := range dr.Forwards {
|
||||
records = append(records, rec)
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (c *porkbunProvider) getNameservers(domain string) ([]string, error) {
|
||||
params := requestParams{}
|
||||
var bodyString, err = c.post(fmt.Sprintf("/domain/getNs/%s", domain), params)
|
||||
|
|
|
@ -11,12 +11,20 @@ import (
|
|||
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
)
|
||||
|
||||
const (
|
||||
minimumTTL = 600
|
||||
)
|
||||
|
||||
const (
|
||||
metaType = "type"
|
||||
metaIncludePath = "includePath"
|
||||
metaWildcard = "wildcard"
|
||||
)
|
||||
|
||||
// https://kb.porkbun.com/article/63-how-to-switch-to-porkbuns-nameservers
|
||||
var defaultNS = []string{
|
||||
"curitiba.ns.porkbun.com",
|
||||
|
@ -78,6 +86,7 @@ func init() {
|
|||
}
|
||||
providers.RegisterDomainServiceProviderType(providerName, fns, features)
|
||||
providers.RegisterMaintainer(providerName, providerMaintainer)
|
||||
providers.RegisterCustomRecordType("PORKBUN_URLFWD", providerName, "")
|
||||
}
|
||||
|
||||
// GetNameservers returns the nameservers for a domain.
|
||||
|
@ -85,6 +94,13 @@ func (c *porkbunProvider) GetNameservers(domain string) ([]*models.Nameserver, e
|
|||
return models.ToNameservers(defaultNS)
|
||||
}
|
||||
|
||||
func genComparable(rec *models.RecordConfig) string {
|
||||
if rec.Type == "PORKBUN_URLFWD" {
|
||||
return fmt.Sprintf("type=%s includePath=%s wildcard=%s", rec.Metadata[metaType], rec.Metadata[metaIncludePath], rec.Metadata[metaWildcard])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
||||
func (c *porkbunProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) {
|
||||
var corrections []*models.Correction
|
||||
|
@ -95,9 +111,24 @@ func (c *porkbunProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
|
|||
// Make sure TTL larger than the minimum TTL
|
||||
for _, record := range dc.Records {
|
||||
record.TTL = fixTTL(record.TTL)
|
||||
if record.Type == "PORKBUN_URLFWD" {
|
||||
record.TTL = 0
|
||||
if record.Metadata == nil {
|
||||
record.Metadata = make(map[string]string)
|
||||
}
|
||||
if record.Metadata[metaType] == "" {
|
||||
record.Metadata[metaType] = "temporary"
|
||||
}
|
||||
if record.Metadata[metaIncludePath] == "" {
|
||||
record.Metadata[metaIncludePath] = "no"
|
||||
}
|
||||
if record.Metadata[metaWildcard] == "" {
|
||||
record.Metadata[metaWildcard] = "yes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changes, err := diff2.ByRecord(existingRecords, dc, nil)
|
||||
changes, err := diff2.ByRecord(existingRecords, dc, genComparable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -114,6 +145,9 @@ func (c *porkbunProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
|
|||
corr = &models.Correction{
|
||||
Msg: change.Msgs[0],
|
||||
F: func() error {
|
||||
if change.New[0].Type == "PORKBUN_URLFWD" {
|
||||
return c.createUrlForwardingRecord(dc.Name, req)
|
||||
}
|
||||
return c.createRecord(dc.Name, req)
|
||||
},
|
||||
}
|
||||
|
@ -126,6 +160,9 @@ func (c *porkbunProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
|
|||
corr = &models.Correction{
|
||||
Msg: fmt.Sprintf("%s, porkbun ID: %s", change.Msgs[0], id),
|
||||
F: func() error {
|
||||
if change.New[0].Type == "PORKBUN_URLFWD" {
|
||||
return c.modifyUrlForwardingRecord(dc.Name, id, req)
|
||||
}
|
||||
return c.modifyRecord(dc.Name, id, req)
|
||||
},
|
||||
}
|
||||
|
@ -134,6 +171,9 @@ func (c *porkbunProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
|
|||
corr = &models.Correction{
|
||||
Msg: fmt.Sprintf("%s, porkbun ID: %s", change.Msgs[0], id),
|
||||
F: func() error {
|
||||
if change.Old[0].Type == "PORKBUN_URLFWD" {
|
||||
return c.deleteUrlForwardingRecord(dc.Name, id)
|
||||
}
|
||||
return c.deleteRecord(dc.Name, id)
|
||||
},
|
||||
}
|
||||
|
@ -152,9 +192,54 @@ func (c *porkbunProvider) GetZoneRecords(domain string, meta map[string]string)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingRecords := make([]*models.RecordConfig, len(records))
|
||||
forwards, err := c.getUrlForwardingRecords(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingRecords := make([]*models.RecordConfig, 0)
|
||||
for i := range records {
|
||||
existingRecords[i] = toRc(domain, &records[i])
|
||||
shouldSkip := false
|
||||
if strings.HasSuffix(records[i].Content, ".porkbun.com") {
|
||||
name := dnsutil.TrimDomainName(records[i].Name, domain)
|
||||
if name == "@" {
|
||||
name = ""
|
||||
}
|
||||
if records[i].Type == "ALIAS" {
|
||||
for _, forward := range forwards {
|
||||
if name == forward.Subdomain {
|
||||
shouldSkip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if records[i].Type == "CNAME" {
|
||||
for _, forward := range forwards {
|
||||
if name == "*."+forward.Subdomain {
|
||||
shouldSkip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldSkip {
|
||||
continue
|
||||
}
|
||||
existingRecords = append(existingRecords, toRc(domain, &records[i]))
|
||||
}
|
||||
for i := range forwards {
|
||||
r := &forwards[i]
|
||||
rc := &models.RecordConfig{
|
||||
Type: "PORKBUN_URLFWD",
|
||||
Original: r,
|
||||
Metadata: map[string]string{
|
||||
metaType: r.Type,
|
||||
metaIncludePath: r.IncludePath,
|
||||
metaWildcard: r.Wildcard,
|
||||
},
|
||||
}
|
||||
rc.SetLabel(r.Subdomain, domain)
|
||||
rc.SetTarget(r.Location)
|
||||
existingRecords = append(existingRecords, rc)
|
||||
}
|
||||
return existingRecords, nil
|
||||
}
|
||||
|
@ -219,6 +304,20 @@ func toRc(domain string, r *domainRecord) *models.RecordConfig {
|
|||
|
||||
// toReq takes a RecordConfig and turns it into the native format used by the API.
|
||||
func toReq(rc *models.RecordConfig) (requestParams, error) {
|
||||
if rc.Type == "PORKBUN_URLFWD" {
|
||||
subdomain := rc.GetLabel()
|
||||
if subdomain == "@" {
|
||||
subdomain = ""
|
||||
}
|
||||
return requestParams{
|
||||
"subdomain": subdomain,
|
||||
"location": rc.GetTargetField(),
|
||||
"type": rc.Metadata[metaType],
|
||||
"includePath": rc.Metadata[metaIncludePath],
|
||||
"wildcard": rc.Metadata[metaWildcard],
|
||||
}, nil
|
||||
}
|
||||
|
||||
req := requestParams{
|
||||
"type": rc.Type,
|
||||
"name": rc.GetLabel(),
|
||||
|
|
Loading…
Reference in a new issue