2017-09-13 23:49:15 +08:00
package ns1
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"gopkg.in/ns1/ns1-go.v2/rest"
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
2021-02-11 00:57:15 +08:00
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-29 00:06:56 +08:00
2020-04-15 04:47:30 +08:00
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
"github.com/StackExchange/dnscontrol/v3/providers"
2017-09-13 23:49:15 +08:00
)
2017-09-15 04:13:17 +08:00
var docNotes = providers . DocumentationNotes {
2020-07-07 08:29:25 +08:00
providers . CanUseAlias : providers . Can ( ) ,
2021-05-12 04:35:28 +08:00
providers . CanUseCAA : providers . Can ( ) ,
2020-07-07 08:29:25 +08:00
providers . CanUsePTR : providers . Can ( ) ,
2021-05-15 02:39:09 +08:00
providers . DocCreateDomains : providers . Can ( ) ,
2017-09-15 04:13:17 +08:00
providers . DocDualHost : providers . Can ( ) ,
2022-01-20 02:58:21 +08:00
providers . CanGetZones : providers . Can ( ) ,
2021-05-12 04:35:28 +08:00
providers . DocOfficiallySupported : providers . Cannot ( ) ,
2017-09-15 04:13:17 +08:00
}
2017-09-13 23:49:15 +08:00
func init ( ) {
2021-03-08 02:19:22 +08:00
fns := providers . DspFuncs {
2021-05-05 02:15:31 +08:00
Initializer : newProvider ,
2021-03-09 09:14:30 +08:00
RecordAuditor : AuditRecords ,
2021-03-08 02:19:22 +08:00
}
providers . RegisterDomainServiceProviderType ( "NS1" , fns , providers . CanUseSRV , docNotes )
2021-02-11 00:57:15 +08:00
providers . RegisterCustomRecordType ( "NS1_URLFWD" , "NS1" , "URLFWD" )
2017-09-13 23:49:15 +08:00
}
type nsone struct {
* rest . Client
}
func newProvider ( creds map [ string ] string , meta json . RawMessage ) ( providers . DNSServiceProvider , error ) {
if creds [ "api_token" ] == "" {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-29 00:06:56 +08:00
return nil , fmt . Errorf ( "api_token required for ns1" )
2017-09-13 23:49:15 +08:00
}
return & nsone { rest . NewClient ( http . DefaultClient , rest . SetAPIKey ( creds [ "api_token" ] ) ) } , nil
}
2021-05-15 02:39:09 +08:00
func ( n * nsone ) EnsureDomainExists ( domain string ) error {
// This enables the create-domains subcommand
zone := dns . NewZone ( domain )
_ , err := n . Zones . Create ( zone )
if err == rest . ErrZoneExists {
// if domain exists already, just return nil, nothing to do here.
return nil
}
return err
}
2017-09-13 23:49:15 +08:00
func ( n * nsone ) GetNameservers ( domain string ) ( [ ] * models . Nameserver , error ) {
z , _ , err := n . Zones . Get ( domain )
if err != nil {
return nil , err
}
2020-03-26 21:59:59 +08:00
return models . ToNameservers ( z . DNSServers )
2017-09-13 23:49:15 +08:00
}
2020-02-18 21:59:18 +08:00
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
2020-06-18 21:37:57 +08:00
func ( n * nsone ) GetZoneRecords ( domain string ) ( models . Records , error ) {
2022-01-20 02:58:21 +08:00
z , _ , err := n . Zones . Get ( domain )
2017-09-13 23:49:15 +08:00
if err != nil {
return nil , err
}
found := models . Records { }
for _ , r := range z . Records {
2022-01-20 02:58:21 +08:00
zrs , err := convert ( r , domain )
2017-09-13 23:49:15 +08:00
if err != nil {
return nil , err
}
found = append ( found , zrs ... )
}
2022-01-20 02:58:21 +08:00
return found , nil
}
func ( n * nsone ) GetDomainCorrections ( dc * models . DomainConfig ) ( [ ] * models . Correction , error ) {
dc . Punycode ( )
//dc.CombineMXs()
domain := dc . Name
// Get existing records
existingRecords , err := n . GetZoneRecords ( domain )
if err != nil {
return nil , err
}
existingGrouped := existingRecords . GroupedByKey ( )
2020-01-21 03:13:32 +08:00
desiredGrouped := dc . Records . GroupedByKey ( )
2017-09-13 23:49:15 +08:00
2017-11-08 06:12:17 +08:00
// Normalize
2022-01-20 02:58:21 +08:00
models . PostProcessRecords ( existingRecords )
2017-11-08 06:12:17 +08:00
2017-09-13 23:49:15 +08:00
differ := diff . New ( dc )
2022-01-20 02:58:21 +08:00
changedGroups , err := differ . ChangedGroups ( existingRecords )
2020-08-21 03:49:00 +08:00
if err != nil {
return nil , err
}
2017-09-13 23:49:15 +08:00
corrections := [ ] * models . Correction { }
// each name/type is given to the api as a unit.
for k , descs := range changedGroups {
key := k
2021-05-12 03:52:27 +08:00
2017-09-13 23:49:15 +08:00
desc := strings . Join ( descs , "\n" )
2022-01-20 02:58:21 +08:00
_ , current := existingGrouped [ k ]
2017-09-13 23:49:15 +08:00
recs , wanted := desiredGrouped [ k ]
if wanted && ! current {
// pure addition
corrections = append ( corrections , & models . Correction {
Msg : desc ,
F : func ( ) error { return n . add ( recs , dc . Name ) } ,
} )
} else if current && ! wanted {
// pure deletion
corrections = append ( corrections , & models . Correction {
Msg : desc ,
F : func ( ) error { return n . remove ( key , dc . Name ) } ,
} )
} else {
// modification
corrections = append ( corrections , & models . Correction {
Msg : desc ,
F : func ( ) error { return n . modify ( recs , dc . Name ) } ,
} )
}
}
return corrections , nil
}
func ( n * nsone ) add ( recs models . Records , domain string ) error {
_ , err := n . Records . Create ( buildRecord ( recs , domain , "" ) )
return err
}
func ( n * nsone ) remove ( key models . RecordKey , domain string ) error {
2018-09-08 01:46:44 +08:00
_ , err := n . Records . Delete ( domain , key . NameFQDN , key . Type )
2017-09-13 23:49:15 +08:00
return err
}
func ( n * nsone ) modify ( recs models . Records , domain string ) error {
_ , err := n . Records . Update ( buildRecord ( recs , domain , "" ) )
return err
}
func buildRecord ( recs models . Records , domain string , id string ) * dns . Record {
r := recs [ 0 ]
rec := & dns . Record {
2021-02-11 00:57:15 +08:00
Domain : r . GetLabelFQDN ( ) ,
Type : r . Type ,
ID : id ,
TTL : int ( r . TTL ) ,
Zone : domain ,
Filters : [ ] * filter . Filter { } , // Work through a bug in the NS1 API library that causes 400 Input validation failed (Value None for field '<obj>.filters' is not of type array)
2017-09-13 23:49:15 +08:00
}
for _ , r := range recs {
2020-07-14 20:33:21 +08:00
if r . Type == "MX" {
rec . AddAnswer ( & dns . Answer { Rdata : strings . Split ( fmt . Sprintf ( "%d %v" , r . MxPreference , r . GetTargetField ( ) ) , " " ) } )
} else if r . Type == "TXT" {
2018-02-16 01:02:50 +08:00
rec . AddAnswer ( & dns . Answer { Rdata : r . TxtStrings } )
2021-05-12 04:35:28 +08:00
} else if r . Type == "CAA" {
rec . AddAnswer ( & dns . Answer { Rdata : strings . Split ( fmt . Sprintf ( "%v %s %s" , r . CaaFlag , r . CaaTag , r . GetTargetField ( ) ) , " " ) } )
2017-11-29 20:57:35 +08:00
} else if r . Type == "SRV" {
2018-02-16 01:02:50 +08:00
rec . AddAnswer ( & dns . Answer { Rdata : strings . Split ( fmt . Sprintf ( "%d %d %d %v" , r . SrvPriority , r . SrvWeight , r . SrvPort , r . GetTargetField ( ) ) , " " ) } )
2017-11-20 21:53:44 +08:00
} else {
2018-02-16 01:02:50 +08:00
rec . AddAnswer ( & dns . Answer { Rdata : strings . Split ( r . GetTargetField ( ) , " " ) } )
2017-09-13 23:49:15 +08:00
}
}
return rec
}
func convert ( zr * dns . ZoneRecord , domain string ) ( [ ] * models . RecordConfig , error ) {
found := [ ] * models . RecordConfig { }
for _ , ans := range zr . ShortAns {
rec := & models . RecordConfig {
TTL : uint32 ( zr . TTL ) ,
Original : zr ,
}
2018-02-16 01:02:50 +08:00
rec . SetLabelFromFQDN ( zr . Domain , domain )
switch rtype := zr . Type ; rtype {
2020-07-07 08:29:25 +08:00
case "ALIAS" :
2021-05-12 03:52:27 +08:00
rec . Type = rtype
if err := rec . SetTarget ( ans ) ; err != nil {
panic ( fmt . Errorf ( "unparsable %s record received from ns1: %w" , rtype , err ) )
}
2021-02-11 00:57:15 +08:00
case "URLFWD" :
2020-07-07 08:29:25 +08:00
rec . Type = rtype
if err := rec . SetTarget ( ans ) ; err != nil {
2021-02-11 00:57:15 +08:00
panic ( fmt . Errorf ( "unparsable %s record received from ns1: %w" , rtype , err ) )
2020-07-07 08:29:25 +08:00
}
2018-02-16 01:02:50 +08:00
default :
if err := rec . PopulateFromString ( rtype , ans , domain ) ; err != nil {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-29 00:06:56 +08:00
panic ( fmt . Errorf ( "unparsable record received from ns1: %w" , err ) )
2018-02-16 01:02:50 +08:00
}
2017-09-13 23:49:15 +08:00
}
found = append ( found , rec )
}
return found , nil
}