mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW PROVIDER: Netlify (#1820)
Signed-off-by: Amogh Lele <amolele@gmail.com>
This commit is contained in:
1
OWNERS
1
OWNERS
@ -28,6 +28,7 @@ providers/linode @koesie10
|
||||
providers/namecheap @captncraig
|
||||
# providers/namedotcom NEEDS VOLUNTEER
|
||||
providers/netcup @kordianbruck
|
||||
providers/netlify @SphericalKat
|
||||
providers/ns1 @costasd
|
||||
providers/opensrs @philhug
|
||||
providers/oracle @kallsyms
|
||||
|
@ -47,6 +47,7 @@ Currently supported DNS providers:
|
||||
- Name.com
|
||||
- Namecheap
|
||||
- Netcup
|
||||
- Netlify
|
||||
- OVH
|
||||
- OctoDNS
|
||||
- Oracle Cloud
|
||||
|
@ -35,6 +35,7 @@
|
||||
<th class="rotate"><div><span>NAMECHEAP</span></div></th>
|
||||
<th class="rotate"><div><span>NAMEDOTCOM</span></div></th>
|
||||
<th class="rotate"><div><span>NETCUP</span></div></th>
|
||||
<th class="rotate"><div><span>NETLIFY</span></div></th>
|
||||
<th class="rotate"><div><span>NS1</span></div></th>
|
||||
<th class="rotate"><div><span>OCTODNS</span></div></th>
|
||||
<th class="rotate"><div><span>OPENSRS</span></div></th>
|
||||
@ -281,6 +282,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -413,6 +417,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -522,6 +529,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
@ -603,6 +613,9 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -714,6 +727,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
@ -823,6 +839,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -913,6 +932,9 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -992,6 +1014,7 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
|
||||
@ -1083,6 +1106,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
@ -1183,6 +1209,9 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
@ -1287,6 +1316,9 @@
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
@ -1527,6 +1559,9 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1681,6 +1716,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Netlify does not allow sufficient control over the apex NS records">
|
||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1805,6 +1843,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -2066,6 +2107,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="info">
|
||||
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
|
||||
</td>
|
||||
|
47
docs/_providers/netlify.md
Normal file
47
docs/_providers/netlify.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
name: Netlify
|
||||
title: Netlify Provider
|
||||
layout: default
|
||||
jsId: NETLIFY
|
||||
---
|
||||
# Netlify Provider
|
||||
## Configuration
|
||||
|
||||
To use this provider, add an entry to `creds.json` with `TYPE` set to `NETLIFY`
|
||||
along with a Netlify account personal access token. You can also optionally add an
|
||||
account slug. This is _typically_ your username on Netlify.
|
||||
|
||||
Examples:
|
||||
|
||||
```json
|
||||
{
|
||||
"netlify": {
|
||||
"TYPE": "NETLIFY",
|
||||
"token": "your-netlify-account-access-token",
|
||||
"slug": "account-slug" // this is optional
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Metadata
|
||||
This provider does not recognize any special metadata fields unique to Netlify.
|
||||
|
||||
## Usage
|
||||
An example `dnsconfig.js` configuration:
|
||||
|
||||
```js
|
||||
var REG_NETLIFY = NewRegistrar("netlify");
|
||||
var DSP_NETLIFY = NewDnsProvider("netlify");
|
||||
|
||||
D("example.tld", REG_NETLIFY, DnsProvider(DSP_NETLIFY),
|
||||
A("test", "1.2.3.4")
|
||||
);
|
||||
```
|
||||
|
||||
## Activation
|
||||
DNSControl depends on a Netlify account personal access token.
|
||||
|
||||
## Caveats
|
||||
Empty MX records are not supported.
|
||||
|
||||
|
@ -95,6 +95,7 @@ Providers in this category and their maintainers are:
|
||||
* `LINODE` @koesie10
|
||||
* `NAMECHEAP` VOLUNTEER NEEDED
|
||||
* `NETCUP` @kordianbruck
|
||||
* `NETLIFY` @SphericalKat
|
||||
* `NS1` @costasd
|
||||
* `OCTODNS` @TomOnTime
|
||||
* `OPENSRS` @pierre-emmanuelJ
|
||||
|
@ -215,5 +215,10 @@
|
||||
"token": "$DOMAINNAMESHOP_TOKEN",
|
||||
"secret": "$DOMAINNAMESHOP_SECRET",
|
||||
"domain": "$DOMAINNAMESHOP_DOMAIN"
|
||||
},
|
||||
"NETLIFY": {
|
||||
"token": "$NETLIFY_TOKEN",
|
||||
"slug": "$NETLIFY_ACCOUNT_SLUG",
|
||||
"domain": "$NETLIFY_DOMAIN"
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/namecheap"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/namedotcom"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/netcup"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/netlify"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/ns1"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/octodns"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/opensrs"
|
||||
|
169
providers/netlify/api.go
Normal file
169
providers/netlify/api.go
Normal file
@ -0,0 +1,169 @@
|
||||
package netlify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const baseURL = "https://api.netlify.com/api/v1"
|
||||
|
||||
type dnsRecord struct {
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
Priority int64 `json:"priority,omitempty"`
|
||||
Flag int64 `json:"flag,omitempty"`
|
||||
Weight uint16 `json:"weight,omitempty"`
|
||||
Port uint16 `json:"port,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
SiteID string `json:"site_id,omitempty"`
|
||||
DNSZoneID string `json:"dns_zone_id,omitempty"`
|
||||
Managed bool `json:"managed,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type dnsZone struct {
|
||||
AccountID string `json:"account_id,omitempty"`
|
||||
AccountName string `json:"account_name,omitempty"`
|
||||
AccountSlug string `json:"account_slug,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
Dedicated bool `json:"dedicated,omitempty"`
|
||||
DNSServers []string `json:"dns_servers"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Errors []string `json:"errors"`
|
||||
ID string `json:"id,omitempty"`
|
||||
IPV6Enabled bool `json:"ipv6_enabled,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Records []*dnsRecord `json:"records"`
|
||||
SiteID string `json:"site_id,omitempty"`
|
||||
SupportedRecordTypes []string `json:"supported_record_types"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
type dnsRecordCreate struct {
|
||||
Flag int64 `json:"flag"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Port int64 `json:"port,omitempty"`
|
||||
Priority int64 `json:"priority,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Weight int64 `json:"weight"`
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) getDNSZones() ([]*dnsZone, error) {
|
||||
reqURL := fmt.Sprintf("%s/dns_zones", baseURL)
|
||||
|
||||
req, err := http.NewRequest("GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.apiToken))
|
||||
|
||||
if n.accountSlug != "" {
|
||||
q := req.URL.Query()
|
||||
q.Add("account_slug", n.accountSlug)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
dnsZones := make([]*dnsZone, 0)
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(&dnsZones)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dnsZones, nil
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) getDNSRecords(zoneID string) ([]*dnsRecord, error) {
|
||||
reqURL := fmt.Sprintf("%s/dns_zones/%s/dns_records", baseURL, zoneID)
|
||||
|
||||
req, err := http.NewRequest("GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.apiToken))
|
||||
|
||||
if n.accountSlug != "" {
|
||||
q := req.URL.Query()
|
||||
q.Add("account_slug", n.accountSlug)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
records := make([]*dnsRecord, 0)
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(&records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) deleteDNSRecord(zoneID string, recordID string) error {
|
||||
reqURL := fmt.Sprintf("%s/dns_zones/%s/dns_records/%s", baseURL, zoneID, recordID)
|
||||
|
||||
req, err := http.NewRequest("DELETE", reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.apiToken))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) createDNSRecord(zoneID string, rec *dnsRecordCreate) (*dnsRecord, error) {
|
||||
reqURL := fmt.Sprintf("%s/dns_zones/%s/dns_records", baseURL, zoneID)
|
||||
|
||||
data, err := json.Marshal(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", reqURL, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.apiToken))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
record := &dnsRecord{}
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
17
providers/netlify/auditrecords.go
Normal file
17
providers/netlify/auditrecords.go
Normal file
@ -0,0 +1,17 @@
|
||||
package netlify
|
||||
|
||||
import (
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
|
||||
)
|
||||
|
||||
// AuditRecords returns a list of errors corresponding to the records
|
||||
// that aren't supported by this provider. If all records are
|
||||
// supported, an empty list is returned.
|
||||
func AuditRecords(records []*models.RecordConfig) []error {
|
||||
a := rejectif.Auditor{}
|
||||
|
||||
a.Add("MX", rejectif.MxNull) // Last verified 2022-11-20
|
||||
|
||||
return a.Audit(records)
|
||||
}
|
271
providers/netlify/netlifyProvider.go
Normal file
271
providers/netlify/netlifyProvider.go
Normal file
@ -0,0 +1,271 @@
|
||||
package netlify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var nameServerSuffixes = []string{
|
||||
".nsone.net.",
|
||||
}
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanAutoDNSSEC: providers.Cannot(),
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Can(),
|
||||
providers.CanUseCAA: providers.Can(),
|
||||
providers.CanUseDS: providers.Cannot(),
|
||||
providers.CanUseDSForChildren: providers.Cannot(),
|
||||
providers.CanUseNAPTR: providers.Cannot(),
|
||||
providers.CanUsePTR: providers.Cannot(),
|
||||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseSSHFP: providers.Cannot(),
|
||||
providers.CanUseTLSA: providers.Cannot(),
|
||||
providers.DocCreateDomains: providers.Cannot(),
|
||||
providers.DocDualHost: providers.Cannot("Netlify does not allow sufficient control over the apex NS records"),
|
||||
providers.DocOfficiallySupported: providers.Cannot(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
fns := providers.DspFuncs{
|
||||
Initializer: newNetlify,
|
||||
RecordAuditor: AuditRecords,
|
||||
}
|
||||
providers.RegisterDomainServiceProviderType("NETLIFY", fns, features)
|
||||
providers.RegisterCustomRecordType("NETLIFY", "NETLIFY", "")
|
||||
providers.RegisterCustomRecordType("NETLIFYv6", "NETLIFY", "")
|
||||
}
|
||||
|
||||
type netlifyProvider struct {
|
||||
apiToken string // the account access token
|
||||
accountSlug string // the account identifier slug. optional.
|
||||
}
|
||||
|
||||
func newNetlify(m map[string]string, message json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
api := &netlifyProvider{}
|
||||
api.apiToken = m["token"]
|
||||
if api.apiToken == "" {
|
||||
return nil, fmt.Errorf("missing Netlify personal access token")
|
||||
}
|
||||
|
||||
api.accountSlug = m["slug"]
|
||||
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
zone, err := n.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return models.ToNameservers(zone.DNSServers)
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) getZone(domain string) (*dnsZone, error) {
|
||||
zones, err := n.getDNSZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
if zone.Name == domain {
|
||||
return zone, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no zones found for this domain")
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) GetZoneRecords(domain string) (models.Records, error) {
|
||||
zone, err := n.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records, err := n.getDNSRecords(zone.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanRecords := make(models.Records, 0)
|
||||
|
||||
for _, r := range records {
|
||||
if r.Type == "SOA" {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := &models.RecordConfig{
|
||||
TTL: uint32(r.TTL),
|
||||
Original: r,
|
||||
}
|
||||
|
||||
rec.SetLabelFromFQDN(r.Hostname, domain) // netlify returns the FQDN
|
||||
|
||||
switch rtype := r.Type; rtype {
|
||||
case "NETLIFY", "NETLIFYv6": // these behave similar to a CNAME
|
||||
continue
|
||||
case "MX":
|
||||
err = rec.SetTargetMX(uint16(r.Priority), r.Value)
|
||||
case "SRV":
|
||||
parts := strings.Fields(r.Value)
|
||||
if len(parts) == 3 {
|
||||
r.Value += "."
|
||||
}
|
||||
err = rec.SetTargetSRV(uint16(r.Priority), r.Weight, r.Port, r.Value)
|
||||
case "TXT":
|
||||
err = rec.SetTargetTXT(r.Value)
|
||||
case "CAA":
|
||||
err = rec.SetTargetCAA(uint8(r.Flag), r.Tag, r.Value)
|
||||
default:
|
||||
err = rec.PopulateFromString(r.Type, r.Value, domain)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unparsable record received from Netlify: %w", err)
|
||||
}
|
||||
|
||||
cleanRecords = append(cleanRecords, rec)
|
||||
}
|
||||
|
||||
return cleanRecords, nil
|
||||
}
|
||||
|
||||
// Return true if the string ends in one of Netlify's name server domains
|
||||
// False if anything else
|
||||
func isNetlifyNameServerDomain(name string) bool {
|
||||
for _, i := range nameServerSuffixes {
|
||||
if strings.HasSuffix(name, i) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// remove all non-netlify NS records from our desired state.
|
||||
// if any are found, print a warning
|
||||
func removeOtherApexNS(dc *models.DomainConfig) {
|
||||
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if rec.Type == "NS" {
|
||||
// apex NS inside netlify are expected.
|
||||
// We ignore them, warning as needed.
|
||||
// Child delegations are supported so, we allow non-apex NS records.
|
||||
if rec.GetLabelFQDN() == dc.Name {
|
||||
if !isNetlifyNameServerDomain(rec.GetTargetField()) {
|
||||
printer.Printf("Warning: Netlify does not allow NS records to be modified. %s will not be added.\n", rec.GetTargetField())
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
newList = append(newList, rec)
|
||||
}
|
||||
dc.Records = newList
|
||||
}
|
||||
|
||||
func (n *netlifyProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
var corrections []*models.Correction
|
||||
err := dc.Punycode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records, err := n.GetZoneRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Normalize
|
||||
models.PostProcessRecords(records)
|
||||
txtutil.SplitSingleLongTxt(dc.Records) // Auto split long TXT records
|
||||
removeOtherApexNS(dc)
|
||||
|
||||
differ := diff.New(dc)
|
||||
_, create, del, modify, err := differ.IncrementalDiff(records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zone, err := n.getZone(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deletes first so changing type works etc.
|
||||
for _, m := range del {
|
||||
id := m.Existing.Original.(*dnsRecord).ID
|
||||
corr := &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
return n.deleteDNSRecord(zone.ID, id)
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
|
||||
for _, m := range create {
|
||||
req := toReq(m.Desired)
|
||||
corr := &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
_, err := n.createDNSRecord(zone.ID, req)
|
||||
return err
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
|
||||
for _, m := range modify {
|
||||
id := m.Existing.Original.(*dnsRecord).ID
|
||||
req := toReq(m.Desired)
|
||||
corr := &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
if err := n.deleteDNSRecord(zone.ID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := n.createDNSRecord(zone.ID, req)
|
||||
return err
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func toReq(rc *models.RecordConfig) *dnsRecordCreate {
|
||||
name := rc.GetLabelFQDN() // Netlify wants the FQDN
|
||||
target := rc.GetTargetField()
|
||||
priority := int64(0)
|
||||
|
||||
switch rc.Type {
|
||||
case "MX":
|
||||
priority = int64(rc.MxPreference)
|
||||
case "SRV":
|
||||
priority = int64(rc.SrvPriority)
|
||||
case "TXT":
|
||||
target = rc.GetTargetTXTJoined()
|
||||
default:
|
||||
// no action required
|
||||
}
|
||||
|
||||
return &dnsRecordCreate{
|
||||
Type: rc.Type,
|
||||
Hostname: name,
|
||||
Value: target,
|
||||
TTL: int64(rc.TTL),
|
||||
Priority: priority,
|
||||
Port: int64(rc.SrvPort),
|
||||
Weight: int64(rc.SrvWeight),
|
||||
Tag: rc.CaaTag,
|
||||
Flag: int64(rc.CaaFlag),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user