mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Transip provider (#1196)
* Add initial transip provider * Add GetNameservers to transip * Add first try to correction function. * Implemented corrections * Add docs for transip * Fix TransIP TTL updates * Fix transip nameserver records * Update docs/_providers/transip.md Co-authored-by: Sven Luijten <11269635+svenluijten@users.noreply.github.com> Co-authored-by: Sven Luijten <11269635+svenluijten@users.noreply.github.com>
This commit is contained in:
1
OWNERS
1
OWNERS
@@ -30,3 +30,4 @@ providers/oracle @kallsyms
|
||||
providers/vultr @pgaskin
|
||||
providers/ovh @masterzen
|
||||
providers/powerdns @jpbede
|
||||
providers/transip @blackshadev
|
||||
|
@@ -48,6 +48,7 @@ Currently supported DNS providers:
|
||||
- Oracle Cloud
|
||||
- PowerDNS
|
||||
- SoftLayer
|
||||
- TransIP
|
||||
- Vultr
|
||||
- deSEC
|
||||
|
||||
|
40
docs/_providers/transip.md
Normal file
40
docs/_providers/transip.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: TransIP DNS
|
||||
title: TransIP DNS Provider
|
||||
layout: default
|
||||
jsId: TRANSIP
|
||||
---
|
||||
|
||||
# TransIP DNS Provider
|
||||
|
||||
## Configuration
|
||||
|
||||
In your providers config json file you must include a TransIP personal access token:
|
||||
|
||||
{% highlight json %}
|
||||
{
|
||||
"transip":{
|
||||
"AccessToken": "your-transip-personal-access-token"
|
||||
}
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
## Metadata
|
||||
|
||||
This provider does not recognize any special metadata fields unique to TransIP.
|
||||
|
||||
## Usage
|
||||
|
||||
Example javascript:
|
||||
|
||||
{% highlight js %}
|
||||
var TRANSIP = NewDnsProvider("transip", "TRANSIP");
|
||||
|
||||
D("example.tld", REG_DNSIMPLE, DnsProvider(TRANSIP),
|
||||
A("test","1.2.3.4")
|
||||
);
|
||||
{% endhighlight %}
|
||||
|
||||
## Activation
|
||||
|
||||
TransIP depends on a TransIP personal access token.
|
@@ -98,6 +98,7 @@ Maintainers of contributed providers:
|
||||
* `OVH` @masterzen
|
||||
* `POWERDNS` @jpbede
|
||||
* `SOFTLAYER`@jamielennox
|
||||
* `TRANSIP` @blackshadev
|
||||
* `VULTR` @pgaskin
|
||||
|
||||
### Requested providers
|
||||
|
1
go.mod
1
go.mod
@@ -61,6 +61,7 @@ require (
|
||||
github.com/softlayer/softlayer-go v1.0.3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tdewolff/minify/v2 v2.9.18
|
||||
github.com/transip/gotransip/v6 v6.6.1 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
github.com/vultr/govultr v1.1.1
|
||||
github.com/xddxdd/ottoext v0.0.0-20210101073831-439879ee6281
|
||||
|
2
go.sum
2
go.sum
@@ -484,6 +484,8 @@ github.com/tdewolff/parse/v2 v2.5.18 h1:d67Ql/Pe36JcJZ7J2MY8upx6iTxbxGS9lzwyFGtM
|
||||
github.com/tdewolff/parse/v2 v2.5.18/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/transip/gotransip/v6 v6.6.1 h1:nsCU1ErZS5G0FeOpgGXc4FsWvBff9GPswSMggsC4564=
|
||||
github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
|
@@ -183,5 +183,9 @@
|
||||
"VULTR": {
|
||||
"domain": "$VULTR_DOMAIN",
|
||||
"token": "$VULTR_TOKEN"
|
||||
},
|
||||
"TRANSIP": {
|
||||
"AccessToken": "$TRANSIP_ACCESS_TOKEN",
|
||||
"domain": "$TRANSIP_DOMAIN"
|
||||
}
|
||||
}
|
||||
|
@@ -38,5 +38,6 @@ import (
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/powerdns"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/route53"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/softlayer"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/transip"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/vultr"
|
||||
)
|
||||
|
11
providers/transip/auditrecords.go
Normal file
11
providers/transip/auditrecords.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package transip
|
||||
|
||||
import (
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
)
|
||||
|
||||
// AuditRecords returns an error if any records are not
|
||||
// supportable by this provider.
|
||||
func AuditRecords(records []*models.RecordConfig) error {
|
||||
return nil
|
||||
}
|
251
providers/transip/transipProvider.go
Normal file
251
providers/transip/transipProvider.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package transip
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||
"github.com/transip/gotransip/v6"
|
||||
"github.com/transip/gotransip/v6/domain"
|
||||
"github.com/transip/gotransip/v6/repository"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
TransIP DNS Provider (transip.nl)
|
||||
|
||||
Info required in `creds.json`
|
||||
- AccessToken
|
||||
|
||||
*/
|
||||
|
||||
type transipProvider struct {
|
||||
client *repository.Client
|
||||
domains *domain.Repository
|
||||
}
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanAutoDNSSEC: providers.Cannot(),
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Cannot(),
|
||||
providers.CanUseCAA: providers.Can(),
|
||||
providers.CanUseDS: providers.Cannot(),
|
||||
providers.CanUseDSForChildren: providers.Cannot(),
|
||||
providers.CanUseNAPTR: providers.Can(),
|
||||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseSSHFP: providers.Can(),
|
||||
providers.CanUseTLSA: providers.Can(),
|
||||
providers.DocCreateDomains: providers.Cannot(),
|
||||
providers.DocOfficiallySupported: providers.Cannot(),
|
||||
}
|
||||
|
||||
func NewTransip(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
if m["AccessToken"] == "" {
|
||||
return nil, fmt.Errorf("no TransIP token provided")
|
||||
}
|
||||
|
||||
client, err := gotransip.NewClient(gotransip.ClientConfiguration{
|
||||
Token: m["AccessToken"],
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TransIP client fail %s", err.Error())
|
||||
}
|
||||
|
||||
api := &transipProvider{}
|
||||
api.client = &client
|
||||
api.domains = &domain.Repository{Client: client}
|
||||
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
fns := providers.DspFuncs{
|
||||
Initializer: NewTransip,
|
||||
RecordAuditor: AuditRecords,
|
||||
}
|
||||
providers.RegisterDomainServiceProviderType("TRANSIP", fns, features)
|
||||
}
|
||||
|
||||
func (n *transipProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
var corrections []*models.Correction
|
||||
|
||||
curRecords, err := n.GetZoneRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dc.Punycode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
removeOtherNS(dc)
|
||||
|
||||
models.PostProcessRecords(curRecords)
|
||||
|
||||
differ := diff.New(dc)
|
||||
_, create, del, modify, err := differ.IncrementalDiff(curRecords)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, del := range del {
|
||||
entry, err := recordToNative(del.Existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: del.String(),
|
||||
F: func() error { return n.domains.RemoveDNSEntry(dc.Name, entry) },
|
||||
})
|
||||
}
|
||||
|
||||
for _, cre := range create {
|
||||
entry, err := recordToNative(cre.Desired)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: cre.String(),
|
||||
F: func() error { return n.domains.AddDNSEntry(dc.Name, entry) },
|
||||
})
|
||||
}
|
||||
|
||||
for _, mod := range modify {
|
||||
targetEntry, err := recordToNative(mod.Desired)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TransIP identifies records by (Label, TTL Type), we can only update it if only the contents
|
||||
// has changed. Otherwise we delete the old record and create the new one
|
||||
if canUpdateDNSEntry(mod.Desired, mod.Existing) {
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: mod.String(),
|
||||
F: func() error { return n.domains.UpdateDNSEntry(dc.Name, targetEntry) },
|
||||
})
|
||||
} else {
|
||||
oldEntry, err := recordToNative(mod.Existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
corrections = append(corrections,
|
||||
&models.Correction{
|
||||
Msg: mod.String() + "[1/2]",
|
||||
F: func() error { return n.domains.RemoveDNSEntry(dc.Name, oldEntry) },
|
||||
},
|
||||
&models.Correction{
|
||||
Msg: mod.String() + "[2/2]",
|
||||
F: func() error { return n.domains.AddDNSEntry(dc.Name, targetEntry) },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func canUpdateDNSEntry(desired *models.RecordConfig, existing *models.RecordConfig) bool {
|
||||
return desired.Name == existing.Name && desired.TTL == existing.TTL && desired.Type == existing.Type
|
||||
}
|
||||
|
||||
func (n *transipProvider) GetZoneRecords(domainName string) (models.Records, error) {
|
||||
|
||||
entries, err := n.domains.GetDNSEntries(domainName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var existingRecords = []*models.RecordConfig{}
|
||||
for _, entry := range entries {
|
||||
rts, err := nativeToRecord(entry, domainName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingRecords = append(existingRecords, rts)
|
||||
}
|
||||
|
||||
return existingRecords, nil
|
||||
}
|
||||
|
||||
func (n *transipProvider) GetNameservers(domainName string) ([]*models.Nameserver, error) {
|
||||
var nss []string
|
||||
|
||||
entries, err := n.domains.GetNameservers(domainName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
nss = append(nss, entry.Hostname)
|
||||
}
|
||||
|
||||
return models.ToNameservers(nss)
|
||||
}
|
||||
|
||||
func recordToNative(config *models.RecordConfig) (domain.DNSEntry, error) {
|
||||
return domain.DNSEntry{
|
||||
Name: config.Name,
|
||||
Expire: int(config.TTL),
|
||||
Type: config.Type,
|
||||
Content: getTargetRecordContent(config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func nativeToRecord(entry domain.DNSEntry, origin string) (*models.RecordConfig, error) {
|
||||
rc := &models.RecordConfig{
|
||||
TTL: uint32(*&entry.Expire),
|
||||
Type: entry.Type,
|
||||
Original: entry,
|
||||
}
|
||||
rc.SetLabel(entry.Name, origin)
|
||||
if err := rc.PopulateFromString(entry.Type, entry.Content, origin); err != nil {
|
||||
return nil, fmt.Errorf("unparsable record received from TransIP: %w", err)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func removeNS(records models.Records) models.Records {
|
||||
var noNameServers models.Records
|
||||
for _, r := range records {
|
||||
if r.Type != "NS" {
|
||||
noNameServers = append(noNameServers, r)
|
||||
}
|
||||
}
|
||||
return noNameServers
|
||||
}
|
||||
|
||||
func removeOtherNS(dc *models.DomainConfig) {
|
||||
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if rec.Type == "NS" && (strings.HasPrefix(rec.GetTargetField(), "ns0.transip") ||
|
||||
strings.HasPrefix(rec.GetTargetField(), "ns1.transip") ||
|
||||
strings.HasPrefix(rec.GetTargetField(), "ns2.transip")) {
|
||||
continue
|
||||
}
|
||||
newList = append(newList, rec)
|
||||
}
|
||||
dc.Records = newList
|
||||
}
|
||||
|
||||
func getTargetRecordContent(rc *models.RecordConfig) string {
|
||||
switch rtype := rc.Type; rtype {
|
||||
case "CAA":
|
||||
return rc.GetTargetCombined()
|
||||
case "SSHFP":
|
||||
return fmt.Sprintf("%d %d %s", rc.SshfpAlgorithm, rc.SshfpFingerprint, rc.GetTargetField())
|
||||
case "DS":
|
||||
return fmt.Sprintf("%d %d %d %s", rc.DsKeyTag, rc.DsAlgorithm, rc.DsDigestType, rc.DsDigest)
|
||||
case "SRV":
|
||||
return fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
|
||||
default:
|
||||
return rc.GetTargetCombined()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user