Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OVH provider #105

Merged
merged 2 commits into from Mar 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions main.go
Expand Up @@ -16,6 +16,7 @@ import (
_ "github.com/rancher/external-dns/providers/dnsimple"
_ "github.com/rancher/external-dns/providers/gandi"
_ "github.com/rancher/external-dns/providers/infoblox"
_ "github.com/rancher/external-dns/providers/ovh"
_ "github.com/rancher/external-dns/providers/pointhq"
_ "github.com/rancher/external-dns/providers/powerdns"
_ "github.com/rancher/external-dns/providers/rfc2136"
Expand Down
251 changes: 251 additions & 0 deletions providers/ovh/ovh.go
@@ -0,0 +1,251 @@
package OVH

import (
"fmt"
"os"
"strconv"
"strings"

"github.com/Sirupsen/logrus"
api "github.com/ovh/go-ovh/ovh"
"github.com/rancher/external-dns/providers"
"github.com/rancher/external-dns/utils"
)

type Record struct {
Target string `json:"target"`
TTL int64 `json:"ttl"`
Zone string `json:"zone"`
FieldType string `json:"fieldType"`
ID int64 `json:"id"`
SubDomain string `json:"subDomain"`
}

type OVHProvider struct {
client *api.Client
root string
}

func init() {
providers.RegisterProvider("ovh", &OVHProvider{})
}

func (d *OVHProvider) Init(rootDomainName string) error {
var endpoint, applicationKey, applicationSecret, consumerKey string
if endpoint = os.Getenv("OVH_ENDPOINT"); len(endpoint) == 0 {
return fmt.Errorf("OVH_ENDPOINT is not set")
}

if applicationKey = os.Getenv("OVH_APPLICATION_KEY"); len(applicationKey) == 0 {
return fmt.Errorf("OVH_APPLICATION_KEY is not set")
}

if applicationSecret = os.Getenv("OVH_APPLICATION_SECRET"); len(applicationSecret) == 0 {
return fmt.Errorf("OVH_APPLICATION_SECRET is not set")
}

if consumerKey = os.Getenv("OVH_CONSUMER_KEY"); len(consumerKey) == 0 {
return fmt.Errorf("OVH_CONSUMER_KEY is not set")
}

d.root = utils.UnFqdn(rootDomainName)
client, err := api.NewClient(endpoint, applicationKey, applicationSecret, consumerKey)
if err != nil {
return fmt.Errorf("Failed to create OVH client: %v", err)
}

d.client = client

var zones []string
err = d.client.Get("/domain/zone", &zones)

if err != nil {
return fmt.Errorf("Failed to list hosted zones: %v", err)
}

found := false
for _, zone := range zones {
if zone == d.root {
found = true
break
}
}

if !found {
return fmt.Errorf("Zone for '%s' not found", d.root)
}

logrus.Infof("Configured %s with zone '%s'", d.GetName(), d.root)
return nil
}

func (*OVHProvider) GetName() string {
return "OVH"
}

func (d *OVHProvider) HealthCheck() error {
var me interface{}
err := d.client.Get("/me", &me)
return err
}

func (d *OVHProvider) parseName(record utils.DnsRecord) string {
name := strings.TrimSuffix(record.Fqdn, fmt.Sprintf(".%s.", d.root))
return name
}

func (d *OVHProvider) AddRecord(record utils.DnsRecord) (err error) {
var url string
var body interface{}
var resType interface{}
for _, rec := range record.Records {
if url, body, err = d.prepareRecord(rec, record.Type, record.Fqdn, record.TTL); err != nil {
return err
}
if err = d.client.Post(url, body, resType); err != nil {
return fmt.Errorf("OVH API call `POST %s` with body `%s` has failed: %v", url, body, err)
}
}
d.refreshZone()
return nil
}

func (d *OVHProvider) FindRecords(record utils.DnsRecord) ([]*Record, error) {
var records []*Record

urlRecIDs := strings.Join([]string{"/domain/zone/", d.root, "/record"}, "")

var recIDs []int64
err := d.client.Get(urlRecIDs, &recIDs)

if err != nil {
return records, fmt.Errorf("OVH API call `GET %s` has failed: %v", urlRecIDs, err)
}

name := d.parseName(record)
for _, recID := range recIDs {
urlRecord := strings.Join([]string{"/domain/zone/", d.root, "/record/", strconv.FormatInt(recID, 10)}, "")
var rec *Record
if err = d.client.Get(urlRecord, &rec); err != nil {
return records, fmt.Errorf("OVH API call `GET %s` has failed: %v", urlRecord, err)
}

if rec.SubDomain == name && rec.FieldType == record.Type {
records = append(records, rec)
}
}

return records, nil
}

func (d *OVHProvider) UpdateRecord(record utils.DnsRecord) error {
err := d.RemoveRecord(record)
if err != nil {
return err
}

return d.AddRecord(record)
}

func (d *OVHProvider) RemoveRecord(record utils.DnsRecord) error {
records, err := d.FindRecords(record)
if err != nil {
return err
}

for _, rec := range records {
urlRecord := strings.Join([]string{"/domain/zone/", d.root, "/record/", strconv.FormatInt(rec.ID, 10)}, "")
var resType interface{}
if err := d.client.Delete(urlRecord, &resType); err != nil {
return fmt.Errorf("OVH API call `DELETE %s` has failed: %v", urlRecord, err)
}
}

d.refreshZone()

return nil
}

func (d *OVHProvider) GetRecords() ([]utils.DnsRecord, error) {
var dnsRecords []utils.DnsRecord

urlRecIDs := strings.Join([]string{"/domain/zone/", d.root, "/record"}, "")

var recIDs []int64
err := d.client.Get(urlRecIDs, &recIDs)

if err != nil {
return dnsRecords, fmt.Errorf("OVH API call `GET %s` has failed: %v", urlRecIDs, err)
}

var records []*Record
for _, recID := range recIDs {
urlRecord := strings.Join([]string{"/domain/zone/", d.root, "/record/", strconv.FormatInt(recID, 10)}, "")
var record *Record
if err = d.client.Get(urlRecord, &record); err != nil {
return dnsRecords, fmt.Errorf("OVH API call `GET %s` has failed: %v", urlRecord, err)
}
records = append(records, record)
}

recordMap := map[string]map[string][]string{}
recordTTLs := map[string]map[string]int{}

for _, rec := range records {
var fqdn string

if rec.SubDomain == "" {
fqdn = fmt.Sprintf("%s.", rec.Zone)
} else {
fqdn = fmt.Sprintf("%s.%s.", rec.SubDomain, rec.Zone)
}

recordTTLs[fqdn] = map[string]int{}
recordTTLs[fqdn][rec.FieldType] = int(rec.TTL)
recordSet, exists := recordMap[fqdn]

if exists {
recordSlice, sliceExists := recordSet[rec.FieldType]
if sliceExists {
recordSlice = append(recordSlice, rec.Target)
recordSet[rec.FieldType] = recordSlice
} else {
recordSet[rec.FieldType] = []string{rec.Target}
}
} else {
recordMap[fqdn] = map[string][]string{}
recordMap[fqdn][rec.FieldType] = []string{rec.Target}
}
}

for fqdn, recordSet := range recordMap {
for recordType, recordSlice := range recordSet {
ttl := recordTTLs[fqdn][recordType]
record := utils.DnsRecord{Fqdn: fqdn, Records: recordSlice, Type: recordType, TTL: ttl}
dnsRecords = append(dnsRecords, record)
}
}

return dnsRecords, nil
}

func (d *OVHProvider) prepareRecord(rec string, tp string, fqdn string, ttl int) (string, interface{}, error) {
var url string
url = strings.Join([]string{"/domain/zone/", d.root, "/record"}, "")
body := make(map[string]interface{})
name := strings.TrimSuffix(fqdn, fmt.Sprintf(".%s.", d.root))
body["fieldType"] = tp
body["subDomain"] = name
body["ttl"] = ttl
body["target"] = rec
return url, body, nil
}

func (d *OVHProvider) refreshZone() error {
url := strings.Join([]string{"/domain/zone/", d.root, "/refresh"}, "")
var resType interface{}
if err := d.client.Post(url, nil, &resType); err != nil {
return fmt.Errorf("OVH API call `POST %s` has failed: %v", url, err)
}
return nil
}
2 changes: 2 additions & 0 deletions vendor.conf
Expand Up @@ -18,6 +18,7 @@ github.com/jmespath/go-jmespath 0b12d6b
github.com/juju/ratelimit 77ed1c8
github.com/kolo/xmlrpc 0826b98
github.com/miekg/dns 48ab660
github.com/ovh/go-ovh df6beeb
github.com/pkg/errors ff09b13
github.com/prasmussen/gandi-api 2dd22da
github.com/rancher/go-rancher-metadata/metadata 11a77c2
Expand All @@ -29,3 +30,4 @@ golang.org/x/net/context 633434a
golang.org/x/oauth2 314dd2c
github.com/fanatic/go-infoblox 81fb1b0
golang.org/x/net/publicsuffix 8351a75
gopkg.in/ini.v1 32e4c1e
3 changes: 3 additions & 0 deletions vendor/github.com/ovh/go-ovh/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions vendor/github.com/ovh/go-ovh/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.