mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 10:06:57 +02:00
Initial Skeleton From NS1 Provider
This needs unit tests and a full integration test. Pushing this as an initial checkpoint.
This commit is contained in:
parent
a0976df0d9
commit
bff09c20c9
8
main.go
8
main.go
@ -201,6 +201,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
case "rfc2136":
|
case "rfc2136":
|
||||||
p, err = provider.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, nil)
|
p, err = provider.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, nil)
|
||||||
|
case "ns1":
|
||||||
|
p, err = provider.NewNS1Provider(
|
||||||
|
provider.NS1Config{
|
||||||
|
DomainFilter: domainFilter,
|
||||||
|
ZoneIDFilter: zoneIDFilter,
|
||||||
|
DryRun: cfg.DryRun,
|
||||||
|
},
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||||
}
|
}
|
||||||
|
241
provider/ns1.go
Normal file
241
provider/ns1.go
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
api "gopkg.in/ns1/ns1-go.v2/rest"
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||||
|
"github.com/kubernetes-incubator/external-dns/plan"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ns1Create is a ChangeAction enum value
|
||||||
|
ns1Create = "CREATE"
|
||||||
|
// ns1Delete is a ChangeAction enum value
|
||||||
|
ns1Delete = "DELETE"
|
||||||
|
// ns1Update is a ChangeAction enum value
|
||||||
|
ns1Update = "UPDATE"
|
||||||
|
// ns1DefaultTTL is the default ttl for ttls that are not set
|
||||||
|
ns1DefaultTTL = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// NS1Config passes cli args to the NS1Provider
|
||||||
|
type NS1Config struct {
|
||||||
|
DomainFilter DomainFilter
|
||||||
|
ZoneIDFilter ZoneIDFilter
|
||||||
|
DryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NS1Provider is the NS1 provider
|
||||||
|
type NS1Provider struct {
|
||||||
|
client *api.Client
|
||||||
|
domainFilter DomainFilter
|
||||||
|
zoneIDFilter ZoneIDFilter
|
||||||
|
dryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNS1Provider creates a new NS1 Provider
|
||||||
|
func NewNS1Provider(config NS1Config) (*NS1Provider, error) {
|
||||||
|
return newNS1ProviderWithHTTPClient(config, http.DefaultClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNS1ProviderWithHTTPClient(config NS1Config, client *http.Client) (*NS1Provider, error) {
|
||||||
|
token, ok := os.LookupEnv("NS1_APIKEY")
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("NS1_APIKEY environment variable is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiClient := api.NewClient(client, api.SetAPIKey(token))
|
||||||
|
|
||||||
|
provider := &NS1Provider{
|
||||||
|
client: apiClient,
|
||||||
|
domainFilter: config.DomainFilter,
|
||||||
|
zoneIDFilter: config.ZoneIDFilter,
|
||||||
|
}
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NS1Provider) matchEither(id string) bool {
|
||||||
|
return p.domainFilter.Match(id) || p.zoneIDFilter.Match(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Records returns the endpoints this provider knows about
|
||||||
|
func (p *NS1Provider) Records() ([]*endpoint.Endpoint, error) {
|
||||||
|
zones, err := p.zonesFiltered()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoints []*endpoint.Endpoint
|
||||||
|
|
||||||
|
for _, zone := range zones {
|
||||||
|
|
||||||
|
// TODO handle Header Codes
|
||||||
|
zoneData, _, err := p.client.Zones.Get(zone.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, record := range zoneData.Records {
|
||||||
|
if supportedRecordType(record.Type) {
|
||||||
|
name := fmt.Sprintf("%s.%s", record.Domain, zoneData.Zone)
|
||||||
|
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(
|
||||||
|
name,
|
||||||
|
record.Type,
|
||||||
|
endpoint.TTL(record.TTL),
|
||||||
|
record.ShortAns...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ns1BuildRecord(zoneName string, change *ns1Change) *dns.Record {
|
||||||
|
record := dns.NewRecord(zoneName, change.Endpoint.DNSName, change.Endpoint.RecordType)
|
||||||
|
for _, v := range change.Endpoint.Targets {
|
||||||
|
record.AddAnswer(dns.NewAnswer(strings.Split(v, " ")))
|
||||||
|
}
|
||||||
|
// set detault ttl
|
||||||
|
var ttl = ns1DefaultTTL
|
||||||
|
if change.Endpoint.RecordTTL.IsConfigured() {
|
||||||
|
ttl = int(change.Endpoint.RecordTTL)
|
||||||
|
}
|
||||||
|
record.TTL = ttl
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NS1Provider) ns1SubmitChanges(changes []*ns1Change) error {
|
||||||
|
// return early if there is nothing to change
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
zones, err := p.zonesFiltered()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// separate into per-zone change sets to be passed to the API.
|
||||||
|
changesByZone := ns1ChangesByZone(zones, changes)
|
||||||
|
for zoneName, changes := range changesByZone {
|
||||||
|
for _, change := range changes {
|
||||||
|
record := ns1BuildRecord(zoneName, change)
|
||||||
|
logFields := log.Fields{
|
||||||
|
"record": record.Domain,
|
||||||
|
"type": record.Type,
|
||||||
|
"ttl": record.TTL,
|
||||||
|
"action": change.Action,
|
||||||
|
"zone": zoneName,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(logFields).Info("Changing record.")
|
||||||
|
|
||||||
|
if p.dryRun {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch change.Action {
|
||||||
|
case ns1Create:
|
||||||
|
_, err := p.client.Records.Create(record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case ns1Delete:
|
||||||
|
_, err := p.client.Records.Delete(zoneName, record.Domain, record.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case ns1Update:
|
||||||
|
_, err := p.client.Records.Update(record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zones returns the list of hosted zones.
|
||||||
|
func (p *NS1Provider) zonesFiltered() ([]*dns.Zone, error) {
|
||||||
|
// TODO handle Header Codes
|
||||||
|
zones, _, err := p.client.Zones.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toReturn := []*dns.Zone{}
|
||||||
|
|
||||||
|
for _, z := range zones {
|
||||||
|
if !p.matchEither(z.Zone) && !p.matchEither(z.ID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toReturn = append(toReturn, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ns1Change differentiates between ChangActions
|
||||||
|
type ns1Change struct {
|
||||||
|
Action string
|
||||||
|
Endpoint *endpoint.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyChanges applies a given set of changes in a given zone.
|
||||||
|
func (p *NS1Provider) ApplyChanges(changes *plan.Changes) error {
|
||||||
|
combinedChanges := make([]*ns1Change, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||||
|
|
||||||
|
combinedChanges = append(combinedChanges, newNS1Changes(ns1Create, changes.Create)...)
|
||||||
|
combinedChanges = append(combinedChanges, newNS1Changes(ns1Update, changes.UpdateNew)...)
|
||||||
|
combinedChanges = append(combinedChanges, newNS1Changes(ns1Delete, changes.Delete)...)
|
||||||
|
|
||||||
|
return p.ns1SubmitChanges(combinedChanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newNS1Changes returns a collection of Changes based on the given records and action.
|
||||||
|
func newNS1Changes(action string, endpoints []*endpoint.Endpoint) []*ns1Change {
|
||||||
|
changes := make([]*ns1Change, 0, len(endpoints))
|
||||||
|
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
changes = append(changes, &ns1Change{
|
||||||
|
Action: action,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ns1ChangesByZone separates a multi-zone change into a single change per zone.
|
||||||
|
func ns1ChangesByZone(zones []*dns.Zone, changeSets []*ns1Change) map[string][]*ns1Change {
|
||||||
|
changes := make(map[string][]*ns1Change)
|
||||||
|
zoneNameIDMapper := zoneIDName{}
|
||||||
|
for _, z := range zones {
|
||||||
|
zoneNameIDMapper.Add(z.Zone, z.Zone)
|
||||||
|
changes[z.Zone] = []*ns1Change{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range changeSets {
|
||||||
|
zone, _ := zoneNameIDMapper.FindZone(c.Endpoint.DNSName)
|
||||||
|
if zone == "" {
|
||||||
|
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected ", c.Endpoint.DNSName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changes[zone] = append(changes[zone], c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes
|
||||||
|
}
|
1
provider/ns1_test.go
Normal file
1
provider/ns1_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package provider
|
Loading…
Reference in New Issue
Block a user