1
0
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:
Vincent Hagen
2021-07-07 17:43:10 +02:00
committed by GitHub
parent 991f73730d
commit 506b6cc32f
10 changed files with 313 additions and 0 deletions

1
OWNERS
View File

@@ -30,3 +30,4 @@ providers/oracle @kallsyms
providers/vultr @pgaskin
providers/ovh @masterzen
providers/powerdns @jpbede
providers/transip @blackshadev

View File

@@ -48,6 +48,7 @@ Currently supported DNS providers:
- Oracle Cloud
- PowerDNS
- SoftLayer
- TransIP
- Vultr
- deSEC

View 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.

View File

@@ -98,6 +98,7 @@ Maintainers of contributed providers:
* `OVH` @masterzen
* `POWERDNS` @jpbede
* `SOFTLAYER`@jamielennox
* `TRANSIP` @blackshadev
* `VULTR` @pgaskin
### Requested providers

1
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -183,5 +183,9 @@
"VULTR": {
"domain": "$VULTR_DOMAIN",
"token": "$VULTR_TOKEN"
},
"TRANSIP": {
"AccessToken": "$TRANSIP_ACCESS_TOKEN",
"domain": "$TRANSIP_DOMAIN"
}
}

View File

@@ -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"
)

View 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
}

View 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()
}
}