From 6647ce7b1de3dd120968ffcd2d03181b4000d3c3 Mon Sep 17 00:00:00 2001 From: Frans Kuipers Date: Thu, 27 Jun 2019 19:30:10 +0200 Subject: [PATCH] Add support for versio.nl (#909) --- README.md | 3 +- cmd/zz_gen_cmd_dnshelp.go | 24 ++ docs/content/dns/zz_gen_versio.md | 67 +++++ providers/dns/dns_providers.go | 5 +- providers/dns/versio/client.go | 127 ++++++++++ providers/dns/versio/versio.go | 155 ++++++++++++ providers/dns/versio/versio.toml | 30 +++ providers/dns/versio/versio_mock_test.go | 13 + providers/dns/versio/versio_test.go | 304 +++++++++++++++++++++++ 9 files changed, 726 insertions(+), 2 deletions(-) create mode 100644 docs/content/dns/zz_gen_versio.md create mode 100644 providers/dns/versio/client.go create mode 100644 providers/dns/versio/versio.go create mode 100644 providers/dns/versio/versio.toml create mode 100644 providers/dns/versio/versio_mock_test.go create mode 100644 providers/dns/versio/versio_test.go diff --git a/README.md b/README.md index be621b0a..d78bc6a7 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,5 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | -| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | | | +| [Versio](https://go-acme.github.io/lego/dns/versio/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) +| [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | | | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 2308aa79..a9199404 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -70,6 +70,7 @@ func allDNSCodes() string { "stackpath", "transip", "vegadns", + "versio", "vscale", "vultr", "zoneee", @@ -1218,6 +1219,29 @@ func displayDNSHelp(name string) { fmt.Fprintln(w) fmt.Fprintln(w, `More information: https://go-acme.github.io/lego/dns/vegadns`) + case "versio": + // generated from: providers/dns/versio/versio.toml + fmt.Fprintln(w, `Configuration for Versio.[nl|eu|uk].`) + fmt.Fprintln(w, `Code: 'versio'`) + fmt.Fprintln(w, `Since: 'v2.7.0'`) + fmt.Fprintln(w) + + fmt.Fprintln(w, `Credentials:`) + fmt.Fprintln(w, ` - "VERSIO_PASSWORD": Basic authentication password`) + fmt.Fprintln(w, ` - "VERSIO_USERNAME": Basic authentication username`) + fmt.Fprintln(w) + + fmt.Fprintln(w, `Additional Configuration:`) + fmt.Fprintln(w, ` - "VERSIO_ENDPOINT": The endpoint URL of the API Server`) + fmt.Fprintln(w, ` - "VERSIO_HTTP_TIMEOUT": API request timeout`) + fmt.Fprintln(w, ` - "VERSIO_POLLING_INTERVAL": Time between DNS propagation check`) + fmt.Fprintln(w, ` - "VERSIO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + fmt.Fprintln(w, ` - "VERSIO_SEQUENCE_INTERVAL": Interval between iteration, default 60s`) + fmt.Fprintln(w, ` - "VERSIO_TTL": The TTL of the TXT record used for the DNS challenge`) + + fmt.Fprintln(w) + fmt.Fprintln(w, `More information: https://go-acme.github.io/lego/dns/versio`) + case "vscale": // generated from: providers/dns/vscale/vscale.toml fmt.Fprintln(w, `Configuration for Vscale.`) diff --git a/docs/content/dns/zz_gen_versio.md b/docs/content/dns/zz_gen_versio.md new file mode 100644 index 00000000..b3c7ca52 --- /dev/null +++ b/docs/content/dns/zz_gen_versio.md @@ -0,0 +1,67 @@ +--- +title: "Versio.[nl|eu|uk]" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: versio +--- + + + + + +Since: v2.7.0 + +Configuration for [Versio.[nl|eu|uk]](https://www.versio.nl/domeinnamen). + + + + +- Code: `versio` + +Here is an example bash command using the Versio.[nl|eu|uk] provider: + +```bash +VERSIO_USERNAME= \ +VERSIO_PASSWORD= \ +lego --dns versio --domains my.domain.com --email my@email.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `VERSIO_PASSWORD` | Basic authentication password | +| `VERSIO_USERNAME` | Basic authentication username | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `VERSIO_ENDPOINT` | The endpoint URL of the API Server | +| `VERSIO_HTTP_TIMEOUT` | API request timeout | +| `VERSIO_POLLING_INTERVAL` | Time between DNS propagation check | +| `VERSIO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `VERSIO_SEQUENCE_INTERVAL` | Interval between iteration, default 60s | +| `VERSIO_TTL` | The TTL of the TXT record used for the DNS challenge | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + +To test with the sandbox environment set ```VERSIO_ENDPOINT=https://www.versio.nl/testapi/v1/``` + + + +## More information + +- [API documentation](https://www.versio.nl/RESTapidoc/) + + + + diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index 194db1d3..dd50b601 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -59,6 +59,7 @@ import ( "github.com/go-acme/lego/providers/dns/stackpath" "github.com/go-acme/lego/providers/dns/transip" "github.com/go-acme/lego/providers/dns/vegadns" + "github.com/go-acme/lego/providers/dns/versio" "github.com/go-acme/lego/providers/dns/vscale" "github.com/go-acme/lego/providers/dns/vultr" "github.com/go-acme/lego/providers/dns/zoneee" @@ -177,6 +178,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return transip.NewDNSProvider() case "vegadns": return vegadns.NewDNSProvider() + case "versio": + return versio.NewDNSProvider() case "vultr": return vultr.NewDNSProvider() case "vscale": @@ -184,6 +187,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { case "zoneee": return zoneee.NewDNSProvider() default: - return nil, fmt.Errorf("unrecognised DNS provider: %s", name) + return nil, fmt.Errorf("unrecognized DNS provider: %s", name) } } diff --git a/providers/dns/versio/client.go b/providers/dns/versio/client.go new file mode 100644 index 00000000..a0f48378 --- /dev/null +++ b/providers/dns/versio/client.go @@ -0,0 +1,127 @@ +package versio + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "path" +) + +const defaultBaseURL = "https://www.versio.nl/api/v1/" + +type dnsRecordsResponse struct { + Record dnsRecord `json:"domainInfo"` +} + +type dnsRecord struct { + DNSRecords []record `json:"dns_records"` +} + +type record struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Priority int `json:"prio,omitempty"` + TTL int `json:"ttl,omitempty"` +} + +type dnsErrorResponse struct { + Error errorMessage `json:"error"` +} + +type errorMessage struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (d *DNSProvider) postDNSRecords(domain string, msg interface{}) error { + reqBody := &bytes.Buffer{} + err := json.NewEncoder(reqBody).Encode(msg) + if err != nil { + return err + } + + req, err := d.makeRequest(http.MethodPost, "domains/"+domain+"/update", reqBody) + if err != nil { + return err + } + + return d.do(req, nil) +} + +func (d *DNSProvider) getDNSRecords(domain string) (*dnsRecordsResponse, error) { + req, err := d.makeRequest(http.MethodGet, "domains/"+domain+"?show_dns_records=true", nil) + if err != nil { + return nil, err + } + + // we'll need all the dns_records to add the new TXT record + respData := &dnsRecordsResponse{} + err = d.do(req, respData) + if err != nil { + return nil, err + } + + return respData, nil +} + +func (d *DNSProvider) makeRequest(method string, uri string, body io.Reader) (*http.Request, error) { + endpoint, err := d.config.BaseURL.Parse(path.Join(d.config.BaseURL.EscapedPath(), uri)) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(method, endpoint.String(), body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + if len(d.config.Username) > 0 && len(d.config.Password) > 0 { + req.SetBasicAuth(d.config.Username, d.config.Password) + } + + return req, nil +} + +func (d *DNSProvider) do(req *http.Request, result interface{}) error { + resp, err := d.config.HTTPClient.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + return err + } + + if resp.StatusCode >= http.StatusBadRequest { + var body []byte + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err) + } + + respError := &dnsErrorResponse{} + err = json.Unmarshal(body, respError) + if err != nil { + return fmt.Errorf("%d: request failed: %s", resp.StatusCode, string(body)) + } + return fmt.Errorf("%d: request failed: %s", resp.StatusCode, respError.Error.Message) + } + + if result != nil { + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("request failed: %v", err) + } + + if err = json.Unmarshal(content, result); err != nil { + return fmt.Errorf("%v: %s", err, content) + } + } + + return nil +} diff --git a/providers/dns/versio/versio.go b/providers/dns/versio/versio.go new file mode 100644 index 00000000..66644db1 --- /dev/null +++ b/providers/dns/versio/versio.go @@ -0,0 +1,155 @@ +// Package versio implements a DNS provider for solving the DNS-01 challenge using versio DNS. +package versio + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "github.com/go-acme/lego/challenge/dns01" + "github.com/go-acme/lego/platform/config/env" +) + +// Config is used to configure the creation of the DNSProvider +type Config struct { + BaseURL *url.URL + TTL int + Username string + Password string + PropagationTimeout time.Duration + PollingInterval time.Duration + SequenceInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + baseURL, err := url.Parse(env.GetOrDefaultString("VERSIO_ENDPOINT", defaultBaseURL)) + if err != nil { + baseURL, _ = url.Parse(defaultBaseURL) + } + + return &Config{ + BaseURL: baseURL, + TTL: env.GetOrDefaultInt("VERSIO_TTL", 300), + PropagationTimeout: env.GetOrDefaultSecond("VERSIO_PROPAGATION_TIMEOUT", 60*time.Second), + PollingInterval: env.GetOrDefaultSecond("VERSIO_POLLING_INTERVAL", 5*time.Second), + SequenceInterval: env.GetOrDefaultSecond("VERSIO_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond("VERSIO_HTTP_TIMEOUT", 30*time.Second), + }, + } +} + +// DNSProvider describes a provider for acme-proxy +type DNSProvider struct { + config *Config + dnsEntriesMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("VERSIO_USERNAME", "VERSIO_PASSWORD") + if err != nil { + return nil, fmt.Errorf("versio: %v", err) + } + + config := NewDefaultConfig() + config.Username = values["VERSIO_USERNAME"] + config.Password = values["VERSIO_PASSWORD"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider . +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("versio: the configuration of the DNS provider is nil") + } + if config.Username == "" { + return nil, errors.New("versio: the versio username is missing") + } + if config.Password == "" { + return nil, errors.New("versio: the versio password is missing") + } + + return &DNSProvider{config: config}, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return fmt.Errorf("versio: %v", err) + } + + // use mutex to prevent race condition from getDNSRecords until postDNSRecords + d.dnsEntriesMu.Lock() + defer d.dnsEntriesMu.Unlock() + + zoneName := dns01.UnFqdn(authZone) + domains, err := d.getDNSRecords(zoneName) + if err != nil { + return fmt.Errorf("versio: %v", err) + } + + txtRecord := record{ + Type: "TXT", + Name: fqdn, + Value: `"` + value + `"`, + TTL: d.config.TTL, + } + // Add new txtRercord to existing array of DNSRecords + msg := &domains.Record + msg.DNSRecords = append(msg.DNSRecords, txtRecord) + + err = d.postDNSRecords(zoneName, msg) + if err != nil { + return fmt.Errorf("versio: %v", err) + } + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, _ := dns01.GetRecord(domain, keyAuth) + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return fmt.Errorf("versio: %v", err) + } + + // use mutex to prevent race condition from getDNSRecords until postDNSRecords + d.dnsEntriesMu.Lock() + defer d.dnsEntriesMu.Unlock() + + zoneName := dns01.UnFqdn(authZone) + domains, err := d.getDNSRecords(zoneName) + if err != nil { + return fmt.Errorf("versio: %v", err) + } + + // loop through the existing entries and remove the specific record + msg := &dnsRecord{} + for _, e := range domains.Record.DNSRecords { + if e.Name != fqdn { + msg.DNSRecords = append(msg.DNSRecords, e) + } + } + + err = d.postDNSRecords(zoneName, msg) + if err != nil { + return fmt.Errorf("versio: %v", err) + } + return nil +} diff --git a/providers/dns/versio/versio.toml b/providers/dns/versio/versio.toml new file mode 100644 index 00000000..697a1ce3 --- /dev/null +++ b/providers/dns/versio/versio.toml @@ -0,0 +1,30 @@ +Name = "Versio.[nl|eu|uk]" +Description = '''''' +URL = "https://www.versio.nl/domeinnamen" +Code = "versio" +Since = "v2.7.0" + +Example = ''' +VERSIO_USERNAME= \ +VERSIO_PASSWORD= \ +lego --dns versio --domains my.domain.com --email my@email.com run +''' + +Additional = ''' +To test with the sandbox environment set ```VERSIO_ENDPOINT=https://www.versio.nl/testapi/v1/``` +''' + +[Configuration] + [Configuration.Credentials] + VERSIO_USERNAME = "Basic authentication username" + VERSIO_PASSWORD = "Basic authentication password" + [Configuration.Additional] + VERSIO_ENDPOINT = "The endpoint URL of the API Server" + VERSIO_POLLING_INTERVAL = "Time between DNS propagation check" + VERSIO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + VERSIO_HTTP_TIMEOUT = "API request timeout" + VERSIO_SEQUENCE_INTERVAL = "Interval between iteration, default 60s" + VERSIO_TTL = "The TTL of the TXT record used for the DNS challenge" + +[Links] + API = "https://www.versio.nl/RESTapidoc/" diff --git a/providers/dns/versio/versio_mock_test.go b/providers/dns/versio/versio_mock_test.go new file mode 100644 index 00000000..07dc74e8 --- /dev/null +++ b/providers/dns/versio/versio_mock_test.go @@ -0,0 +1,13 @@ +package versio + +const tokenResponseMock = ` +{ + "access_token":"699dd4ff-e381-46b8-8bf8-5de49dd56c1f", + "token_type":"bearer", + "expires_in":3600 +} +` + +const tokenFailToFindZoneMock = `{"error":{"code":401,"message":"ObjectDoesNotExist|Domain not found"}}` + +const tokenFailToCreateTXTMock = `{"error":{"code":400,"message":"ProcessError|DNS record invalid type _acme-challenge.example.eu. TST"}}` diff --git a/providers/dns/versio/versio_test.go b/providers/dns/versio/versio_test.go new file mode 100644 index 00000000..532f2d1d --- /dev/null +++ b/providers/dns/versio/versio_test.go @@ -0,0 +1,304 @@ +package versio + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/log" + "github.com/go-acme/lego/platform/tester" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testDomain = "example.com" + +var envTest = tester.NewEnvTest("VERSIO_USERNAME", "VERSIO_PASSWORD", "VERSIO_ENDPOINT").WithDomain("VERSIO_DOMAIN") + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + "VERSIO_USERNAME": "me@example.com", + "VERSIO_PASSWORD": "SECRET", + }, + }, + { + desc: "missing token", + envVars: map[string]string{ + "VERSIO_PASSWORD": "me@example.com", + }, + expected: "versio: some credentials information are missing: VERSIO_USERNAME", + }, + { + desc: "missing key", + envVars: map[string]string{ + "VERSIO_USERNAME": "TOKEN", + }, + expected: "versio: some credentials information are missing: VERSIO_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "versio: some credentials information are missing: VERSIO_USERNAME,VERSIO_PASSWORD", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + p, err := NewDNSProvider() + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + config *Config + expected string + }{ + { + desc: "success", + config: &Config{ + Username: "me@example.com", + Password: "PW", + }, + }, + { + desc: "nil config", + config: nil, + expected: "versio: the configuration of the DNS provider is nil", + }, + { + desc: "missing username", + config: &Config{ + Password: "PW", + }, + expected: "versio: the versio username is missing", + }, + { + desc: "missing password", + config: &Config{ + Username: "UN", + }, + expected: "versio: the versio password is missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p, err := NewDNSProviderConfig(test.config) + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestDNSProvider_Present(t *testing.T) { + testCases := []struct { + desc string + handler http.Handler + expectedError string + }{ + { + desc: "Success", + handler: muxSuccess(), + }, + { + desc: "FailToFindZone", + handler: muxFailToFindZone(), + expectedError: `versio: 401: request failed: ObjectDoesNotExist|Domain not found`, + }, + { + desc: "FailToCreateTXT", + handler: muxFailToCreateTXT(), + expectedError: `versio: 400: request failed: ProcessError|DNS record invalid type _acme-challenge.example.eu. TST`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + baseURL, tearDown := startTestServer(test.handler) + defer tearDown() + + envTest.Apply(map[string]string{ + "VERSIO_USERNAME": "me@example.com", + "VERSIO_PASSWORD": "secret", + "VERSIO_ENDPOINT": baseURL, + }) + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.Present(testDomain, "token", "keyAuth") + if len(test.expectedError) == 0 { + require.NoError(t, err) + } else { + assert.EqualError(t, err, test.expectedError) + } + }) + } +} + +func TestDNSProvider_CleanUp(t *testing.T) { + testCases := []struct { + desc string + handler http.Handler + expectedError string + }{ + { + desc: "Success", + handler: muxSuccess(), + }, + { + desc: "FailToFindZone", + handler: muxFailToFindZone(), + expectedError: `versio: 401: request failed: ObjectDoesNotExist|Domain not found`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + baseURL, tearDown := startTestServer(test.handler) + defer tearDown() + + envTest.Apply(map[string]string{ + "VERSIO_USERNAME": "me@example.com", + "VERSIO_PASSWORD": "secret", + "VERSIO_ENDPOINT": baseURL, + }) + + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.CleanUp(testDomain, "token", "keyAuth") + if len(test.expectedError) == 0 { + require.NoError(t, err) + } else { + require.EqualError(t, err, test.expectedError) + } + }) + } +} + +func muxSuccess() *http.ServeMux { + mux := http.NewServeMux() + + mux.HandleFunc("/domains/example.com", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && r.URL.Query().Get("show_dns_records") == "true" { + fmt.Fprint(w, tokenResponseMock) + return + } + w.WriteHeader(http.StatusBadRequest) + }) + + mux.HandleFunc("/domains/example.com/update", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + fmt.Fprint(w, tokenResponseMock) + return + } + w.WriteHeader(http.StatusBadRequest) + }) + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + log.Printf("Not Found for Request: (%+v)\n\n", r) + http.NotFound(w, r) + }) + + return mux +} + +func muxFailToFindZone() *http.ServeMux { + mux := http.NewServeMux() + + mux.HandleFunc("/domains/example.com", func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, tokenFailToFindZoneMock, http.StatusUnauthorized) + }) + + return mux +} + +func muxFailToCreateTXT() *http.ServeMux { + mux := http.NewServeMux() + + mux.HandleFunc("/domains/example.com", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && r.URL.Query().Get("show_dns_records") == "true" { + fmt.Fprint(w, tokenResponseMock) + return + } + w.WriteHeader(http.StatusBadRequest) + }) + + mux.HandleFunc("/domains/example.com/update", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + http.Error(w, tokenFailToCreateTXTMock, http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusBadRequest) + }) + + return mux +} + +func startTestServer(handler http.Handler) (string, func()) { + ts := httptest.NewServer(handler) + return ts.URL, func() { + ts.Close() + } +} + +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +}