mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
feat(ovh): major rewriting of the provider (#5143)
* feat: ovh: improve cache invalidation on errors + dry-run mode + relative CNAME handling + optimization Signed-off-by: Romain Beuque <556072+rbeuque74@users.noreply.github.com> * chore: add more tests Signed-off-by: Romain Beuque <556072+rbeuque74@users.noreply.github.com> * fix: align cache expiration with Default value * chore: address comments from review + updated documentation * chore: address comments from review --------- Signed-off-by: Romain Beuque <556072+rbeuque74@users.noreply.github.com>
This commit is contained in:
parent
823ea7e6f1
commit
ecd57c86f5
@ -57,7 +57,7 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
|
||||
- [RFC2136](https://tools.ietf.org/html/rfc2136)
|
||||
- [NS1](https://ns1.com/)
|
||||
- [TransIP](https://www.transip.eu/domain-name/)
|
||||
- [OVH](https://www.ovh.com)
|
||||
- [OVHcloud](https://www.ovhcloud.com)
|
||||
- [Scaleway](https://www.scaleway.com)
|
||||
- [Akamai Edge DNS](https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html)
|
||||
- [GoDaddy](https://www.godaddy.com)
|
||||
@ -85,7 +85,7 @@ See PR #3063 for all the discussions about it.
|
||||
Known providers using webhooks:
|
||||
|
||||
| Provider | Repo |
|
||||
|-----------------------|----------------------------------------------------------------------|
|
||||
| --------------------- | -------------------------------------------------------------------- |
|
||||
| Abion | https://github.com/abiondevelopment/external-dns-webhook-abion |
|
||||
| Adguard Home Provider | https://github.com/muhlba91/external-dns-provider-adguard |
|
||||
| Anexia | https://github.com/ProbstenHias/external-dns-anexia-webhook |
|
||||
@ -145,7 +145,7 @@ The following table clarifies the current status of the providers according to t
|
||||
| RFC2136 | Alpha | |
|
||||
| NS1 | Alpha | |
|
||||
| TransIP | Alpha | |
|
||||
| OVH | Alpha | |
|
||||
| OVHcloud | Beta | @rbeuque74 |
|
||||
| Scaleway DNS | Alpha | @Sh4d1 |
|
||||
| UltraDNS | Alpha | |
|
||||
| GoDaddy | Alpha | |
|
||||
@ -207,7 +207,7 @@ The following tutorials are provided:
|
||||
- [PowerDNS](docs/tutorials/pdns.md)
|
||||
- [RFC2136](docs/tutorials/rfc2136.md)
|
||||
- [TransIP](docs/tutorials/transip.md)
|
||||
- [OVH](docs/tutorials/ovh.md)
|
||||
- [OVHcloud](docs/tutorials/ovh.md)
|
||||
- [Scaleway](docs/tutorials/scaleway.md)
|
||||
- [UltraDNS](docs/tutorials/ultradns.md)
|
||||
- [GoDaddy](docs/tutorials/godaddy.md)
|
||||
|
@ -107,6 +107,7 @@
|
||||
| `--inmemory-zone=` | Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional) |
|
||||
| `--ovh-endpoint="ovh-eu"` | When using the OVH provider, specify the endpoint (default: ovh-eu) |
|
||||
| `--ovh-api-rate-limit=20` | When using the OVH provider, specify the API request rate limit, X operations by seconds (default: 20) |
|
||||
| `--[no-]ovh-enable-cname-relative` | When using the OVH provider, specify if CNAME should be treated as relative on target without final dot (default: false) |
|
||||
| `--pdns-server="http://localhost:8081"` | When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns) |
|
||||
| `--pdns-server-id="localhost"` | When using the PowerDNS/PDNS provider, specify the id of the server to retrieve. Should be `localhost` except when the server is behind a proxy (optional when --provider=pdns) (default: localhost) |
|
||||
| `--pdns-api-key=""` | When using the PowerDNS/PDNS provider, specify the API key to use to authorize requests (required when --provider=pdns) |
|
||||
|
@ -1,30 +1,31 @@
|
||||
# OVHcloud
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for use within a
|
||||
Kubernetes cluster using OVH DNS.
|
||||
Kubernetes cluster using OVHcloud DNS.
|
||||
|
||||
Make sure to use **>=0.6** version of ExternalDNS for this tutorial.
|
||||
|
||||
## Creating a zone with OVH DNS
|
||||
## Creating a zone with OVHcloud DNS
|
||||
|
||||
If you are new to OVH, we recommend you first read the following
|
||||
If you are new to OVHcloud, we recommend you first read the following
|
||||
instructions for creating a zone.
|
||||
|
||||
[Creating a zone using the OVH manager](https://docs.ovh.com/gb/en/domains/create_a_dns_zone_for_a_domain_which_is_not_registered_at_ovh/)
|
||||
[Creating a zone using the OVHcloud Manager](https://help.ovhcloud.com/csm/en-gb-dns-create-dns-zone?id=kb_article_view&sysparm_article=KB0051667/)
|
||||
|
||||
[Creating a zone using the OVH API](https://api.ovh.com/console/)
|
||||
[Creating a zone using the OVHcloud API](https://api.ovh.com/console/)
|
||||
|
||||
## Creating OVH Credentials
|
||||
## Creating OVHcloud Credentials
|
||||
|
||||
You first need to create an OVH application.
|
||||
|
||||
Using the [OVH documentation](https://docs.ovh.com/gb/en/api/first-steps-with-ovh-api/#advanced-usage-pair-ovhcloud-apis-with-an-application_2) you will have your `Application key` and `Application secret`
|
||||
You first need to create an OVHcloud application: follow the
|
||||
[OVHcloud documentation](https://help.ovhcloud.com/csm/en-gb-api-getting-started-ovhcloud-api?id=kb_article_view&sysparm_article=KB0042784#advanced-usage-pair-ovhcloud-apis-with-an-application)
|
||||
you will have your `Application key` and `Application secret`
|
||||
|
||||
And you will need to generate your consumer key, here the permissions needed :
|
||||
|
||||
- GET on `/domain/zone`
|
||||
- GET on `/domain/zone/*/record`
|
||||
- GET on `/domain/zone/*/record/*`
|
||||
- PUT on `/domain/zone/*/record/*`
|
||||
- POST on `/domain/zone/*/record`
|
||||
- DELETE on `/domain/zone/*/record/*`
|
||||
- GET on `/domain/zone/*/soa`
|
||||
@ -51,6 +52,10 @@ curl -XPOST -H "X-Ovh-Application: <ApplicationKey>" -H "Content-type: applicati
|
||||
"method": "GET",
|
||||
"path": "/domain/zone/*/record/*"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/domain/zone/*/record/*"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/domain/zone/*/record"
|
||||
@ -223,7 +228,7 @@ spec:
|
||||
|
||||
**A note about annotations**
|
||||
|
||||
Verify that the annotation on the service uses the same hostname as the OVH DNS zone created above. The annotation may also be a subdomain of the DNS zone (e.g. 'www.example.com').
|
||||
Verify that the annotation on the service uses the same hostname as the OVHcloud DNS zone created above. The annotation may also be a subdomain of the DNS zone (e.g. 'www.example.com').
|
||||
|
||||
The TTL annotation can be used to configure the TTL on DNS records managed by ExternalDNS and is optional. If this annotation is not set, the TTL on records managed by ExternalDNS will default to 10.
|
||||
|
||||
@ -235,11 +240,11 @@ ExternalDNS uses the hostname annotation to determine which services should be r
|
||||
kubectl create -f nginx.yaml
|
||||
```
|
||||
|
||||
Depending on where you run your service, it may take some time for your cloud provider to create an external IP for the service. Once an external IP is assigned, ExternalDNS detects the new service IP address and synchronizes the OVH DNS records.
|
||||
Depending on where you run your service, it may take some time for your cloud provider to create an external IP for the service. Once an external IP is assigned, ExternalDNS detects the new service IP address and synchronizes the OVHcloud DNS records.
|
||||
|
||||
## Verifying OVH DNS records
|
||||
## Verifying OVHcloud DNS records
|
||||
|
||||
Use the OVH manager or API to verify that the A record for your domain shows the external IP address of the services.
|
||||
Use the OVHcloud manager or API to verify that the A record for your domain shows the external IP address of the services.
|
||||
|
||||
## Cleanup
|
||||
|
||||
|
@ -46,7 +46,7 @@ func (f *Flags) addFlag(name, description string) {
|
||||
|
||||
// It generates a markdown file
|
||||
// with the supported flags and writes it to the 'docs/flags.md' file.
|
||||
// to re-generate `docs/flags.md` execute 'go run internal/gen/main.go'
|
||||
// to re-generate `docs/flags.md` execute 'go run internal/gen/docs/flags/main.go'
|
||||
func main() {
|
||||
testPath, _ := os.Getwd()
|
||||
path := fmt.Sprintf("%s/docs/flags.md", testPath)
|
||||
|
2
main.go
2
main.go
@ -267,7 +267,7 @@ func main() {
|
||||
case "digitalocean":
|
||||
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
|
||||
case "ovh":
|
||||
p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.DryRun)
|
||||
p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.OVHEnableCNAMERelative, cfg.DryRun)
|
||||
case "linode":
|
||||
p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
|
||||
case "dnsimple":
|
||||
|
@ -131,6 +131,7 @@ type Config struct {
|
||||
InMemoryZones []string
|
||||
OVHEndpoint string
|
||||
OVHApiRateLimit int
|
||||
OVHEnableCNAMERelative bool
|
||||
PDNSServer string
|
||||
PDNSServerID string
|
||||
PDNSAPIKey string `secure:"yes"`
|
||||
@ -295,6 +296,7 @@ var defaultConfig = &Config{
|
||||
InMemoryZones: []string{},
|
||||
OVHEndpoint: "ovh-eu",
|
||||
OVHApiRateLimit: 20,
|
||||
OVHEnableCNAMERelative: false,
|
||||
PDNSServer: "http://localhost:8081",
|
||||
PDNSServerID: "localhost",
|
||||
PDNSAPIKey: "",
|
||||
@ -544,6 +546,7 @@ func App(cfg *Config) *kingpin.Application {
|
||||
app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones)
|
||||
app.Flag("ovh-endpoint", "When using the OVH provider, specify the endpoint (default: ovh-eu)").Default(defaultConfig.OVHEndpoint).StringVar(&cfg.OVHEndpoint)
|
||||
app.Flag("ovh-api-rate-limit", "When using the OVH provider, specify the API request rate limit, X operations by seconds (default: 20)").Default(strconv.Itoa(defaultConfig.OVHApiRateLimit)).IntVar(&cfg.OVHApiRateLimit)
|
||||
app.Flag("ovh-enable-cname-relative", "When using the OVH provider, specify if CNAME should be treated as relative on target without final dot (default: false)").Default(strconv.FormatBool(defaultConfig.OVHEnableCNAMERelative)).BoolVar(&cfg.OVHEnableCNAMERelative)
|
||||
app.Flag("pdns-server", "When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns)").Default(defaultConfig.PDNSServer).StringVar(&cfg.PDNSServer)
|
||||
app.Flag("pdns-server-id", "When using the PowerDNS/PDNS provider, specify the id of the server to retrieve. Should be `localhost` except when the server is behind a proxy (optional when --provider=pdns) (default: localhost)").Default(defaultConfig.PDNSServerID).StringVar(&cfg.PDNSServerID)
|
||||
app.Flag("pdns-api-key", "When using the PowerDNS/PDNS provider, specify the API key to use to authorize requests (required when --provider=pdns)").Default(defaultConfig.PDNSAPIKey).StringVar(&cfg.PDNSAPIKey)
|
||||
|
@ -20,6 +20,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -41,13 +44,12 @@ const (
|
||||
ovhDefaultTTL = 0
|
||||
ovhCreate = iota
|
||||
ovhDelete
|
||||
ovhUpdate
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrRecordToMutateNotFound when ApplyChange has to update/delete and didn't found the record in the existing zone (Change with no record ID)
|
||||
ErrRecordToMutateNotFound = errors.New("record to mutate not found in current zone")
|
||||
// ErrNoDryRun No dry run support for the moment
|
||||
ErrNoDryRun = errors.New("dry run not supported")
|
||||
)
|
||||
|
||||
// OVHProvider is an implementation of Provider for OVH DNS.
|
||||
@ -59,7 +61,15 @@ type OVHProvider struct {
|
||||
apiRateLimiter ratelimit.Limiter
|
||||
|
||||
domainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
|
||||
// DryRun enables dry-run mode
|
||||
DryRun bool
|
||||
|
||||
// EnableCNAMERelativeTarget controls if CNAME target should be sent with relative format.
|
||||
// Previous implementations of the OVHProvider always added a final dot as for absolut format.
|
||||
// Default value is false, all CNAME are transformed into absolut format.
|
||||
// Setting this to true will allow relative format to be sent to DNS zone.
|
||||
EnableCNAMERelativeTarget bool
|
||||
|
||||
// UseCache controls if the OVHProvider will cache records in memory, and serve them
|
||||
// without recontacting the OVHcloud API if the SOA of the domain zone hasn't changed.
|
||||
@ -67,16 +77,19 @@ type OVHProvider struct {
|
||||
// your refresh rate/number of records is too big, which might cause issue with the
|
||||
// provider.
|
||||
// Default value: true
|
||||
UseCache bool
|
||||
UseCache bool
|
||||
lastRunRecords []ovhRecord
|
||||
lastRunZones []string
|
||||
|
||||
cacheInstance *cache.Cache
|
||||
dnsClient dnsClient
|
||||
}
|
||||
|
||||
type ovhClient interface {
|
||||
Post(string, interface{}, interface{}) error
|
||||
Get(string, interface{}) error
|
||||
Delete(string, interface{}) error
|
||||
PostWithContext(context.Context, string, any, any) error
|
||||
PutWithContext(context.Context, string, any, any) error
|
||||
GetWithContext(context.Context, string, any) error
|
||||
DeleteWithContext(context.Context, string, any) error
|
||||
}
|
||||
|
||||
type dnsClient interface {
|
||||
@ -84,7 +97,11 @@ type dnsClient interface {
|
||||
}
|
||||
|
||||
type ovhRecordFields struct {
|
||||
ovhRecordFieldUpdate
|
||||
FieldType string `json:"fieldType"`
|
||||
}
|
||||
|
||||
type ovhRecordFieldUpdate struct {
|
||||
SubDomain string `json:"subDomain"`
|
||||
TTL int64 `json:"ttl"`
|
||||
Target string `json:"target"`
|
||||
@ -96,86 +113,160 @@ type ovhRecord struct {
|
||||
Zone string `json:"zone"`
|
||||
}
|
||||
|
||||
func (r ovhRecord) String() string {
|
||||
return "record#" + strconv.Itoa(int(r.ID)) + ": " + r.FieldType + " | " + r.SubDomain + " => " + r.Target + " (" + strconv.Itoa(int(r.TTL)) + ")"
|
||||
}
|
||||
|
||||
type ovhChange struct {
|
||||
ovhRecord
|
||||
Action int
|
||||
}
|
||||
|
||||
// NewOVHProvider initializes a new OVH DNS based Provider.
|
||||
func NewOVHProvider(ctx context.Context, domainFilter endpoint.DomainFilter, endpoint string, apiRateLimit int, dryRun bool) (*OVHProvider, error) {
|
||||
func NewOVHProvider(ctx context.Context, domainFilter endpoint.DomainFilter, endpoint string, apiRateLimit int, enableCNAMERelative, dryRun bool) (*OVHProvider, error) {
|
||||
client, err := ovh.NewEndpointClient(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.UserAgent = externaldns.Version
|
||||
client.UserAgent = "ExternalDNS/" + externaldns.Version
|
||||
|
||||
// TODO: Add Dry Run support
|
||||
if dryRun {
|
||||
return nil, ErrNoDryRun
|
||||
}
|
||||
return &OVHProvider{
|
||||
client: client,
|
||||
domainFilter: domainFilter,
|
||||
apiRateLimiter: ratelimit.New(apiRateLimit),
|
||||
DryRun: dryRun,
|
||||
cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration),
|
||||
dnsClient: new(dns.Client),
|
||||
UseCache: true,
|
||||
client: client,
|
||||
domainFilter: domainFilter,
|
||||
apiRateLimiter: ratelimit.New(apiRateLimit),
|
||||
DryRun: dryRun,
|
||||
cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration),
|
||||
dnsClient: new(dns.Client),
|
||||
UseCache: true,
|
||||
EnableCNAMERelativeTarget: enableCNAMERelative,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Records returns the list of records in all relevant zones.
|
||||
func (p *OVHProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
_, records, err := p.zonesRecords(ctx)
|
||||
zones, records, err := p.zonesRecords(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.lastRunRecords = records
|
||||
p.lastRunZones = zones
|
||||
endpoints := ovhGroupByNameAndType(records)
|
||||
log.Infof("OVH: %d endpoints have been found", len(endpoints))
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *OVHProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) (err error) {
|
||||
zones, records, err := p.zonesRecords(ctx)
|
||||
if err != nil {
|
||||
return provider.NewSoftError(err)
|
||||
func planChangesByZoneName(zones []string, changes *plan.Changes) map[string]*plan.Changes {
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, zone := range zones {
|
||||
zoneNameIDMapper.Add(zone, zone)
|
||||
}
|
||||
|
||||
zonesChangeUniques := map[string]bool{}
|
||||
|
||||
// Always refresh zones even in case of errors.
|
||||
defer func() {
|
||||
log.Debugf("OVH: %d zones will be refreshed", len(zonesChangeUniques))
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for zone := range zonesChangeUniques {
|
||||
// This is necessary because the loop variable zone is reused in each iteration of the loop,
|
||||
// and without this line, the goroutines launched by eg.Go would all reference the same zone variable.
|
||||
zone := zone
|
||||
eg.Go(func() error { return p.refresh(zone) })
|
||||
output := map[string]*plan.Changes{}
|
||||
for _, endpt := range changes.Delete {
|
||||
_, zoneName := zoneNameIDMapper.FindZone(endpt.DNSName)
|
||||
if _, ok := output[zoneName]; !ok {
|
||||
output[zoneName] = &plan.Changes{}
|
||||
}
|
||||
|
||||
if e := eg.Wait(); e != nil && err == nil { // return the error only if there is no error during the changes
|
||||
err = provider.NewSoftError(e)
|
||||
output[zoneName].Delete = append(output[zoneName].Delete, endpt)
|
||||
}
|
||||
for _, endpt := range changes.Create {
|
||||
_, zoneName := zoneNameIDMapper.FindZone(endpt.DNSName)
|
||||
if _, ok := output[zoneName]; !ok {
|
||||
output[zoneName] = &plan.Changes{}
|
||||
}
|
||||
}()
|
||||
output[zoneName].Create = append(output[zoneName].Create, endpt)
|
||||
}
|
||||
for _, endpt := range changes.UpdateOld {
|
||||
_, zoneName := zoneNameIDMapper.FindZone(endpt.DNSName)
|
||||
if _, ok := output[zoneName]; !ok {
|
||||
output[zoneName] = &plan.Changes{}
|
||||
}
|
||||
output[zoneName].UpdateOld = append(output[zoneName].UpdateOld, endpt)
|
||||
}
|
||||
for _, endpt := range changes.UpdateNew {
|
||||
_, zoneName := zoneNameIDMapper.FindZone(endpt.DNSName)
|
||||
if _, ok := output[zoneName]; !ok {
|
||||
output[zoneName] = &plan.Changes{}
|
||||
}
|
||||
output[zoneName].UpdateNew = append(output[zoneName].UpdateNew, endpt)
|
||||
}
|
||||
|
||||
allChanges := make([]ovhChange, 0, countTargets(changes.Create, changes.UpdateNew, changes.UpdateOld, changes.Delete))
|
||||
allChanges = append(allChanges, newOvhChange(ovhCreate, changes.Create, zones, records)...)
|
||||
allChanges = append(allChanges, newOvhChange(ovhCreate, changes.UpdateNew, zones, records)...)
|
||||
allChanges = append(allChanges, newOvhChange(ovhDelete, changes.UpdateOld, zones, records)...)
|
||||
allChanges = append(allChanges, newOvhChange(ovhDelete, changes.Delete, zones, records)...)
|
||||
return output
|
||||
}
|
||||
|
||||
log.Infof("OVH: %d changes will be done", len(allChanges))
|
||||
func (p OVHProvider) computeSingleZoneChanges(_ context.Context, zoneName string, existingRecords []ovhRecord, changes *plan.Changes) []ovhChange {
|
||||
allChanges := []ovhChange{}
|
||||
var computedChanges []ovhChange
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
computedChanges, existingRecords = p.newOvhChangeCreateDelete(ovhCreate, changes.Create, zoneName, existingRecords)
|
||||
allChanges = append(allChanges, computedChanges...)
|
||||
computedChanges, existingRecords = p.newOvhChangeCreateDelete(ovhDelete, changes.Delete, zoneName, existingRecords)
|
||||
allChanges = append(allChanges, computedChanges...)
|
||||
|
||||
computedChanges = p.newOvhChangeUpdate(changes.UpdateOld, changes.UpdateNew, zoneName, existingRecords)
|
||||
allChanges = append(allChanges, computedChanges...)
|
||||
|
||||
return allChanges
|
||||
}
|
||||
|
||||
func (p *OVHProvider) handleSingleZoneUpdate(ctx context.Context, zoneName string, existingRecords []ovhRecord, changes *plan.Changes) error {
|
||||
allChanges := p.computeSingleZoneChanges(ctx, zoneName, existingRecords, changes)
|
||||
log.Infof("OVH: %q: %d changes will be done", zoneName, len(allChanges))
|
||||
|
||||
eg, ctxErrGroup := errgroup.WithContext(ctx)
|
||||
for _, change := range allChanges {
|
||||
change := change
|
||||
zonesChangeUniques[change.Zone] = true
|
||||
eg.Go(func() error { return p.change(change) })
|
||||
eg.Go(func() error {
|
||||
return p.change(ctxErrGroup, change)
|
||||
})
|
||||
}
|
||||
|
||||
err := eg.Wait()
|
||||
|
||||
// do not refresh zone if errors: some records might haven't been processed yet, hence the zone will be in an inconsistent state
|
||||
// if modification of the zone was in error, invalidating the cache to make sure next run will start freshly
|
||||
if err == nil {
|
||||
err = p.refresh(ctx, zoneName)
|
||||
} else {
|
||||
p.invalidateCache(zoneName)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *OVHProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) (err error) {
|
||||
zones, records := p.lastRunZones, p.lastRunRecords
|
||||
defer func() {
|
||||
p.lastRunRecords = []ovhRecord{}
|
||||
p.lastRunZones = []string{}
|
||||
}()
|
||||
|
||||
if log.IsLevelEnabled(log.DebugLevel) {
|
||||
for _, change := range changes.Create {
|
||||
log.Debugf("OVH: changes CREATE dns:%q / targets:%v / type:%s", change.DNSName, change.Targets, change.RecordType)
|
||||
}
|
||||
for _, change := range changes.UpdateOld {
|
||||
log.Debugf("OVH: changes UPDATEOLD dns:%q / targets:%v / type:%s", change.DNSName, change.Targets, change.RecordType)
|
||||
}
|
||||
for _, change := range changes.UpdateNew {
|
||||
log.Debugf("OVH: changes UPDATENEW dns:%q / targets:%v / type:%s", change.DNSName, change.Targets, change.RecordType)
|
||||
}
|
||||
for _, change := range changes.Delete {
|
||||
log.Debugf("OVH: changes DELETE dns:%q / targets:%v / type:%s", change.DNSName, change.Targets, change.RecordType)
|
||||
}
|
||||
}
|
||||
|
||||
changesByZoneName := planChangesByZoneName(zones, changes)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
for zoneName, changes := range changesByZoneName {
|
||||
eg.Go(func() error {
|
||||
return p.handleSingleZoneUpdate(ctx, zoneName, records, changes)
|
||||
})
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return provider.NewSoftError(err)
|
||||
}
|
||||
@ -183,7 +274,7 @@ func (p *OVHProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) (
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *OVHProvider) refresh(zone string) error {
|
||||
func (p *OVHProvider) refresh(ctx context.Context, zone string) error {
|
||||
log.Debugf("OVH: Refresh %s zone", zone)
|
||||
|
||||
// Zone has been altered so we invalidate the cache
|
||||
@ -191,26 +282,50 @@ func (p *OVHProvider) refresh(zone string) error {
|
||||
p.invalidateCache(zone)
|
||||
|
||||
p.apiRateLimiter.Take()
|
||||
if err := p.client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil); err != nil {
|
||||
if p.DryRun {
|
||||
log.Infof("OVH: Dry-run: Would have refresh DNS zone %q", zone)
|
||||
return nil
|
||||
}
|
||||
if err := p.client.PostWithContext(ctx, fmt.Sprintf("/domain/zone/%s/refresh", url.PathEscape(zone)), nil, nil); err != nil {
|
||||
return provider.NewSoftError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *OVHProvider) change(change ovhChange) error {
|
||||
func (p *OVHProvider) change(ctx context.Context, change ovhChange) error {
|
||||
p.apiRateLimiter.Take()
|
||||
|
||||
switch change.Action {
|
||||
case ovhCreate:
|
||||
log.Debugf("OVH: Add an entry to %s", change.String())
|
||||
return p.client.Post(fmt.Sprintf("/domain/zone/%s/record", change.Zone), change.ovhRecordFields, nil)
|
||||
if p.DryRun {
|
||||
log.Infof("OVH: Dry-run: Would have created a DNS record for zone %s", change.Zone)
|
||||
return nil
|
||||
}
|
||||
return p.client.PostWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record", url.PathEscape(change.Zone)), change.ovhRecordFields, nil)
|
||||
case ovhDelete:
|
||||
if change.ID == 0 {
|
||||
return ErrRecordToMutateNotFound
|
||||
}
|
||||
log.Debugf("OVH: Delete an entry to %s", change.String())
|
||||
return p.client.Delete(fmt.Sprintf("/domain/zone/%s/record/%d", change.Zone, change.ID), nil)
|
||||
if p.DryRun {
|
||||
log.Infof("OVH: Dry-run: Would have deleted a DNS record for zone %s", change.Zone)
|
||||
return nil
|
||||
}
|
||||
return p.client.DeleteWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%d", url.PathEscape(change.Zone), change.ID), nil)
|
||||
case ovhUpdate:
|
||||
if change.ID == 0 {
|
||||
return ErrRecordToMutateNotFound
|
||||
}
|
||||
log.Debugf("OVH: Update an entry to %s", change.String())
|
||||
if p.DryRun {
|
||||
log.Infof("OVH: Dry-run: Would have updated a DNS record for zone %s", change.Zone)
|
||||
return nil
|
||||
}
|
||||
return p.client.PutWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%d", url.PathEscape(change.Zone), change.ID), change.ovhRecordFieldUpdate, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -220,7 +335,7 @@ func (p *OVHProvider) invalidateCache(zone string) {
|
||||
|
||||
func (p *OVHProvider) zonesRecords(ctx context.Context) ([]string, []ovhRecord, error) {
|
||||
var allRecords []ovhRecord
|
||||
zones, err := p.zones()
|
||||
zones, err := p.zones(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, provider.NewSoftError(err)
|
||||
}
|
||||
@ -229,7 +344,7 @@ func (p *OVHProvider) zonesRecords(ctx context.Context) ([]string, []ovhRecord,
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, zone := range zones {
|
||||
zone := zone
|
||||
eg.Go(func() error { return p.records(&ctx, &zone, chRecords) })
|
||||
eg.Go(func() error { return p.records(ctx, &zone, chRecords) })
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, nil, provider.NewSoftError(err)
|
||||
@ -241,12 +356,12 @@ func (p *OVHProvider) zonesRecords(ctx context.Context) ([]string, []ovhRecord,
|
||||
return zones, allRecords, nil
|
||||
}
|
||||
|
||||
func (p *OVHProvider) zones() ([]string, error) {
|
||||
func (p *OVHProvider) zones(ctx context.Context) ([]string, error) {
|
||||
zones := []string{}
|
||||
filteredZones := []string{}
|
||||
|
||||
p.apiRateLimiter.Take()
|
||||
if err := p.client.Get("/domain/zone", &zones); err != nil {
|
||||
if err := p.client.GetWithContext(ctx, "/domain/zone", &zones); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -265,22 +380,24 @@ type ovhSoa struct {
|
||||
records []ovhRecord
|
||||
}
|
||||
|
||||
func (p *OVHProvider) records(ctx *context.Context, zone *string, records chan<- []ovhRecord) error {
|
||||
func (p *OVHProvider) records(ctx context.Context, zone *string, records chan<- []ovhRecord) error {
|
||||
var recordsIds []uint64
|
||||
ovhRecords := make([]ovhRecord, len(recordsIds))
|
||||
eg, _ := errgroup.WithContext(*ctx)
|
||||
eg, ctxErrGroup := errgroup.WithContext(ctx)
|
||||
|
||||
if p.UseCache {
|
||||
if cachedSoaItf, ok := p.cacheInstance.Get(*zone + "#soa"); ok {
|
||||
cachedSoa := cachedSoaItf.(ovhSoa)
|
||||
|
||||
log.Debugf("OVH: zone %s: Checking SOA against %v", *zone, cachedSoa.Serial)
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(*zone), dns.TypeSOA)
|
||||
in, _, err := p.dnsClient.ExchangeContext(*ctx, m, strings.TrimSuffix(cachedSoa.Server, ".")+":53")
|
||||
in, _, err := p.dnsClient.ExchangeContext(ctx, m, strings.TrimSuffix(cachedSoa.Server, ".")+":53")
|
||||
if err == nil {
|
||||
if s, ok := in.Answer[0].(*dns.SOA); ok {
|
||||
// do something with t.Txt
|
||||
if s.Serial == cachedSoa.Serial {
|
||||
log.Debugf("OVH: zone %s: SOA from cache is valid", *zone)
|
||||
records <- cachedSoa.records
|
||||
return nil
|
||||
}
|
||||
@ -291,23 +408,23 @@ func (p *OVHProvider) records(ctx *context.Context, zone *string, records chan<-
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("OVH: Getting records for %s", *zone)
|
||||
log.Debugf("OVH: Getting records for %s from API", *zone)
|
||||
|
||||
p.apiRateLimiter.Take()
|
||||
var soa ovhSoa
|
||||
if p.UseCache {
|
||||
if err := p.client.Get("/domain/zone/"+*zone+"/soa", &soa); err != nil {
|
||||
if err := p.client.GetWithContext(ctx, "/domain/zone/"+url.PathEscape(*zone)+"/soa", &soa); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.client.Get(fmt.Sprintf("/domain/zone/%s/record", *zone), &recordsIds); err != nil {
|
||||
if err := p.client.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record", url.PathEscape(*zone)), &recordsIds); err != nil {
|
||||
return err
|
||||
}
|
||||
chRecords := make(chan ovhRecord, len(recordsIds))
|
||||
for _, id := range recordsIds {
|
||||
id := id
|
||||
eg.Go(func() error { return p.record(zone, id, chRecords) })
|
||||
eg.Go(func() error { return p.record(ctxErrGroup, zone, id, chRecords) })
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
@ -319,20 +436,20 @@ func (p *OVHProvider) records(ctx *context.Context, zone *string, records chan<-
|
||||
|
||||
if p.UseCache {
|
||||
soa.records = ovhRecords
|
||||
_ = p.cacheInstance.Add(*zone+"#soa", soa, time.Hour)
|
||||
_ = p.cacheInstance.Add(*zone+"#soa", soa, cache.DefaultExpiration)
|
||||
}
|
||||
|
||||
records <- ovhRecords
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *OVHProvider) record(zone *string, id uint64, records chan<- ovhRecord) error {
|
||||
func (p *OVHProvider) record(ctx context.Context, zone *string, id uint64, records chan<- ovhRecord) error {
|
||||
record := ovhRecord{}
|
||||
|
||||
log.Debugf("OVH: Getting record %d for %s", id, *zone)
|
||||
|
||||
p.apiRateLimiter.Take()
|
||||
if err := p.client.Get(fmt.Sprintf("/domain/zone/%s/record/%d", *zone, id), &record); err != nil {
|
||||
if err := p.client.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%d", url.PathEscape(*zone), id), &record); err != nil {
|
||||
return err
|
||||
}
|
||||
if provider.SupportedRecordType(record.FieldType) {
|
||||
@ -349,7 +466,7 @@ func ovhGroupByNameAndType(records []ovhRecord) []*endpoint.Endpoint {
|
||||
groups := map[string][]ovhRecord{}
|
||||
|
||||
for _, r := range records {
|
||||
groupBy := r.Zone + r.SubDomain + r.FieldType
|
||||
groupBy := r.Zone + "//" + r.SubDomain + "//" + r.FieldType
|
||||
if _, ok := groups[groupBy]; !ok {
|
||||
groups[groupBy] = []ovhRecord{}
|
||||
}
|
||||
@ -375,74 +492,214 @@ func ovhGroupByNameAndType(records []ovhRecord) []*endpoint.Endpoint {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func newOvhChange(action int, endpoints []*endpoint.Endpoint, zones []string, records []ovhRecord) []ovhChange {
|
||||
// Copy the records because we need to mutate the list.
|
||||
newRecords := make([]ovhRecord, len(records))
|
||||
copy(newRecords, records)
|
||||
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
ovhChanges := make([]ovhChange, 0, countTargets(endpoints))
|
||||
for _, zone := range zones {
|
||||
zoneNameIDMapper.Add(zone, zone)
|
||||
}
|
||||
func (p OVHProvider) newOvhChangeCreateDelete(action int, endpoints []*endpoint.Endpoint, zone string, existingRecords []ovhRecord) ([]ovhChange, []ovhRecord) {
|
||||
ovhChanges := []ovhChange{}
|
||||
toDeleteIds := []int{}
|
||||
|
||||
for _, e := range endpoints {
|
||||
zone, _ := zoneNameIDMapper.FindZone(e.DNSName)
|
||||
if zone == "" {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", e.DNSName)
|
||||
continue
|
||||
}
|
||||
for _, target := range e.Targets {
|
||||
if e.RecordType == endpoint.RecordTypeCNAME {
|
||||
target = target + "."
|
||||
}
|
||||
change := ovhChange{
|
||||
Action: action,
|
||||
ovhRecord: ovhRecord{
|
||||
Zone: zone,
|
||||
ovhRecordFields: ovhRecordFields{
|
||||
FieldType: e.RecordType,
|
||||
SubDomain: strings.TrimSuffix(e.DNSName, "."+zone),
|
||||
TTL: ovhDefaultTTL,
|
||||
Target: target,
|
||||
ovhRecordFieldUpdate: ovhRecordFieldUpdate{
|
||||
SubDomain: convertDNSNameIntoSubDomain(e.DNSName, zone),
|
||||
TTL: ovhDefaultTTL,
|
||||
Target: target,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.formatCNAMETarget(&change)
|
||||
if e.RecordTTL.IsConfigured() {
|
||||
change.TTL = int64(e.RecordTTL)
|
||||
}
|
||||
|
||||
// The Zone might have multiple records with the same target. In order to avoid applying the action to the
|
||||
// same OVH record, we remove a record from the list when a match is found.
|
||||
for i := 0; i < len(newRecords); i++ {
|
||||
rec := newRecords[i]
|
||||
if rec.Zone == change.Zone && rec.SubDomain == change.SubDomain && rec.FieldType == change.FieldType && rec.Target == change.Target {
|
||||
change.ID = rec.ID
|
||||
// Deleting this record from the list to avoid retargetting it later if a change with a similar target exists.
|
||||
newRecords = append(newRecords[:i], newRecords[i+1:]...)
|
||||
break
|
||||
if action == ovhDelete {
|
||||
for i, rec := range existingRecords {
|
||||
if rec.Zone == change.Zone && rec.SubDomain == change.SubDomain && rec.FieldType == change.FieldType && rec.Target == change.Target && !slices.Contains(toDeleteIds, i) {
|
||||
change.ID = rec.ID
|
||||
toDeleteIds = append(toDeleteIds, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ovhChanges = append(ovhChanges, change)
|
||||
}
|
||||
}
|
||||
|
||||
return ovhChanges
|
||||
}
|
||||
|
||||
func countTargets(allEndpoints ...[]*endpoint.Endpoint) int {
|
||||
count := 0
|
||||
for _, endpoints := range allEndpoints {
|
||||
for _, endpoint := range endpoints {
|
||||
count += len(endpoint.Targets)
|
||||
if len(toDeleteIds) > 0 {
|
||||
// Copy the records because we need to mutate the list.
|
||||
existingRecords = slices.Clone(existingRecords)
|
||||
alreadyRemoved := 0
|
||||
for _, id := range toDeleteIds {
|
||||
existingRecords = slices.Delete(existingRecords, id-alreadyRemoved, id-alreadyRemoved+1)
|
||||
alreadyRemoved++
|
||||
}
|
||||
}
|
||||
return count
|
||||
|
||||
return ovhChanges, existingRecords
|
||||
}
|
||||
|
||||
func convertDNSNameIntoSubDomain(DNSName string, zoneName string) string {
|
||||
if DNSName == zoneName {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(DNSName, "."+zoneName)
|
||||
}
|
||||
|
||||
func (p OVHProvider) newOvhChangeUpdate(endpointsOld []*endpoint.Endpoint, endpointsNew []*endpoint.Endpoint, zone string, existingRecords []ovhRecord) []ovhChange {
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
zoneNameIDMapper.Add(zone, zone)
|
||||
|
||||
oldEndpointByTypeAndName := map[string]*endpoint.Endpoint{}
|
||||
newEndpointByTypeAndName := map[string]*endpoint.Endpoint{}
|
||||
oldRecordsInZone := map[string][]ovhRecord{}
|
||||
|
||||
for _, e := range endpointsOld {
|
||||
sub := convertDNSNameIntoSubDomain(e.DNSName, zone)
|
||||
oldEndpointByTypeAndName[e.RecordType+"//"+sub] = e
|
||||
}
|
||||
for _, e := range endpointsNew {
|
||||
sub := convertDNSNameIntoSubDomain(e.DNSName, zone)
|
||||
newEndpointByTypeAndName[e.RecordType+"//"+sub] = e
|
||||
}
|
||||
|
||||
for id := range oldEndpointByTypeAndName {
|
||||
for _, record := range existingRecords {
|
||||
if id == record.FieldType+"//"+record.SubDomain {
|
||||
oldRecordsInZone[id] = append(oldRecordsInZone[id], record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changes := []ovhChange{}
|
||||
|
||||
for id := range oldEndpointByTypeAndName {
|
||||
oldRecords := slices.Clone(oldRecordsInZone[id])
|
||||
endpointsNew := newEndpointByTypeAndName[id]
|
||||
|
||||
toInsertTarget := []string{}
|
||||
|
||||
for _, target := range endpointsNew.Targets {
|
||||
var toDelete int = -1
|
||||
|
||||
for i, record := range oldRecords {
|
||||
if target == record.Target {
|
||||
toDelete = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if toDelete >= 0 {
|
||||
oldRecords = slices.Delete(oldRecords, toDelete, toDelete+1)
|
||||
} else {
|
||||
toInsertTarget = append(toInsertTarget, target)
|
||||
}
|
||||
}
|
||||
|
||||
toInsertTargetToDelete := []int{}
|
||||
for i, target := range toInsertTarget {
|
||||
if len(oldRecords) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
record := oldRecords[0]
|
||||
oldRecords = slices.Delete(oldRecords, 0, 1)
|
||||
record.Target = target
|
||||
|
||||
if endpointsNew.RecordTTL.IsConfigured() {
|
||||
record.TTL = int64(endpointsNew.RecordTTL)
|
||||
} else {
|
||||
record.TTL = ovhDefaultTTL
|
||||
}
|
||||
|
||||
change := ovhChange{
|
||||
Action: ovhUpdate,
|
||||
ovhRecord: record,
|
||||
}
|
||||
p.formatCNAMETarget(&change)
|
||||
changes = append(changes, change)
|
||||
toInsertTargetToDelete = append(toInsertTargetToDelete, i)
|
||||
}
|
||||
for _, i := range toInsertTargetToDelete {
|
||||
toInsertTarget = slices.Delete(toInsertTarget, i, i+1)
|
||||
}
|
||||
|
||||
if len(toInsertTarget) > 0 {
|
||||
for _, target := range toInsertTarget {
|
||||
recordTTL := int64(ovhDefaultTTL)
|
||||
if endpointsNew.RecordTTL.IsConfigured() {
|
||||
recordTTL = int64(endpointsNew.RecordTTL)
|
||||
}
|
||||
|
||||
change := ovhChange{
|
||||
Action: ovhCreate,
|
||||
ovhRecord: ovhRecord{
|
||||
Zone: zone,
|
||||
ovhRecordFields: ovhRecordFields{
|
||||
FieldType: endpointsNew.RecordType,
|
||||
ovhRecordFieldUpdate: ovhRecordFieldUpdate{
|
||||
SubDomain: convertDNSNameIntoSubDomain(endpointsNew.DNSName, zone),
|
||||
TTL: recordTTL,
|
||||
Target: target,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.formatCNAMETarget(&change)
|
||||
changes = append(changes, change)
|
||||
}
|
||||
}
|
||||
|
||||
if len(oldRecords) > 0 {
|
||||
for i := range oldRecords {
|
||||
changes = append(changes, ovhChange{
|
||||
Action: ovhDelete,
|
||||
ovhRecord: oldRecords[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func (c *ovhChange) String() string {
|
||||
if c.ID != 0 {
|
||||
return fmt.Sprintf("%s zone (ID : %d) : %s %d IN %s %s", c.Zone, c.ID, c.SubDomain, c.TTL, c.FieldType, c.Target)
|
||||
var action string
|
||||
switch c.Action {
|
||||
case ovhCreate:
|
||||
action = "create"
|
||||
case ovhUpdate:
|
||||
action = "update"
|
||||
case ovhDelete:
|
||||
action = "delete"
|
||||
}
|
||||
return fmt.Sprintf("%s zone : %s %d IN %s %s", c.Zone, c.SubDomain, c.TTL, c.FieldType, c.Target)
|
||||
|
||||
if c.ID != 0 {
|
||||
return fmt.Sprintf("%s zone (ID : %d) action(%s) : %s %d IN %s %s", c.Zone, c.ID, action, c.SubDomain, c.TTL, c.FieldType, c.Target)
|
||||
}
|
||||
return fmt.Sprintf("%s zone action(%s) : %s %d IN %s %s", c.Zone, action, c.SubDomain, c.TTL, c.FieldType, c.Target)
|
||||
}
|
||||
|
||||
func (p OVHProvider) formatCNAMETarget(change *ovhChange) {
|
||||
if change.FieldType != endpoint.RecordTypeCNAME {
|
||||
return
|
||||
}
|
||||
|
||||
if p.EnableCNAMERelativeTarget {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(change.Target, ".") {
|
||||
return
|
||||
}
|
||||
|
||||
change.Target += "."
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/maxatome/go-testdeep/td"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/ovh/go-ovh/ovh"
|
||||
"github.com/patrickmn/go-cache"
|
||||
@ -38,21 +39,28 @@ type mockOvhClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (c *mockOvhClient) Post(endpoint string, input interface{}, output interface{}) error {
|
||||
func (c *mockOvhClient) PostWithContext(ctx context.Context, endpoint string, input interface{}, output interface{}) error {
|
||||
stub := c.Called(endpoint, input)
|
||||
data, _ := json.Marshal(stub.Get(0))
|
||||
json.Unmarshal(data, output)
|
||||
return stub.Error(1)
|
||||
}
|
||||
|
||||
func (c *mockOvhClient) Get(endpoint string, output interface{}) error {
|
||||
func (c *mockOvhClient) PutWithContext(ctx context.Context, endpoint string, input interface{}, output interface{}) error {
|
||||
stub := c.Called(endpoint, input)
|
||||
data, _ := json.Marshal(stub.Get(0))
|
||||
json.Unmarshal(data, output)
|
||||
return stub.Error(1)
|
||||
}
|
||||
|
||||
func (c *mockOvhClient) GetWithContext(ctx context.Context, endpoint string, output interface{}) error {
|
||||
stub := c.Called(endpoint)
|
||||
data, _ := json.Marshal(stub.Get(0))
|
||||
json.Unmarshal(data, output)
|
||||
return stub.Error(1)
|
||||
}
|
||||
|
||||
func (c *mockOvhClient) Delete(endpoint string, output interface{}) error {
|
||||
func (c *mockOvhClient) DeleteWithContext(ctx context.Context, endpoint string, output interface{}) error {
|
||||
stub := c.Called(endpoint)
|
||||
data, _ := json.Marshal(stub.Get(0))
|
||||
json.Unmarshal(data, output)
|
||||
@ -84,16 +92,16 @@ func TestOvhZones(t *testing.T) {
|
||||
}
|
||||
|
||||
// Basic zones
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.com", "example.net"}, nil).Once()
|
||||
domains, err := provider.zones()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.com", "example.net"}, nil).Once()
|
||||
domains, err := provider.zones(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.Contains(domains, "example.com")
|
||||
assert.NotContains(domains, "example.net")
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Error on getting zones
|
||||
client.On("Get", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
domains, err = provider.zones()
|
||||
client.On("GetWithContext", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
domains, err = provider.zones(t.Context())
|
||||
assert.Error(err)
|
||||
assert.Nil(domains)
|
||||
client.AssertExpectations(t)
|
||||
@ -106,21 +114,21 @@ func TestOvhZoneRecords(t *testing.T) {
|
||||
|
||||
// Basic zones records
|
||||
t.Log("Basic zones records")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090901}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
zones, records, err := provider.zonesRecords(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090901}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
zones, records, err := provider.zonesRecords(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.ElementsMatch(zones, []string{"example.org"})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}})
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Error on getting zones list
|
||||
t.Log("Error on getting zones list")
|
||||
client.On("Get", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.Error(err)
|
||||
assert.Nil(zones)
|
||||
assert.Nil(records)
|
||||
@ -129,9 +137,9 @@ func TestOvhZoneRecords(t *testing.T) {
|
||||
// Error on getting zone SOA
|
||||
t.Log("Error on getting zone SOA")
|
||||
provider.cacheInstance = cache.New(cache.NoExpiration, cache.NoExpiration)
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.Error(err)
|
||||
assert.Nil(zones)
|
||||
assert.Nil(records)
|
||||
@ -139,10 +147,10 @@ func TestOvhZoneRecords(t *testing.T) {
|
||||
|
||||
// Error on getting zone records
|
||||
t.Log("Error on getting zone records")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090902}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090902}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.Error(err)
|
||||
assert.Nil(zones)
|
||||
assert.Nil(records)
|
||||
@ -150,11 +158,11 @@ func TestOvhZoneRecords(t *testing.T) {
|
||||
|
||||
// Error on getting zone record detail
|
||||
t.Log("Error on getting zone record detail")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090902}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/42").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090902}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/42").Return(nil, ovh.ErrAPIDown).Once()
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.Error(err)
|
||||
assert.Nil(zones)
|
||||
assert.Nil(records)
|
||||
@ -169,16 +177,16 @@ func TestOvhZoneRecordsCache(t *testing.T) {
|
||||
|
||||
// First call, cache miss
|
||||
t.Log("First call, cache miss")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090901}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090901}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
|
||||
zones, records, err := provider.zonesRecords(context.TODO())
|
||||
zones, records, err := provider.zonesRecords(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.ElementsMatch(zones, []string{"example.org"})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}})
|
||||
client.AssertExpectations(t)
|
||||
dnsClient.AssertExpectations(t)
|
||||
|
||||
@ -189,13 +197,13 @@ func TestOvhZoneRecordsCache(t *testing.T) {
|
||||
|
||||
// second call, cache hit
|
||||
t.Log("second call, cache hit")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
dnsClient.On("ExchangeContext", mock.AnythingOfType("*context.cancelCtx"), mock.AnythingOfType("*dns.Msg"), "ns.example.org:53").
|
||||
Return(&dns.Msg{Answer: []dns.RR{&dns.SOA{Serial: 2022090901}}}, nil)
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.ElementsMatch(zones, []string{"example.org"})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}})
|
||||
client.AssertExpectations(t)
|
||||
dnsClient.AssertExpectations(t)
|
||||
|
||||
@ -206,17 +214,17 @@ func TestOvhZoneRecordsCache(t *testing.T) {
|
||||
|
||||
// third call, cache out of date
|
||||
t.Log("third call, cache out of date")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
dnsClient.On("ExchangeContext", mock.AnythingOfType("*context.cancelCtx"), mock.AnythingOfType("*dns.Msg"), "ns.example.org:53").
|
||||
Return(&dns.Msg{Answer: []dns.RR{&dns.SOA{Serial: 2022090902}}}, nil)
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090902}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return([]uint64{24}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090902}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return([]uint64{24}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.ElementsMatch(zones, []string{"example.org"})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}})
|
||||
client.AssertExpectations(t)
|
||||
dnsClient.AssertExpectations(t)
|
||||
|
||||
@ -227,14 +235,14 @@ func TestOvhZoneRecordsCache(t *testing.T) {
|
||||
|
||||
// fourth call, cache hit
|
||||
t.Log("fourth call, cache hit")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
dnsClient.On("ExchangeContext", mock.AnythingOfType("*context.cancelCtx"), mock.AnythingOfType("*dns.Msg"), "ns.example.org:53").
|
||||
Return(&dns.Msg{Answer: []dns.RR{&dns.SOA{Serial: 2022090902}}}, nil)
|
||||
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.ElementsMatch(zones, []string{"example.org"})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}})
|
||||
client.AssertExpectations(t)
|
||||
dnsClient.AssertExpectations(t)
|
||||
|
||||
@ -245,18 +253,18 @@ func TestOvhZoneRecordsCache(t *testing.T) {
|
||||
|
||||
// fifth call, dns issue
|
||||
t.Log("fourth call, cache hit")
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org"}, nil).Once()
|
||||
dnsClient.On("ExchangeContext", mock.AnythingOfType("*context.cancelCtx"), mock.AnythingOfType("*dns.Msg"), "ns.example.org:53").
|
||||
Return(&dns.Msg{Answer: []dns.RR{}}, errors.New("dns issue"))
|
||||
client.On("Get", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090903}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/soa").Return(ovhSoa{Server: "ns.example.org.", Serial: 2022090903}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
|
||||
zones, records, err = provider.zonesRecords(context.TODO())
|
||||
zones, records, err = provider.zonesRecords(t.Context())
|
||||
assert.NoError(err)
|
||||
assert.ElementsMatch(zones, []string{"example.org"})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "NS", TTL: 10, Target: "203.0.113.42"}}})
|
||||
assert.ElementsMatch(records, []ovhRecord{{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, {ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "NS", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}})
|
||||
client.AssertExpectations(t)
|
||||
dnsClient.AssertExpectations(t)
|
||||
}
|
||||
@ -267,14 +275,14 @@ func TestOvhRecords(t *testing.T) {
|
||||
provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
|
||||
// Basic zones records
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.org", "example.net"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{SubDomain: "www", FieldType: "CNAME", TTL: 10, Target: "example.org."}}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record/24").Return(ovhRecord{ID: 24, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.43"}}, nil).Once()
|
||||
endpoints, err := provider.Records(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.org", "example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/24").Return(ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.org/record/42").Return(ovhRecord{ID: 42, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "CNAME", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "www", TTL: 10, Target: "example.org."}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{24, 42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/24").Return(ovhRecord{ID: 24, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.43"}}}, nil).Once()
|
||||
endpoints, err := provider.Records(t.Context())
|
||||
assert.NoError(err)
|
||||
// Little fix for multi targets endpoint
|
||||
for _, endpoint := range endpoints {
|
||||
@ -288,25 +296,70 @@ func TestOvhRecords(t *testing.T) {
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Error getting zone
|
||||
client.On("Get", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
endpoints, err = provider.Records(context.TODO())
|
||||
client.On("GetWithContext", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
endpoints, err = provider.Records(t.Context())
|
||||
assert.Error(err)
|
||||
assert.Nil(endpoints)
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestOvhComputeChanges(t *testing.T) {
|
||||
existingRecords := []ovhRecord{
|
||||
{
|
||||
ID: 1,
|
||||
Zone: "example.net",
|
||||
ovhRecordFields: ovhRecordFields{
|
||||
FieldType: "A",
|
||||
ovhRecordFieldUpdate: ovhRecordFieldUpdate{
|
||||
SubDomain: "",
|
||||
Target: "203.0.113.42",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
changes := plan.Changes{
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", Targets: []string{"203.0.113.43", "203.0.113.42"}},
|
||||
},
|
||||
}
|
||||
|
||||
provider := &OVHProvider{client: nil, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
ovhChanges := provider.computeSingleZoneChanges(t.Context(), "example.net", existingRecords, &changes)
|
||||
td.Cmp(t, ovhChanges, []ovhChange{
|
||||
{
|
||||
Action: ovhCreate,
|
||||
ovhRecord: ovhRecord{
|
||||
Zone: "example.net",
|
||||
ovhRecordFields: ovhRecordFields{
|
||||
FieldType: "A",
|
||||
ovhRecordFieldUpdate: ovhRecordFieldUpdate{
|
||||
SubDomain: "",
|
||||
Target: "203.0.113.43",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestOvhRefresh(t *testing.T) {
|
||||
client := new(mockOvhClient)
|
||||
provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
|
||||
// Basic zone refresh
|
||||
client.On("Post", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
provider.refresh("example.net")
|
||||
client.On("PostWithContext", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
provider.refresh(t.Context(), "example.net")
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestOvhNewChange(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
provider := &OVHProvider{client: nil, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "ovh.example.net", RecordType: "A", Targets: []string{"203.0.113.43"}},
|
||||
@ -315,11 +368,11 @@ func TestOvhNewChange(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create change
|
||||
changes := newOvhChange(ovhCreate, endpoints, []string{"example.net"}, []ovhRecord{})
|
||||
assert.ElementsMatch(changes, []ovhChange{
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "", FieldType: "A", TTL: 10, Target: "203.0.113.42"}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: ovhDefaultTTL, Target: "203.0.113.43"}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh2", FieldType: "CNAME", TTL: ovhDefaultTTL, Target: "ovh.example.net."}}},
|
||||
changes, _ := provider.newOvhChangeCreateDelete(ovhCreate, endpoints, "example.net", []ovhRecord{})
|
||||
td.Cmp(t, changes, []ovhChange{
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: ovhDefaultTTL, Target: "203.0.113.43"}}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "CNAME", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh2", TTL: ovhDefaultTTL, Target: "ovh.example.net."}}}},
|
||||
})
|
||||
|
||||
// Delete change
|
||||
@ -327,70 +380,196 @@ func TestOvhNewChange(t *testing.T) {
|
||||
{DNSName: "ovh.example.net", RecordType: "A", Targets: []string{"203.0.113.42", "203.0.113.42", "203.0.113.42"}},
|
||||
}
|
||||
records := []ovhRecord{
|
||||
{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", SubDomain: "ovh", Target: "203.0.113.42"}},
|
||||
{ID: 43, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", SubDomain: "ovh", Target: "203.0.113.42"}},
|
||||
{ID: 44, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", SubDomain: "ovh", Target: "203.0.113.42"}},
|
||||
{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", Target: "203.0.113.42"}}},
|
||||
{ID: 43, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", Target: "203.0.113.42"}}},
|
||||
{ID: 44, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", Target: "203.0.113.42"}}},
|
||||
}
|
||||
changes = newOvhChange(ovhDelete, endpoints, []string{"example.net"}, records)
|
||||
assert.ElementsMatch(changes, []ovhChange{
|
||||
{Action: ovhDelete, ovhRecord: ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: ovhDefaultTTL, Target: "203.0.113.42"}}},
|
||||
{Action: ovhDelete, ovhRecord: ovhRecord{ID: 43, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: ovhDefaultTTL, Target: "203.0.113.42"}}},
|
||||
{Action: ovhDelete, ovhRecord: ovhRecord{ID: 44, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: ovhDefaultTTL, Target: "203.0.113.42"}}},
|
||||
changes, _ = provider.newOvhChangeCreateDelete(ovhDelete, endpoints, "example.net", records)
|
||||
td.Cmp(t, changes, []ovhChange{
|
||||
{Action: ovhDelete, ovhRecord: ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: ovhDefaultTTL, Target: "203.0.113.42"}}}},
|
||||
{Action: ovhDelete, ovhRecord: ovhRecord{ID: 43, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: ovhDefaultTTL, Target: "203.0.113.42"}}}},
|
||||
{Action: ovhDelete, ovhRecord: ovhRecord{ID: 44, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: ovhDefaultTTL, Target: "203.0.113.42"}}}},
|
||||
})
|
||||
|
||||
// Create change with CNAME relative
|
||||
endpoints = []*endpoint.Endpoint{
|
||||
{DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "ovh.example.net", RecordType: "A", Targets: []string{"203.0.113.43"}},
|
||||
{DNSName: "ovh2.example.net", RecordType: "CNAME", Targets: []string{"ovh"}},
|
||||
{DNSName: "test.example.org"},
|
||||
}
|
||||
|
||||
provider = &OVHProvider{client: nil, EnableCNAMERelativeTarget: true, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
changes, _ = provider.newOvhChangeCreateDelete(ovhCreate, endpoints, "example.net", []ovhRecord{})
|
||||
td.Cmp(t, changes, []ovhChange{
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: ovhDefaultTTL, Target: "203.0.113.43"}}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "CNAME", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh2", TTL: ovhDefaultTTL, Target: "ovh"}}}},
|
||||
})
|
||||
|
||||
// Test with CNAME when target has already final dot
|
||||
endpoints = []*endpoint.Endpoint{
|
||||
{DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "ovh.example.net", RecordType: "A", Targets: []string{"203.0.113.43"}},
|
||||
{DNSName: "ovh2.example.net", RecordType: "CNAME", Targets: []string{"ovh.example.com."}},
|
||||
{DNSName: "test.example.org"},
|
||||
}
|
||||
|
||||
provider = &OVHProvider{client: nil, EnableCNAMERelativeTarget: false, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
changes, _ = provider.newOvhChangeCreateDelete(ovhCreate, endpoints, "example.net", []ovhRecord{})
|
||||
td.Cmp(t, changes, []ovhChange{
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: ovhDefaultTTL, Target: "203.0.113.43"}}}},
|
||||
{Action: ovhCreate, ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "CNAME", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh2", TTL: ovhDefaultTTL, Target: "ovh.example.com."}}}},
|
||||
})
|
||||
}
|
||||
|
||||
func TestOvhApplyChanges(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
client := new(mockOvhClient)
|
||||
provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
changes := plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{DNSName: "ovh.example.net", RecordType: "A", Targets: []string{"203.0.113.43"}},
|
||||
},
|
||||
}
|
||||
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh", FieldType: "A", TTL: 10, Target: "203.0.113.43"}}, nil).Once()
|
||||
client.On("Post", "/domain/zone/example.net/record", ovhRecordFields{SubDomain: "", FieldType: "A", TTL: 10, Target: "203.0.113.42"}).Return(nil, nil).Once()
|
||||
client.On("Delete", "/domain/zone/example.net/record/42").Return(nil, nil).Once()
|
||||
client.On("Post", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.43"}}}, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/record", ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}).Return(nil, nil).Once()
|
||||
client.On("DeleteWithContext", "/domain/zone/example.net/record/42").Return(nil, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
|
||||
_, err := provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
// Basic changes
|
||||
assert.NoError(provider.ApplyChanges(context.TODO(), &changes))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Getting zones failed
|
||||
client.On("Get", "/domain/zone").Return(nil, ovh.ErrAPIDown).Once()
|
||||
assert.Error(provider.ApplyChanges(context.TODO(), &changes))
|
||||
td.CmpNoError(t, provider.ApplyChanges(t.Context(), &changes))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Apply change failed
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record").Return([]uint64{}, nil).Once()
|
||||
client.On("Post", "/domain/zone/example.net/record", ovhRecordFields{SubDomain: "", FieldType: "A", TTL: 10, Target: "203.0.113.42"}).Return(nil, ovh.ErrAPIDown).Once()
|
||||
client.On("Post", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
assert.Error(provider.ApplyChanges(context.TODO(), &plan.Changes{
|
||||
client = new(mockOvhClient)
|
||||
provider.client = client
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{}, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/record", ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}).Return(nil, ovh.ErrAPIDown).Once()
|
||||
|
||||
_, err = provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
td.CmpError(t, provider.ApplyChanges(t.Context(), &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
}))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Refresh failed
|
||||
client.On("Get", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("Get", "/domain/zone/example.net/record").Return([]uint64{}, nil).Once()
|
||||
client.On("Post", "/domain/zone/example.net/record", ovhRecordFields{SubDomain: "", FieldType: "A", TTL: 10, Target: "203.0.113.42"}).Return(nil, nil).Once()
|
||||
client.On("Post", "/domain/zone/example.net/refresh", nil).Return(nil, ovh.ErrAPIDown).Once()
|
||||
assert.Error(provider.ApplyChanges(context.TODO(), &plan.Changes{
|
||||
client = new(mockOvhClient)
|
||||
provider.client = client
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{}, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/record", ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}).Return(nil, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/refresh", nil).Return(nil, ovh.ErrAPIDown).Once()
|
||||
|
||||
_, err = provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
td.CmpError(t, provider.ApplyChanges(t.Context(), &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
}))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Test Dry-Run
|
||||
client = new(mockOvhClient)
|
||||
provider = &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration), DryRun: true}
|
||||
changes = plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{DNSName: "ovh.example.net", RecordType: "A", Targets: []string{"203.0.113.43"}},
|
||||
},
|
||||
}
|
||||
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.43"}}}, nil).Once()
|
||||
|
||||
_, err = provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
td.CmpNoError(t, provider.ApplyChanges(t.Context(), &changes))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Test Update
|
||||
client = new(mockOvhClient)
|
||||
provider = &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration), DryRun: false}
|
||||
changes = plan.Changes{
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.43"}},
|
||||
},
|
||||
}
|
||||
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("PutWithContext", "/domain/zone/example.net/record/42", ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.43"}).Return(nil, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
|
||||
_, err = provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
td.CmpNoError(t, provider.ApplyChanges(t.Context(), &changes))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Test Update DryRun
|
||||
client = new(mockOvhClient)
|
||||
provider = &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration), DryRun: true}
|
||||
changes = plan.Changes{
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.43"}},
|
||||
},
|
||||
}
|
||||
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{42}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
|
||||
_, err = provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
td.CmpNoError(t, provider.ApplyChanges(t.Context(), &changes))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Test Update 2 records => 1 record
|
||||
client = new(mockOvhClient)
|
||||
provider = &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration), DryRun: false}
|
||||
changes = plan.Changes{
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42", "203.0.113.43"}},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{DNSName: "example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.43"}},
|
||||
},
|
||||
}
|
||||
|
||||
client.On("GetWithContext", "/domain/zone").Return([]string{"example.net"}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record").Return([]uint64{42, 43}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/42").Return(ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.42"}}}, nil).Once()
|
||||
client.On("GetWithContext", "/domain/zone/example.net/record/43").Return(ovhRecord{ID: 43, Zone: "example.net", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "", TTL: 10, Target: "203.0.113.43"}}}, nil).Once()
|
||||
client.On("DeleteWithContext", "/domain/zone/example.net/record/42").Return(nil, nil).Once()
|
||||
client.On("PostWithContext", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once()
|
||||
|
||||
_, err = provider.Records(t.Context())
|
||||
td.CmpNoError(t, err)
|
||||
td.CmpNoError(t, provider.ApplyChanges(t.Context(), &changes))
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestOvhChange(t *testing.T) {
|
||||
@ -399,43 +578,44 @@ func TestOvhChange(t *testing.T) {
|
||||
provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10), cacheInstance: cache.New(cache.NoExpiration, cache.NoExpiration)}
|
||||
|
||||
// Record creation
|
||||
client.On("Post", "/domain/zone/example.net/record", ovhRecordFields{SubDomain: "ovh"}).Return(nil, nil).Once()
|
||||
assert.NoError(provider.change(ovhChange{
|
||||
client.On("PostWithContext", "/domain/zone/example.net/record", ovhRecordFields{ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh"}}).Return(nil, nil).Once()
|
||||
assert.NoError(provider.change(t.Context(), ovhChange{
|
||||
Action: ovhCreate,
|
||||
ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh"}},
|
||||
ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh"}}},
|
||||
}))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Record deletion
|
||||
client.On("Delete", "/domain/zone/example.net/record/42").Return(nil, nil).Once()
|
||||
assert.NoError(provider.change(ovhChange{
|
||||
client.On("DeleteWithContext", "/domain/zone/example.net/record/42").Return(nil, nil).Once()
|
||||
assert.NoError(provider.change(t.Context(), ovhChange{
|
||||
Action: ovhDelete,
|
||||
ovhRecord: ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh"}},
|
||||
ovhRecord: ovhRecord{ID: 42, Zone: "example.net", ovhRecordFields: ovhRecordFields{ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh"}}},
|
||||
}))
|
||||
client.AssertExpectations(t)
|
||||
|
||||
// Record deletion error
|
||||
assert.Error(provider.change(ovhChange{
|
||||
assert.Error(provider.change(t.Context(), ovhChange{
|
||||
Action: ovhDelete,
|
||||
ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{SubDomain: "ovh"}},
|
||||
ovhRecord: ovhRecord{Zone: "example.net", ovhRecordFields: ovhRecordFields{ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh"}}},
|
||||
}))
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestOvhCountTargets(t *testing.T) {
|
||||
cases := []struct {
|
||||
endpoints [][]*endpoint.Endpoint
|
||||
count int
|
||||
}{
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 1},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}, {DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 2},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target", "target"}}}}, 3},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}, {{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}}, 4},
|
||||
}
|
||||
for _, test := range cases {
|
||||
count := countTargets(test.endpoints...)
|
||||
if count != test.count {
|
||||
t.Errorf("Wrong targets counts (Should be %d, get %d)", test.count, count)
|
||||
}
|
||||
}
|
||||
func TestOvhRecordString(t *testing.T) {
|
||||
record := ovhRecord{ID: 24, Zone: "example.org", ovhRecordFields: ovhRecordFields{FieldType: "A", ovhRecordFieldUpdate: ovhRecordFieldUpdate{SubDomain: "ovh", TTL: 10, Target: "203.0.113.42"}}}
|
||||
|
||||
td.Cmp(t, record.String(), "record#24: A | ovh => 203.0.113.42 (10)")
|
||||
}
|
||||
|
||||
func TestNewOvhProvider(t *testing.T) {
|
||||
var domainFilter endpoint.DomainFilter
|
||||
_, err := NewOVHProvider(t.Context(), domainFilter, "ovh-eu", 20, false, true)
|
||||
td.CmpError(t, err)
|
||||
|
||||
t.Setenv("OVH_APPLICATION_KEY", "aaaaaa")
|
||||
t.Setenv("OVH_APPLICATION_SECRET", "bbbbbb")
|
||||
t.Setenv("OVH_CONSUMER_KEY", "cccccc")
|
||||
|
||||
_, err = NewOVHProvider(t.Context(), domainFilter, "ovh-eu", 20, false, true)
|
||||
td.CmpNoError(t, err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user