From 76c850a580766893b9b587046fa3dc7b535cd09a Mon Sep 17 00:00:00 2001 From: Caige Nichols Date: Mon, 19 Aug 2019 12:41:10 -0700 Subject: [PATCH] Add Liquid Web DNS Provider (#951) --- README.md | 13 +- cmd/zz_gen_cmd_dnshelp.go | 24 ++ docs/content/dns/zz_gen_liquidweb.md | 67 ++++++ go.mod | 1 + go.sum | 3 + providers/dns/dns_providers.go | 3 + providers/dns/liquidweb/liquidweb.go | 155 ++++++++++++ providers/dns/liquidweb/liquidweb.toml | 27 +++ providers/dns/liquidweb/liquidweb_test.go | 274 ++++++++++++++++++++++ 9 files changed, 561 insertions(+), 6 deletions(-) create mode 100644 docs/content/dns/zz_gen_liquidweb.md create mode 100644 providers/dns/liquidweb/liquidweb.go create mode 100644 providers/dns/liquidweb/liquidweb.toml create mode 100644 providers/dns/liquidweb/liquidweb_test.go diff --git a/README.md b/README.md index f07adbcc..98695acb 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,10 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns) | [Linode (deprecated)](https://go-acme.github.io/lego/dns/linode/) | -| [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | -| [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | -| [NS1](https://go-acme.github.io/lego/dns/ns1/) | [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/) | [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/) | +| [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | +| [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | +| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [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/) | [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 46969b91..c3383c6b 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -50,6 +50,7 @@ func allDNSCodes() string { "lightsail", "linode", "linodev4", + "liquidweb", "mydnsjp", "namecheap", "namedotcom", @@ -835,6 +836,29 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/linodev4`) + case "liquidweb": + // generated from: providers/dns/liquidweb/liquidweb.toml + ew.writeln(`Configuration for Liquid Web.`) + ew.writeln(`Code: 'liquidweb'`) + ew.writeln(`Since: 'v3.1.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "LIQUID_WEB_PASSWORD": Storm API Password`) + ew.writeln(` - "LIQUID_WEB_USERNAME": Storm API Username`) + ew.writeln(` - "LIQUID_WEB_ZONE": DNS Zone`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "LIQUID_WEB_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`) + ew.writeln(` - "LIQUID_WEB_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "LIQUID_WEB_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "LIQUID_WEB_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LIQUID_WEB_URL": Storm API endpoint`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`) + case "mydnsjp": // generated from: providers/dns/mydnsjp/mydnsjp.toml ew.writeln(`Configuration for MyDNS.jp.`) diff --git a/docs/content/dns/zz_gen_liquidweb.md b/docs/content/dns/zz_gen_liquidweb.md new file mode 100644 index 00000000..7fb44bff --- /dev/null +++ b/docs/content/dns/zz_gen_liquidweb.md @@ -0,0 +1,67 @@ +--- +title: "Liquid Web" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: liquidweb +--- + + + + + +Since: v3.1.0 + +Configuration for [Liquid Web](https://cart.liquidweb.com/storm/api/docs/v1/). + + + + +- Code: `liquidweb` + +Here is an example bash command using the Liquid Web provider: + +```bash +LIQUID_WEB_USERNAME=someuser \ +LIQUID_WEB_PASSWORD="somepass" \ +LIQUID_ZONE=tacoman.com.net \ +lego --dns liquidweb --email someaccount@email.com --domains "foo.email.com" run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `LIQUID_WEB_PASSWORD` | Storm API Password | +| `LIQUID_WEB_USERNAME` | Storm API Username | +| `LIQUID_WEB_ZONE` | DNS Zone | + +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 | +|--------------------------------|-------------| +| `LIQUID_WEB_HTTP_TIMEOUT` | Maximum waiting time for the DNS records to be created (not verified) | +| `LIQUID_WEB_POLLING_INTERVAL` | Time between DNS propagation check | +| `LIQUID_WEB_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `LIQUID_WEB_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LIQUID_WEB_URL` | Storm API endpoint | + +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). + + + + +## More information + +- [API documentation](https://cart.liquidweb.com/storm/api/docs/v1/) + + + + diff --git a/go.mod b/go.mod index 3e6cb635..517afca3 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/labbsr0x/bindman-dns-webhook v1.0.0 github.com/labbsr0x/goh v0.0.0-20190417202808-8b16b4848295 // indirect github.com/linode/linodego v0.10.0 + github.com/liquidweb/liquidweb-go v1.6.0 github.com/miekg/dns v1.1.15 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/nrdcg/auroradns v1.0.0 diff --git a/go.sum b/go.sum index b72f579f..3cb11fb6 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,7 @@ github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9E github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -153,6 +154,8 @@ github.com/labbsr0x/goh v0.0.0-20190417202808-8b16b4848295 h1:5hXo89CXm3J5yyggBT github.com/labbsr0x/goh v0.0.0-20190417202808-8b16b4848295/go.mod h1:RBxeaayaaMmp7GxwHiKANjkg9e+rxjOm4mB5vD5rt/I= github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jvM= github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= +github.com/liquidweb/liquidweb-go v1.6.0 h1:vIj1I/Wf97fUnyirD+bi6Y63c0GiXk9nKI1+sFFl3G0= +github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index a4a97323..fe5f55cc 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -41,6 +41,7 @@ import ( "github.com/go-acme/lego/v3/providers/dns/lightsail" "github.com/go-acme/lego/v3/providers/dns/linode" "github.com/go-acme/lego/v3/providers/dns/linodev4" + "github.com/go-acme/lego/v3/providers/dns/liquidweb" "github.com/go-acme/lego/v3/providers/dns/mydnsjp" "github.com/go-acme/lego/v3/providers/dns/namecheap" "github.com/go-acme/lego/v3/providers/dns/namedotcom" @@ -141,6 +142,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return linode.NewDNSProvider() case "linodev4": return linodev4.NewDNSProvider() + case "liquidweb": + return liquidweb.NewDNSProvider() case "manual": return dns01.NewDNSProviderManual() case "mydnsjp": diff --git a/providers/dns/liquidweb/liquidweb.go b/providers/dns/liquidweb/liquidweb.go new file mode 100644 index 00000000..a4a43d22 --- /dev/null +++ b/providers/dns/liquidweb/liquidweb.go @@ -0,0 +1,155 @@ +// Package liquidweb implements a DNS provider for solving the DNS-01 challenge using Liquid Web. +package liquidweb + +import ( + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/go-acme/lego/v3/challenge/dns01" + "github.com/go-acme/lego/v3/platform/config/env" + lw "github.com/liquidweb/liquidweb-go/client" + "github.com/liquidweb/liquidweb-go/network" +) + +const defaultBaseURL = "https://api.stormondemand.com" + +// Config is used to configure the creation of the DNSProvider +type Config struct { + BaseURL string + Username string + Password string + Zone string + TTL int + PollingInterval time.Duration + PropagationTimeout time.Duration + HTTPTimeout time.Duration +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + config := &Config{ + BaseURL: defaultBaseURL, + TTL: env.GetOrDefaultInt("LIQUID_WEB_TTL", 300), + PollingInterval: env.GetOrDefaultSecond("LIQUID_WEB_POLLING_INTERVAL", 2*time.Second), + PropagationTimeout: env.GetOrDefaultSecond("LIQUID_WEB_PROPAGATION_TIMEOUT", 2*time.Minute), + HTTPTimeout: env.GetOrDefaultSecond("LIQUID_WEB_HTTP_TIMEOUT", 1*time.Minute), + } + + return config +} + +// DNSProvider is an implementation of the acme.ChallengeProvider interface +// that uses Liquid Web's REST API to manage TXT records for a domain. +type DNSProvider struct { + config *Config + client *lw.API + recordIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Liquid Web. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("LIQUID_WEB_USERNAME", "LIQUID_WEB_PASSWORD", "LIQUID_WEB_ZONE") + if err != nil { + return nil, fmt.Errorf("liquidweb: %v", err) + } + + config := NewDefaultConfig() + config.BaseURL = env.GetOrFile("LIQUID_WEB_URL") + config.Username = values["LIQUID_WEB_USERNAME"] + config.Password = values["LIQUID_WEB_PASSWORD"] + config.Zone = values["LIQUID_WEB_ZONE"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Liquid Web. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("liquidweb: the configuration of the DNS provider is nil") + } + + if config.BaseURL == "" { + config.BaseURL = defaultBaseURL + } + + if config.Zone == "" { + return nil, fmt.Errorf("liquidweb: zone is missing") + } + + if config.Username == "" { + return nil, fmt.Errorf("liquidweb: username is missing") + } + + if config.Password == "" { + return nil, fmt.Errorf("liquidweb: password is missing") + } + + // Initialize LW client. + client, err := lw.NewAPI(config.Username, config.Password, config.BaseURL, int(config.HTTPTimeout.Seconds())) + if err != nil { + return nil, fmt.Errorf("liquidweb: could not create Liquid Web API client: %v", err) + } + + return &DNSProvider{ + config: config, + recordIDs: make(map[string]int), + client: client, + }, 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() (time.Duration, time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record using the specified parameters +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + params := &network.DNSRecordParams{ + Name: dns01.UnFqdn(fqdn), + RData: strconv.Quote(value), + Type: "TXT", + Zone: d.config.Zone, + TTL: d.config.TTL, + } + + dnsEntry, err := d.client.NetworkDNS.Create(params) + if err != nil { + return fmt.Errorf("liquidweb: could not create TXT record: %v", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = int(dnsEntry.ID) + d.recordIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("liquidweb: unknown record ID for '%s'", domain) + } + + params := &network.DNSRecordParams{ID: recordID} + _, err := d.client.NetworkDNS.Delete(params) + if err != nil { + return fmt.Errorf("liquidweb: could not remove TXT record: %v", err) + } + + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + + return nil +} diff --git a/providers/dns/liquidweb/liquidweb.toml b/providers/dns/liquidweb/liquidweb.toml new file mode 100644 index 00000000..a33c1edb --- /dev/null +++ b/providers/dns/liquidweb/liquidweb.toml @@ -0,0 +1,27 @@ +Name = "Liquid Web" +Description = '''''' +URL = "https://cart.liquidweb.com/storm/api/docs/v1/" +Code = "liquidweb" +Since = "v3.1.0" + +Example = ''' +LIQUID_WEB_USERNAME=someuser \ +LIQUID_WEB_PASSWORD="somepass" \ +LIQUID_ZONE=tacoman.com.net \ +lego --dns liquidweb --email someaccount@email.com --domains "foo.email.com" run +''' + +[Configuration] + [Configuration.Credentials] + LIQUID_WEB_USERNAME = "Storm API Username" + LIQUID_WEB_PASSWORD = "Storm API Password" + LIQUID_WEB_ZONE = "DNS Zone" + [Configuration.Additional] + LIQUID_WEB_URL = "Storm API endpoint" + LIQUID_WEB_TTL = "The TTL of the TXT record used for the DNS challenge" + LIQUID_WEB_POLLING_INTERVAL = "Time between DNS propagation check" + LIQUID_WEB_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + LIQUID_WEB_HTTP_TIMEOUT = "Maximum waiting time for the DNS records to be created (not verified)" + +[Links] + API = "https://cart.liquidweb.com/storm/api/docs/v1/" diff --git a/providers/dns/liquidweb/liquidweb_test.go b/providers/dns/liquidweb/liquidweb_test.go new file mode 100644 index 00000000..a887b138 --- /dev/null +++ b/providers/dns/liquidweb/liquidweb_test.go @@ -0,0 +1,274 @@ +package liquidweb + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-acme/lego/v3/platform/tester" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var envTest = tester.NewEnvTest( + "LIQUID_WEB_URL", + "LIQUID_WEB_USERNAME", + "LIQUID_WEB_PASSWORD", + "LIQUID_WEB_ZONE"). + WithDomain("LIQUID_WEB_DOMAIN") + +func setupTest() (*DNSProvider, *http.ServeMux, func()) { + handler := http.NewServeMux() + server := httptest.NewServer(handler) + + config := NewDefaultConfig() + config.Username = "blars" + config.Password = "tacoman" + config.BaseURL = server.URL + config.Zone = "tacoman.com" + + provider, err := NewDNSProviderConfig(config) + if err != nil { + panic(err) + } + + return provider, handler, server.Close +} + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + "LIQUID_WEB_URL": "https://storm.com", + "LIQUID_WEB_USERNAME": "blars", + "LIQUID_WEB_PASSWORD": "tacoman", + "LIQUID_WEB_ZONE": "blars.com", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "liquidweb: some credentials information are missing: LIQUID_WEB_USERNAME,LIQUID_WEB_PASSWORD,LIQUID_WEB_ZONE", + }, + { + desc: "missing username", + envVars: map[string]string{ + "LIQUID_WEB_PASSWORD": "tacoman", + "LIQUID_WEB_ZONE": "blars.com", + }, + expected: "liquidweb: some credentials information are missing: LIQUID_WEB_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + "LIQUID_WEB_USERNAME": "blars", + "LIQUID_WEB_ZONE": "blars.com", + }, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_PASSWORD", + }, + { + desc: "missing zone", + envVars: map[string]string{ + "LIQUID_WEB_USERNAME": "blars", + "LIQUID_WEB_PASSWORD": "tacoman", + }, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_ZONE", + }, + } + + 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) + require.NotNil(t, p.client) + require.NotNil(t, p.recordIDs) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + username string + password string + zone string + expected string + }{ + { + desc: "success", + username: "acme", + password: "secret", + zone: "example.com", + }, + { + desc: "missing credentials", + username: "", + password: "", + zone: "", + expected: "liquidweb: zone is missing", + }, + { + desc: "missing username", + username: "", + password: "secret", + zone: "example.com", + expected: "liquidweb: username is missing", + }, + { + desc: "missing password", + username: "acme", + password: "", + zone: "example.com", + expected: "liquidweb: password is missing", + }, + { + desc: "missing zone", + username: "acme", + password: "secret", + zone: "", + expected: "liquidweb: zone is missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + config.Zone = test.zone + + p, err := NewDNSProviderConfig(config) + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + require.NotNil(t, p.client) + require.NotNil(t, p.recordIDs) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestDNSProvider_Present(t *testing.T) { + provider, mux, tearDown := setupTest() + defer tearDown() + + mux.HandleFunc("/v1/Network/DNS/Record/create", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + + username, password, ok := r.BasicAuth() + assert.Equal(t, "blars", username) + assert.Equal(t, "tacoman", password) + assert.True(t, ok) + + reqBody, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + expectedReqBody := ` + { + "params": { + "name": "_acme-challenge.tacoman.com", + "rdata": "\"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU\"", + "ttl": 300, + "type": "TXT", + "zone": "tacoman.com" + } + }` + assert.JSONEq(t, expectedReqBody, string(reqBody)) + + w.WriteHeader(http.StatusOK) + _, err = fmt.Fprintf(w, `{ + "type": "TXT", + "name": "_acme-challenge.tacoman.com", + "rdata": "\"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU\"", + "ttl": 300, + "id": 1234567, + "prio": null + }`) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) + + err := provider.Present("tacoman.com", "", "") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider, mux, tearDown := setupTest() + defer tearDown() + + mux.HandleFunc("/v1/Network/DNS/Record/delete", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + + username, password, ok := r.BasicAuth() + assert.Equal(t, "blars", username) + assert.Equal(t, "tacoman", password) + assert.True(t, ok) + + _, err := fmt.Fprintf(w, `{"deleted": "123"}`) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + provider.recordIDs["123"] = 1234567 + + err := provider.CleanUp("tacoman.com.", "123", "") + require.NoError(t, err, "fail to remove TXT record") +} + +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) + + time.Sleep(2 * time.Second) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +}