Skip to content

Commit

Permalink
exoscale: migrate to API v2 endpoints (#1682)
Browse files Browse the repository at this point in the history
  • Loading branch information
kobajagi authored Aug 4, 2022
1 parent d344d8b commit 791eb3c
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 80 deletions.
1 change: 1 addition & 0 deletions cmd/zz_gen_cmd_dnshelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ func displayDNSHelp(name string) error {
ew.writeln()

ew.writeln(`Additional Configuration:`)
ew.writeln(` - "EXOSCALE_API_ZONE": API zone`)
ew.writeln(` - "EXOSCALE_ENDPOINT": API endpoint URL`)
ew.writeln(` - "EXOSCALE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "EXOSCALE_POLLING_INTERVAL": Time between DNS propagation check`)
Expand Down
3 changes: 2 additions & 1 deletion docs/content/dns/zz_gen_exoscale.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).

| Environment Variable Name | Description |
|--------------------------------|-------------|
| `EXOSCALE_API_ZONE` | API zone |
| `EXOSCALE_ENDPOINT` | API endpoint URL |
| `EXOSCALE_HTTP_TIMEOUT` | API request timeout |
| `EXOSCALE_POLLING_INTERVAL` | Time between DNS propagation check |
Expand All @@ -63,7 +64,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).

## More information

- [API documentation](https://community.exoscale.com/documentation/dns/api/)
- [API documentation](https://openapi-v2.exoscale.com/#endpoint-dns)
- [Go client](https://github.com/exoscale/egoscale)

<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Expand Down
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/cloudflare/cloudflare-go v0.20.0
github.com/cpu/goacmedns v0.1.1
github.com/dnsimple/dnsimple-go v0.70.1
github.com/exoscale/egoscale v0.67.0
github.com/exoscale/egoscale v0.89.0
github.com/google/go-querystring v1.1.0
github.com/gophercloud/gophercloud v0.16.0
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae
Expand Down Expand Up @@ -78,21 +78,19 @@ require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.6.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/jarcoal/httpmock v1.0.8 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
Expand Down
59 changes: 42 additions & 17 deletions go.sum

Large diffs are not rendered by default.

168 changes: 112 additions & 56 deletions providers/dns/exoscale/exoscale.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
// Package exoscale implements a DNS provider for solving the DNS-01 challenge using exoscale DNS.
// Package exoscale implements a DNS provider for solving the DNS-01 challenge using Exoscale DNS.
package exoscale

import (
"context"
"errors"
"fmt"
"net/http"
"time"

"github.com/exoscale/egoscale"
egoscale "github.com/exoscale/egoscale/v2"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
)

const defaultBaseURL = "https://api.exoscale.com/dns"
// Default Exoscale API endpoint.
const defaultBaseURL = "https://api.exoscale.com/v2"

// Default Exosacle API zone.
// Each data center location hosts the API and API zone determines which one to connect to.
const defaultAPIZone = "ch-gva-2"

// Environment variables names.
const (
Expand All @@ -22,6 +26,7 @@ const (
EnvAPISecret = envNamespace + "API_SECRET"
EnvAPIKey = envNamespace + "API_KEY"
EnvEndpoint = envNamespace + "ENDPOINT"
EnvAPIZone = envNamespace + "API_ZONE"

EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
Expand All @@ -34,29 +39,27 @@ type Config struct {
APIKey string
APISecret string
Endpoint string
HTTPClient *http.Client
HTTPTimeout time.Duration
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
TTL int64
}

// NewDefaultConfig returns a default configuration for the DNSProvider.
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
TTL: int64(env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
Transport: http.DefaultTransport,
},
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 60*time.Second),
}
}

// DNSProvider implements the challenge.Provider interface.
type DNSProvider struct {
config *Config
client *egoscale.Client
config *Config
client *egoscale.Client
apiZone string
}

// NewDNSProvider Credentials must be passed in the environment variables:
Expand All @@ -78,7 +81,7 @@ func NewDNSProvider() (*DNSProvider, error) {
// NewDNSProviderConfig return a DNSProvider instance configured for Exoscale.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the DNS provider is nil")
return nil, errors.New("exoscale: the configuration of the DNS provider is nil")
}

if config.APIKey == "" || config.APISecret == "" {
Expand All @@ -89,55 +92,74 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
config.Endpoint = defaultBaseURL
}

client := egoscale.NewClient(
config.Endpoint,
client, err := egoscale.NewClient(
config.APIKey,
config.APISecret,
egoscale.WithHTTPClient(config.HTTPClient),
egoscale.ClientOptWithAPIEndpoint(config.Endpoint),
egoscale.ClientOptWithTimeout(config.HTTPTimeout),
)
if err != nil {
return nil, fmt.Errorf("exoscale: initializing client: %w", err)
}

return &DNSProvider{client: client, config: config}, nil
return &DNSProvider{
client: client,
config: config,
apiZone: env.GetOrDefaultString(EnvAPIZone, defaultAPIZone),
}, nil
}

// Present creates a TXT record to fulfill the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
ctx := context.Background()
fqdn, value := dns01.GetRecord(domain, keyAuth)
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
if err != nil {
return err
}

recordID, err := d.FindExistingRecordID(zone, recordName)
zone, err := d.findExistingZone(zoneName)
if err != nil {
return err
return fmt.Errorf("exoscale: %w", err)
}
if zone == nil {
return fmt.Errorf("exoscale: zone %q not found", zoneName)
}

if recordID == 0 {
record := egoscale.DNSRecord{
Name: recordName,
TTL: d.config.TTL,
Content: value,
RecordType: "TXT",
}
recordID, err := d.findExistingRecordID(*zone.ID, recordName)
if err != nil {
return fmt.Errorf("exoscale: %w", err)
}

_, err := d.client.CreateRecord(ctx, zone, record)
if err != nil {
return errors.New("Error while creating DNS record: " + err.Error())
}
} else {
record := egoscale.UpdateDNSRecord{
ID: recordID,
Name: recordName,
TTL: d.config.TTL,
Content: value,
RecordType: "TXT",
recordType := "TXT"

if recordID == "" {
record := egoscale.DNSDomainRecord{
Name: &recordName,
TTL: &d.config.TTL,
Content: &value,
Type: &recordType,
}

_, err := d.client.UpdateRecord(ctx, zone, record)
_, err = d.client.CreateDNSDomainRecord(ctx, d.apiZone, *zone.ID, &record)
if err != nil {
return errors.New("Error while updating DNS record: " + err.Error())
return fmt.Errorf("exoscale: error while creating DNS record: %w", err)
}

return nil
}

record := egoscale.DNSDomainRecord{
ID: &recordID,
Name: &recordName,
TTL: &d.config.TTL,
Content: &value,
Type: &recordType,
}

err = d.client.UpdateDNSDomainRecord(ctx, d.apiZone, *zone.ID, &record)
if err != nil {
return fmt.Errorf("exoscale: error while updating DNS record: %w", err)
}

return nil
Expand All @@ -147,20 +169,28 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
ctx := context.Background()
fqdn, _ := dns01.GetRecord(domain, keyAuth)
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
if err != nil {
return err
}

recordID, err := d.FindExistingRecordID(zone, recordName)
zone, err := d.findExistingZone(zoneName)
if err != nil {
return fmt.Errorf("exoscale: %w", err)
}
if zone == nil {
return fmt.Errorf("exoscale: zone %q not found", zoneName)
}

recordID, err := d.findExistingRecordID(*zone.ID, recordName)
if err != nil {
return err
}

if recordID != 0 {
err = d.client.DeleteRecord(ctx, zone, recordID)
if recordID != "" {
err = d.client.DeleteDNSDomainRecord(ctx, d.apiZone, *zone.ID, &egoscale.DNSDomainRecord{ID: &recordID})
if err != nil {
return errors.New("Error while deleting DNS record: " + err.Error())
return fmt.Errorf("exoscale: error while deleting DNS record: %w", err)
}
}

Expand All @@ -173,29 +203,55 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}

// FindExistingRecordID Query Exoscale to find an existing record for this name.
// Returns nil if no record could be found.
func (d *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, error) {
// findExistingZone Query Exoscale to find an existing zone for this name.
// Returns nil result if no zone could be found.
func (d *DNSProvider) findExistingZone(zoneName string) (*egoscale.DNSDomain, error) {
ctx := context.Background()

zones, err := d.client.ListDNSDomains(ctx, d.apiZone)
if err != nil {
return nil, fmt.Errorf("error while retrieving DNS zones: %w", err)
}

for _, zone := range zones {
if zone.UnicodeName != nil && *zone.UnicodeName == zoneName {
return &zone, nil
}
}

return nil, nil
}

// findExistingRecordID Query Exoscale to find an existing record for this name.
// Returns empty result if no record could be found.
func (d *DNSProvider) findExistingRecordID(zoneID, recordName string) (string, error) {
ctx := context.Background()
records, err := d.client.GetRecords(ctx, zone)

records, err := d.client.ListDNSDomainRecords(ctx, d.apiZone, zoneID)
if err != nil {
return -1, errors.New("Error while retrieving DNS records: " + err.Error())
return "", fmt.Errorf("error while retrieving DNS records: %w", err)
}

recordType := "TXT"
for _, record := range records {
if record.Name == recordName {
return record.ID, nil
if record.Name != nil && *record.Name == recordName &&
record.Type != nil && *record.Type == recordType {
return *record.ID, nil
}
}
return 0, nil

return "", nil
}

// FindZoneAndRecordName Extract DNS zone and DNS entry name.
func (d *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
// findZoneAndRecordName Extract DNS zone and DNS entry name.
func (d *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) {
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
if err != nil {
return "", "", err
}

zone = dns01.UnFqdn(zone)

name := dns01.UnFqdn(fqdn)
name = name[:len(name)-len("."+zone)]

Expand Down
3 changes: 2 additions & 1 deletion providers/dns/exoscale/exoscale.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ lego --email you@example.com --dns exoscale --domains my.example.org run
EXOSCALE_API_SECRET = "API secret"
[Configuration.Additional]
EXOSCALE_ENDPOINT = "API endpoint URL"
EXOSCALE_API_ZONE = "API zone"
EXOSCALE_POLLING_INTERVAL = "Time between DNS propagation check"
EXOSCALE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
EXOSCALE_TTL = "The TTL of the TXT record used for the DNS challenge"
EXOSCALE_HTTP_TIMEOUT = "API request timeout"

[Links]
API = "https://community.exoscale.com/documentation/dns/api/"
API = "https://openapi-v2.exoscale.com/#endpoint-dns"
GoClient = "https://github.com/exoscale/egoscale"
2 changes: 1 addition & 1 deletion providers/dns/exoscale/exoscale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func TestDNSProvider_FindZoneAndRecordName(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

zone, recordName, err := provider.FindZoneAndRecordName(test.fqdn, test.domain)
zone, recordName, err := provider.findZoneAndRecordName(test.fqdn, test.domain)
require.NoError(t, err)
assert.Equal(t, test.expected.zone, zone)
assert.Equal(t, test.expected.recordName, recordName)
Expand Down

0 comments on commit 791eb3c

Please sign in to comment.