dnscontrol/providers/ovh/ovhProvider.go

272 lines
6.8 KiB
Go
Raw Normal View History

package ovh
import (
"encoding/json"
"fmt"
"sort"
"strings"
2023-05-21 01:21:45 +08:00
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/ovh/go-ovh/ovh"
)
type ovhProvider struct {
client *ovh.Client
zones map[string]bool
}
var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
MAINT: Sort various lists (#1433) * maint: sort lists in build/generate/featureMatrix.go * maint: sort lists in docs/byo-secrets.md * maint: sort lists in models/record.go * maint: sort lists in pkg/normalize/validate.go * maint: sort lists in providers/activedir/activedirProvider.go * maint: sort lists in providers/akamaiedgedns/akamaiEdgeDnsProvider.go * maint: sort lists in providers/axfrddns/axfrddnsProvider.go * maint: sort lists in providers/azuredns/azureDnsProvider.go * maint: sort lists in providers/cloudflare/cloudflareProvider.go * maint: sort lists in providers/cloudns/cloudnsProvider.go * maint: sort lists in providers/desec/desecProvider.go * maint: sort lists in providers/digitalocean/digitaloceanProvider.go * maint: sort lists in providers/dnsimple/dnsimpleProvider.go * maint: sort lists in providers/dnsmadeeasy/dnsMadeEasyProvider.go * maint: sort lists in providers/exoscale/exoscaleProvider.go * maint: sort lists in providers/gandiv5/gandi_v5Provider.go * maint: sort lists in providers/hedns/hednsProvider.go * maint: sort lists in providers/hetzner/hetznerProvider.go * maint: sort lists in providers/hexonet/hexonetProvider.go * maint: sort lists in providers/inwx/inwxProvider.go * maint: sort lists in providers/linode/linodeProvider.go * maint: sort lists in providers/namecheap/namecheapProvider.go * maint: sort lists in providers/namedotcom/namedotcomProvider.go * maint: sort lists in providers/netcup/netcupProvider.go * maint: sort lists in providers/ns1/ns1Provider.go * maint: sort lists in providers/octodns/octodnsProvider.go * maint: sort lists in providers/oracle/oracleProvider.go * maint: sort lists in providers/ovh/ovhProvider.go * maint: sort lists in providers/packetframe/packetframeProvider.go * maint: sort lists in providers/powerdns/powerdnsProvider.go * maint: sort lists in providers/route53/route53Provider.go * maint: sort lists in providers/vultr/vultrProvider.go * Update go:build pragmas for windows * More sorting * go generate
2022-03-03 00:19:15 +08:00
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Cannot(),
providers.CanUseAlias: providers.Cannot(),
2019-05-18 23:11:04 +08:00
providers.CanUseCAA: providers.Can(),
providers.CanUseLOC: providers.Unimplemented(),
providers.CanUsePTR: providers.Cannot(),
providers.CanUseSRV: providers.Can(),
2019-05-20 00:19:55 +08:00
providers.CanUseSSHFP: providers.Can(),
MAINT: Sort various lists (#1433) * maint: sort lists in build/generate/featureMatrix.go * maint: sort lists in docs/byo-secrets.md * maint: sort lists in models/record.go * maint: sort lists in pkg/normalize/validate.go * maint: sort lists in providers/activedir/activedirProvider.go * maint: sort lists in providers/akamaiedgedns/akamaiEdgeDnsProvider.go * maint: sort lists in providers/axfrddns/axfrddnsProvider.go * maint: sort lists in providers/azuredns/azureDnsProvider.go * maint: sort lists in providers/cloudflare/cloudflareProvider.go * maint: sort lists in providers/cloudns/cloudnsProvider.go * maint: sort lists in providers/desec/desecProvider.go * maint: sort lists in providers/digitalocean/digitaloceanProvider.go * maint: sort lists in providers/dnsimple/dnsimpleProvider.go * maint: sort lists in providers/dnsmadeeasy/dnsMadeEasyProvider.go * maint: sort lists in providers/exoscale/exoscaleProvider.go * maint: sort lists in providers/gandiv5/gandi_v5Provider.go * maint: sort lists in providers/hedns/hednsProvider.go * maint: sort lists in providers/hetzner/hetznerProvider.go * maint: sort lists in providers/hexonet/hexonetProvider.go * maint: sort lists in providers/inwx/inwxProvider.go * maint: sort lists in providers/linode/linodeProvider.go * maint: sort lists in providers/namecheap/namecheapProvider.go * maint: sort lists in providers/namedotcom/namedotcomProvider.go * maint: sort lists in providers/netcup/netcupProvider.go * maint: sort lists in providers/ns1/ns1Provider.go * maint: sort lists in providers/octodns/octodnsProvider.go * maint: sort lists in providers/oracle/oracleProvider.go * maint: sort lists in providers/ovh/ovhProvider.go * maint: sort lists in providers/packetframe/packetframeProvider.go * maint: sort lists in providers/powerdns/powerdnsProvider.go * maint: sort lists in providers/route53/route53Provider.go * maint: sort lists in providers/vultr/vultrProvider.go * Update go:build pragmas for windows * More sorting * go generate
2022-03-03 00:19:15 +08:00
providers.CanUseTLSA: providers.Can(),
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(),
}
2024-03-05 09:35:10 +08:00
func newOVH(m map[string]string, _ json.RawMessage) (*ovhProvider, error) {
appKey, appSecretKey, consumerKey := m["app-key"], m["app-secret-key"], m["consumer-key"]
c, err := ovh.NewClient(getOVHEndpoint(m), appKey, appSecretKey, consumerKey)
if c == nil {
return nil, err
}
ovh := &ovhProvider{client: c}
if err := ovh.fetchZones(); err != nil {
return nil, err
}
return ovh, nil
}
func getOVHEndpoint(params map[string]string) string {
if ep, ok := params["endpoint"]; ok && ep != "" {
switch strings.ToLower(ep) {
case "eu":
return ovh.OvhEU
case "ca":
return ovh.OvhCA
case "us":
return ovh.OvhUS
default:
return ep
}
}
return ovh.OvhEU
}
func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
return newOVH(conf, metadata)
}
func newReg(conf map[string]string) (providers.Registrar, error) {
return newOVH(conf, nil)
}
func init() {
2024-07-11 03:53:50 +08:00
const providerName = "OVH"
const providerMaintainer = "@masterzen"
fns := providers.DspFuncs{
2021-05-05 02:15:31 +08:00
Initializer: newDsp,
RecordAuditor: AuditRecords,
}
2024-07-11 03:53:50 +08:00
providers.RegisterRegistrarType(providerName, newReg)
providers.RegisterDomainServiceProviderType(providerName, fns, features)
providers.RegisterMaintainer(providerName, providerMaintainer)
}
func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
_, ok := c.zones[domain]
if !ok {
return nil, fmt.Errorf("'%s' not a zone in ovh account", domain)
}
ns, err := c.fetchNS(domain)
if err != nil {
return nil, err
}
return models.ToNameservers(ns)
}
type errNoExist struct {
domain string
}
func (e errNoExist) Error() string {
return fmt.Sprintf("Domain %s not found in your ovh account", e.domain)
}
// ListZones lists the zones on this account.
func (c *ovhProvider) ListZones() (zones []string, err error) {
for zone := range c.zones {
zones = append(zones, zone)
}
return
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (c *ovhProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
if !c.zones[domain] {
return nil, errNoExist{domain}
}
records, err := c.fetchRecords(domain)
if err != nil {
return nil, err
}
var actual models.Records
for _, r := range records {
rec, err := nativeToRecord(r, domain)
if err != nil {
return nil, err
}
if rec != nil {
actual = append(actual, rec)
}
}
return actual, nil
}
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
func (c *ovhProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, actual models.Records) ([]*models.Correction, error) {
corrections, err := c.getDiff2DomainCorrections(dc, actual)
2022-12-19 23:21:54 +08:00
if err != nil {
return nil, err
}
// Only refresh zone if there's a real modification
reportOnlyCorrections := true
for _, c := range corrections {
if c.F != nil {
reportOnlyCorrections = false
break
}
}
if !reportOnlyCorrections {
2022-12-19 23:21:54 +08:00
corrections = append(corrections, &models.Correction{
Msg: "REFRESH zone " + dc.Name,
F: func() error {
return c.refreshZone(dc.Name)
},
})
}
return corrections, nil
}
func (c *ovhProvider) getDiff2DomainCorrections(dc *models.DomainConfig, actual models.Records) ([]*models.Correction, error) {
var corrections []*models.Correction
instructions, err := diff2.ByRecord(actual, dc, nil)
if err != nil {
return nil, err
}
for _, inst := range instructions {
switch inst.Type {
case diff2.REPORT:
corrections = append(corrections, &models.Correction{Msg: inst.MsgsJoined})
2022-12-19 23:21:54 +08:00
case diff2.CHANGE:
corrections = append(corrections, &models.Correction{
2022-12-19 23:21:54 +08:00
Msg: inst.Msgs[0],
F: c.updateRecordFunc(inst.Old[0].Original.(*Record), inst.New[0], dc.Name),
})
2022-12-19 23:21:54 +08:00
case diff2.CREATE:
corrections = append(corrections, &models.Correction{
2022-12-19 23:21:54 +08:00
Msg: inst.Msgs[0],
F: c.createRecordFunc(inst.New[0], dc.Name),
})
2022-12-19 23:21:54 +08:00
case diff2.DELETE:
rec := inst.Old[0].Original.(*Record)
corrections = append(corrections, &models.Correction{
2022-12-19 23:21:54 +08:00
Msg: inst.Msgs[0],
F: c.deleteRecordFunc(rec.ID, dc.Name),
})
default:
panic(fmt.Sprintf("unhandled inst.Type %s", inst.Type))
}
}
return corrections, nil
}
func nativeToRecord(r *Record, origin string) (*models.RecordConfig, error) {
if r.FieldType == "SOA" {
return nil, nil
}
rec := &models.RecordConfig{
TTL: uint32(r.TTL),
Original: r,
}
rtype := r.FieldType
// ovh uses a custom type for SPF, DKIM and DMARC
if rtype == "SPF" || rtype == "DKIM" || rtype == "DMARC" {
rtype = "TXT"
}
rec.SetLabel(r.SubDomain, origin)
if err := rec.PopulateFromString(rtype, r.Target, origin); err != nil {
return nil, fmt.Errorf("unparsable record received from ovh: %w", err)
}
// ovh default is 3600
if rec.TTL == 0 {
rec.TTL = 3600
}
return rec, nil
}
func (c *ovhProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
// get the actual in-use nameservers
actualNs, err := c.fetchRegistrarNS(dc.Name)
if err != nil {
return nil, err
}
// get the actual used ones + the configured one through dnscontrol
expectedNs := []string{}
for _, d := range dc.Nameservers {
expectedNs = append(expectedNs, d.Name)
}
sort.Strings(actualNs)
actual := strings.Join(actualNs, ",")
sort.Strings(expectedNs)
expected := strings.Join(expectedNs, ",")
// check if we need to change something
if actual != expected {
return []*models.Correction{
{
Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", actual, expected),
F: func() error {
err := c.updateNS(dc.Name, expectedNs)
if err != nil {
return err
}
return nil
}},
}, nil
}
return nil, nil
}