diff --git a/README.md b/README.md index 9746e999..dd18cff8 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,14 @@ 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/) | [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) | [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/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | -| [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | -| [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/) | -| [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [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/) | -| [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | -| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [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/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | -| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | +| [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | +| [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [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/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | +| [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/) | [reg.ru](https://go-acme.github.io/lego/dns/regru/) | +| [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | +| [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [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/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | +| [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 4398fb8f..b750b454 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -59,7 +59,6 @@ func allDNSCodes() string { "joker", "lightsail", "linode", - "linodev4", "liquidweb", "luadns", "mydnsjp", @@ -1034,27 +1033,8 @@ func displayDNSHelp(name string) error { case "linode": // generated from: providers/dns/linode/linode.toml - ew.writeln(`Configuration for Linode (deprecated).`) - ew.writeln(`Code: 'linode'`) - ew.writeln(`Since: 'v0.4.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "LINODE_API_KEY": API key`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "LINODE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "LINODE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LINODE_TTL": The TTL of the TXT record used for the DNS challenge`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/linode`) - - case "linodev4": - // generated from: providers/dns/linodev4/linodev4.toml ew.writeln(`Configuration for Linode (v4).`) - ew.writeln(`Code: 'linodev4'`) + ew.writeln(`Code: 'linode'`) ew.writeln(`Since: 'v1.1.0'`) ew.writeln() @@ -1069,7 +1049,7 @@ func displayDNSHelp(name string) error { ew.writeln(` - "LINODE_TTL": The TTL of the TXT record used for the DNS challenge`) ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/linodev4`) + ew.writeln(`More information: https://go-acme.github.io/lego/dns/linode`) case "liquidweb": // generated from: providers/dns/liquidweb/liquidweb.toml diff --git a/docs/content/dns/zz_gen_linode.md b/docs/content/dns/zz_gen_linode.md index fa790a59..3910def6 100644 --- a/docs/content/dns/zz_gen_linode.md +++ b/docs/content/dns/zz_gen_linode.md @@ -1,5 +1,5 @@ --- -title: "Linode (deprecated)" +title: "Linode (v4)" date: 2019-03-03T16:39:46+01:00 draft: false slug: linode @@ -9,9 +9,9 @@ slug: linode -Since: v0.4.0 +Since: v1.1.0 -Configuration for [Linode (deprecated)](https://www.linode.com/). +Configuration for [Linode (v4)](https://www.linode.com/). @@ -29,7 +29,7 @@ _Please contribute by adding a CLI example._ | Environment Variable Name | Description | |-----------------------|-------------| -| `LINODE_API_KEY` | API key | +| `LINODE_TOKEN` | API token | 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). @@ -41,6 +41,7 @@ More information [here](/lego/dns/#configuration-and-credentials). |--------------------------------|-------------| | `LINODE_HTTP_TIMEOUT` | API request timeout | | `LINODE_POLLING_INTERVAL` | Time between DNS propagation check | +| `LINODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | | `LINODE_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. @@ -51,8 +52,8 @@ More information [here](/lego/dns/#configuration-and-credentials). ## More information -- [API documentation](https://www.linode.com/api/dns) -- [Go client](https://github.com/timewasted/linode) +- [API documentation](https://developers.linode.com/api/v4) +- [Go client](https://github.com/linode/linodego) diff --git a/docs/content/dns/zz_gen_linodev4.md b/docs/content/dns/zz_gen_linodev4.md deleted file mode 100644 index 3e71510a..00000000 --- a/docs/content/dns/zz_gen_linodev4.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "Linode (v4)" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: linodev4 ---- - - - - - -Since: v1.1.0 - -Configuration for [Linode (v4)](https://www.linode.com/). - - - - -- Code: `linodev4` - -{{% notice note %}} -_Please contribute by adding a CLI example._ -{{% /notice %}} - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `LINODE_TOKEN` | API token | - -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 | -|--------------------------------|-------------| -| `LINODE_HTTP_TIMEOUT` | API request timeout | -| `LINODE_POLLING_INTERVAL` | Time between DNS propagation check | -| `LINODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LINODE_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). - - - - -## More information - -- [API documentation](https://developers.linode.com/api/v4) -- [Go client](https://github.com/linode/linodego) - - - - diff --git a/go.mod b/go.mod index f08b49f6..14d68eb9 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,6 @@ require ( github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 github.com/sacloud/libsacloud v1.26.1 github.com/stretchr/testify v1.6.1 - github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 github.com/transip/gotransip/v6 v6.0.2 github.com/urfave/cli v1.22.1 github.com/vultr/govultr v0.4.2 diff --git a/go.sum b/go.sum index 21a2d754..d81cdfd5 100644 --- a/go.sum +++ b/go.sum @@ -337,8 +337,6 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8= -github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= github.com/transip/gotransip/v6 v6.0.2 h1:rOCMY607PYF+YvMHHtJt7eZRd0mx/uhyz6dsXWPmn+4= github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo= diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index d2d7d21c..f950bbea 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -50,7 +50,6 @@ import ( "github.com/go-acme/lego/v3/providers/dns/joker" "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/luadns" "github.com/go-acme/lego/v3/providers/dns/mydnsjp" @@ -177,10 +176,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return joker.NewDNSProvider() case "lightsail": return lightsail.NewDNSProvider() - case "linode": + case "linode", "linodev4": // "linodev4" is for compatibility with v3, must be dropped in v5 return linode.NewDNSProvider() - case "linodev4": - return linodev4.NewDNSProvider() case "liquidweb": return liquidweb.NewDNSProvider() case "luadns": diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index 331a759b..a428f4c8 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -1,15 +1,19 @@ -// Package linode implements a DNS provider for solving the DNS-01 challenge using Linode DNS. +// Package linode implements a DNS provider for solving the DNS-01 challenge using Linode DNS and Linode's APIv4 package linode import ( + "context" + "encoding/json" "errors" "fmt" + "net/http" "strings" "time" "github.com/go-acme/lego/v3/challenge/dns01" "github.com/go-acme/lego/v3/platform/config/env" - "github.com/timewasted/linode/dns" + "github.com/linode/linodego" + "golang.org/x/oauth2" ) const ( @@ -22,24 +26,30 @@ const ( const ( envNamespace = "LINODE_" - EnvAPIKey = envNamespace + "API_KEY" + EnvToken = envNamespace + "TOKEN" - EnvTTL = envNamespace + "TTL" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string - PollingInterval time.Duration - TTL int + Token string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration } // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), + TTL: env.GetOrDefaultInt(EnvTTL, minTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 0), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 0), } } @@ -51,19 +61,19 @@ type hostedZoneInfo struct { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *dns.DNS + client *linodego.Client } // NewDNSProvider returns a DNSProvider instance configured for Linode. -// Credentials must be passed in the environment variable: LINODE_API_KEY. +// Credentials must be passed in the environment variable: LINODE_TOKEN. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) + values, err := env.Get(EnvToken) if err != nil { return nil, fmt.Errorf("linode: %w", err) } config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] + config.Token = values[EnvToken] return NewDNSProviderConfig(config) } @@ -74,35 +84,49 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("linode: the configuration of the DNS provider is nil") } - if len(config.APIKey) == 0 { - return nil, errors.New("linode: credentials missing") + if len(config.Token) == 0 { + return nil, errors.New("linode: Linode Access Token missing") } if config.TTL < minTTL { return nil, fmt.Errorf("linode: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) } + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.Token}) + oauth2Client := &http.Client{ + Timeout: config.HTTPTimeout, + Transport: &oauth2.Transport{ + Source: tokenSource, + }, + } + + client := linodego.NewClient(oauth2Client) + client.SetUserAgent(fmt.Sprintf("lego-dns linodego/%s", linodego.Version)) + return &DNSProvider{ config: config, - client: dns.New(config.APIKey), + 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() (timeout, interval time.Duration) { - // Since Linode only updates their zone files every X minutes, we need - // to figure out how many minutes we have to wait until we hit the next - // interval of X. We then wait another couple of minutes, just to be - // safe. Hopefully at some point during all of this, the record will - // have propagated throughout Linode's network. - minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins) +func (d *DNSProvider) Timeout() (time.Duration, time.Duration) { + timeout := d.config.PropagationTimeout + if d.config.PropagationTimeout <= 0 { + // Since Linode only updates their zone files every X minutes, we need + // to figure out how many minutes we have to wait until we hit the next + // interval of X. We then wait another couple of minutes, just to be + // safe. Hopefully at some point during all of this, the record will + // have propagated throughout Linode's network. + minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins) - timeout = (time.Duration(minsRemaining) * time.Minute) + - (minTTL * time.Second) + - (dnsUpdateFudgeSecs * time.Second) - interval = d.config.PollingInterval - return + timeout = (time.Duration(minsRemaining) * time.Minute) + + (minTTL * time.Second) + + (dnsUpdateFudgeSecs * time.Second) + } + + return timeout, d.config.PollingInterval } // Present creates a TXT record using the specified parameters. @@ -113,39 +137,40 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return err } - if _, err = d.client.CreateDomainResourceTXT(zone.domainID, dns01.UnFqdn(fqdn), value, d.config.TTL); err != nil { - return err + createOpts := linodego.DomainRecordCreateOptions{ + Name: dns01.UnFqdn(fqdn), + Target: value, + TTLSec: d.config.TTL, + Type: linodego.RecordTypeTXT, } - return nil + _, err = d.client.CreateDomainRecord(context.Background(), zone.domainID, createOpts) + return err } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { fqdn, value := dns01.GetRecord(domain, keyAuth) + zone, err := d.getHostedZoneInfo(fqdn) if err != nil { return err } // Get all TXT records for the specified domain. - resources, err := d.client.GetResourcesByType(zone.domainID, "TXT") + listOpts := linodego.NewListOptions(0, "{\"type\":\"TXT\"}") + resources, err := d.client.ListDomainRecords(context.Background(), zone.domainID, listOpts) if err != nil { return err } // Remove the specified resource, if it exists. for _, resource := range resources { - if resource.Name == zone.resourceName && resource.Target == value { - resp, err := d.client.DeleteDomainResource(resource.DomainID, resource.ResourceID) - if err != nil { + if (resource.Name == strings.TrimSuffix(fqdn, ".") || resource.Name == zone.resourceName) && + resource.Target == value { + if err := d.client.DeleteDomainRecord(context.Background(), zone.domainID, resource.ID); err != nil { return err } - - if resp.ResourceID != resource.ResourceID { - return errors.New("error deleting resource: resource IDs do not match") - } - break } } @@ -159,16 +184,24 @@ func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { return nil, err } - resourceName := strings.TrimSuffix(fqdn, "."+authZone) - // Query the authority zone. - domain, err := d.client.GetDomain(dns01.UnFqdn(authZone)) + data, err := json.Marshal(map[string]string{"domain": dns01.UnFqdn(authZone)}) if err != nil { return nil, err } + listOpts := linodego.NewListOptions(0, string(data)) + domains, err := d.client.ListDomains(context.Background(), listOpts) + if err != nil { + return nil, err + } + + if len(domains) == 0 { + return nil, errors.New("domain not found") + } + return &hostedZoneInfo{ - domainID: domain.DomainID, - resourceName: resourceName, + domainID: domains[0].ID, + resourceName: strings.TrimSuffix(fqdn, "."+authZone), }, nil } diff --git a/providers/dns/linode/linode.toml b/providers/dns/linode/linode.toml index 0e912f04..a124e989 100644 --- a/providers/dns/linode/linode.toml +++ b/providers/dns/linode/linode.toml @@ -1,19 +1,20 @@ -Name = "Linode (deprecated)" +Name = "Linode (v4)" Description = '''''' URL = "https://www.linode.com/" Code = "linode" -Since = "v0.4.0" +Since = "v1.1.0" Example = '''''' [Configuration] [Configuration.Credentials] - LINODE_API_KEY = "API key" + LINODE_TOKEN = "API token" [Configuration.Additional] LINODE_POLLING_INTERVAL = "Time between DNS propagation check" + LINODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" LINODE_TTL = "The TTL of the TXT record used for the DNS challenge" LINODE_HTTP_TIMEOUT = "API request timeout" [Links] - API = "https://www.linode.com/api/dns" - GoClient = "https://github.com/timewasted/linode" + API = "https://developers.linode.com/api/v4" + GoClient = "https://github.com/linode/linodego" diff --git a/providers/dns/linode/linode_test.go b/providers/dns/linode/linode_test.go index 7323551a..d976567e 100644 --- a/providers/dns/linode/linode_test.go +++ b/providers/dns/linode/linode_test.go @@ -10,45 +10,26 @@ import ( "time" "github.com/go-acme/lego/v3/platform/tester" + "github.com/linode/linodego" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/timewasted/linode" - "github.com/timewasted/linode/dns" ) -type ( - apiResponse struct { - Action string `json:"ACTION"` - Data interface{} `json:"DATA"` - Errors []linode.ResponseError `json:"ERRORARRAY"` - } - MockResponse struct { - Response interface{} - Errors []linode.ResponseError - } - MockResponseMap map[string]MockResponse -) +type MockResponseMap map[string]interface{} -var envTest = tester.NewEnvTest(EnvAPIKey) +var envTest = tester.NewEnvTest(EnvToken) func newMockServer(responses MockResponseMap) *httptest.Server { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Ensure that we support the requested action. - action := r.URL.Query().Get("api_action") + action := r.Method + ":" + r.URL.Path resp, ok := responses[action] if !ok { http.Error(w, fmt.Sprintf("Unsupported mock action: %q", action), http.StatusInternalServerError) return } - // Build the response that the server will return. - response := apiResponse{ - Action: action, - Data: resp.Response, - Errors: resp.Errors, - } - - rawResponse, err := json.Marshal(response) + rawResponse, err := json.Marshal(resp) if err != nil { http.Error(w, fmt.Sprintf("Failed to JSON encode response: %v", err), http.StatusInternalServerError) return @@ -56,11 +37,19 @@ func newMockServer(responses MockResponseMap) *httptest.Server { // Send the response. w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + if err, ok := resp.(linodego.APIError); ok { + if err.Errors[0].Reason == "Not found" { + w.WriteHeader(http.StatusNotFound) + } else { + w.WriteHeader(http.StatusBadRequest) + } + } else { + w.WriteHeader(http.StatusOK) + } + _, err = w.Write(rawResponse) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return } })) @@ -77,15 +66,15 @@ func TestNewDNSProvider(t *testing.T) { { desc: "success", envVars: map[string]string{ - EnvAPIKey: "123", + EnvToken: "123", }, }, { desc: "missing api key", envVars: map[string]string{ - EnvAPIKey: "", + EnvToken: "", }, - expected: "linode: some credentials information are missing: LINODE_API_KEY", + expected: "linode: some credentials information are missing: LINODE_TOKEN", }, } @@ -122,14 +111,14 @@ func TestNewDNSProviderConfig(t *testing.T) { }, { desc: "missing credentials", - expected: "linode: credentials missing", + expected: "linode: Linode Access Token missing", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() - config.APIKey = test.apiKey + config.Token = test.apiKey p, err := NewDNSProviderConfig(config) @@ -147,10 +136,11 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() - os.Setenv(EnvAPIKey, "testing") + os.Setenv(EnvToken, "testing") p, err := NewDNSProvider() require.NoError(t, err) + require.NotNil(t, p) domain := "example.com" keyAuth := "dGVzdGluZw==" @@ -161,57 +151,57 @@ func TestDNSProvider_Present(t *testing.T) { expectedError string }{ { - desc: "success", + desc: "Success", mockResponses: MockResponseMap{ - "domain.list": MockResponse{ - Response: []dns.Domain{ - { - Domain: domain, - DomainID: 1234, - }, + "GET:/domains": linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, }, + Data: []linodego.Domain{{ + Domain: domain, + ID: 1234, + }}, }, - "domain.resource.create": MockResponse{ - Response: dns.ResourceResponse{ - ResourceID: 1234, - }, + "POST:/domains/1234/records": linodego.DomainRecord{ + ID: 1234, }, }, }, { desc: "NoDomain", mockResponses: MockResponseMap{ - "domain.list": MockResponse{ - Response: []dns.Domain{{ - Domain: "foobar.com", - DomainID: 1234, + "GET:/domains": linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Not found", }}, }, }, - expectedError: "dns: requested domain not found", + expectedError: "[404] Not found", }, { desc: "CreateFailed", mockResponses: MockResponseMap{ - "domain.list": MockResponse{ - Response: []dns.Domain{ - { - Domain: domain, - DomainID: 1234, - }, + "GET:/domains": &linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, }, + Data: []linodego.Domain{{ + Domain: "foobar.com", + ID: 1234, + }}, }, - "domain.resource.create": MockResponse{ - Response: nil, - Errors: []linode.ResponseError{ - { - Code: 1234, - Message: "Failed to create domain resource", - }, - }, + "POST:/domains/1234/records": linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Failed to create domain resource", + Field: "somefield", + }}, }, }, - expectedError: "Failed to create domain resource", + expectedError: "[400] [somefield] Failed to create domain resource", }, } @@ -220,7 +210,8 @@ func TestDNSProvider_Present(t *testing.T) { server := newMockServer(test.mockResponses) defer server.Close() - p.client.ToLinode().SetEndpoint(server.URL) + assert.NotNil(t, p.client) + p.client.SetBaseURL(server.URL) err = p.Present(domain, "", keyAuth) if len(test.expectedError) == 0 { @@ -234,7 +225,7 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { defer envTest.RestoreEnv() - os.Setenv(EnvAPIKey, "testing") + os.Setenv(EnvToken, "testing") p, err := NewDNSProvider() require.NoError(t, err) @@ -248,81 +239,85 @@ func TestDNSProvider_CleanUp(t *testing.T) { expectedError string }{ { - desc: "success", + desc: "Success", mockResponses: MockResponseMap{ - "domain.list": MockResponse{ - Response: []dns.Domain{ - { - Domain: domain, - DomainID: 1234, - }, + "GET:/domains": &linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, }, + Data: []linodego.Domain{{ + Domain: "foobar.com", + ID: 1234, + }}, }, - "domain.resource.list": MockResponse{ - Response: []dns.Resource{ - { - DomainID: 1234, - Name: "_acme-challenge", - ResourceID: 1234, - Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", - Type: "TXT", - }, - }, - }, - "domain.resource.delete": MockResponse{ - Response: dns.ResourceResponse{ - ResourceID: 1234, + "GET:/domains/1234/records": &linodego.DomainRecordsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, }, + Data: []linodego.DomainRecord{{ + ID: 1234, + Name: "_acme-challenge", + Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", + Type: "TXT", + }}, }, + "DELETE:/domains/1234/records/1234": struct{}{}, }, }, { desc: "NoDomain", mockResponses: MockResponseMap{ - "domain.list": MockResponse{ - Response: []dns.Domain{ - { - Domain: "foobar.com", - DomainID: 1234, - }, - }, + "GET:/domains": linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Not found", + }}, + }, + "GET:/domains/1234/records": linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Not found", + }}, }, }, - expectedError: "dns: requested domain not found", + expectedError: "[404] Not found", }, { desc: "DeleteFailed", mockResponses: MockResponseMap{ - "domain.list": MockResponse{ - Response: []dns.Domain{ - { - Domain: domain, - DomainID: 1234, - }, + "GET:/domains": linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, }, + Data: []linodego.Domain{{ + ID: 1234, + Domain: "example.com", + }}, }, - "domain.resource.list": MockResponse{ - Response: []dns.Resource{ - { - DomainID: 1234, - Name: "_acme-challenge", - ResourceID: 1234, - Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", - Type: "TXT", - }, + "GET:/domains/1234/records": linodego.DomainRecordsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, }, + Data: []linodego.DomainRecord{{ + ID: 1234, + Name: "_acme-challenge", + Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", + Type: "TXT", + }}, }, - "domain.resource.delete": MockResponse{ - Response: nil, - Errors: []linode.ResponseError{ - { - Code: 1234, - Message: "Failed to delete domain resource", - }, - }, + "DELETE:/domains/1234/records/1234": linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Failed to delete domain resource", + }}, }, }, - expectedError: "Failed to delete domain resource", + expectedError: "[400] Failed to delete domain resource", }, } @@ -331,7 +326,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { server := newMockServer(test.mockResponses) defer server.Close() - p.client.ToLinode().SetEndpoint(server.URL) + p.client.SetBaseURL(server.URL) err = p.CleanUp(domain, "", keyAuth) if len(test.expectedError) == 0 { diff --git a/providers/dns/linodev4/linodev4.go b/providers/dns/linodev4/linodev4.go deleted file mode 100644 index b2d8ff1f..00000000 --- a/providers/dns/linodev4/linodev4.go +++ /dev/null @@ -1,207 +0,0 @@ -// Package linodev4 implements a DNS provider for solving the DNS-01 challenge using Linode DNS and Linode's APIv4 -package linodev4 - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - "time" - - "github.com/go-acme/lego/v3/challenge/dns01" - "github.com/go-acme/lego/v3/platform/config/env" - "github.com/linode/linodego" - "golang.org/x/oauth2" -) - -const ( - minTTL = 300 - dnsUpdateFreqMins = 15 - dnsUpdateFudgeSecs = 120 -) - -// Environment variables names. -const ( - envNamespace = "LINODE_" - - EnvToken = envNamespace + "TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Token string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 0), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 0), - } -} - -type hostedZoneInfo struct { - domainID int - resourceName string -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *linodego.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for Linode. -// Credentials must be passed in the environment variable: LINODE_TOKEN. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvToken) - if err != nil { - return nil, fmt.Errorf("linodev4: %w", err) - } - - config := NewDefaultConfig() - config.Token = values[EnvToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Linode. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("linodev4: the configuration of the DNS provider is nil") - } - - if len(config.Token) == 0 { - return nil, errors.New("linodev4: Linode Access Token missing") - } - - if config.TTL < minTTL { - return nil, fmt.Errorf("linodev4: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.Token}) - oauth2Client := &http.Client{ - Timeout: config.HTTPTimeout, - Transport: &oauth2.Transport{ - Source: tokenSource, - }, - } - - client := linodego.NewClient(oauth2Client) - client.SetUserAgent(fmt.Sprintf("lego-dns linodego/%s", linodego.Version)) - - return &DNSProvider{ - config: config, - 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) { - timeout := d.config.PropagationTimeout - if d.config.PropagationTimeout <= 0 { - // Since Linode only updates their zone files every X minutes, we need - // to figure out how many minutes we have to wait until we hit the next - // interval of X. We then wait another couple of minutes, just to be - // safe. Hopefully at some point during all of this, the record will - // have propagated throughout Linode's network. - minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins) - - timeout = (time.Duration(minsRemaining) * time.Minute) + - (minTTL * time.Second) + - (dnsUpdateFudgeSecs * time.Second) - } - - return timeout, 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) - zone, err := d.getHostedZoneInfo(fqdn) - if err != nil { - return err - } - - createOpts := linodego.DomainRecordCreateOptions{ - Name: dns01.UnFqdn(fqdn), - Target: value, - TTLSec: d.config.TTL, - Type: linodego.RecordTypeTXT, - } - - _, err = d.client.CreateDomainRecord(context.Background(), zone.domainID, createOpts) - return err -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, value := dns01.GetRecord(domain, keyAuth) - - zone, err := d.getHostedZoneInfo(fqdn) - if err != nil { - return err - } - - // Get all TXT records for the specified domain. - listOpts := linodego.NewListOptions(0, "{\"type\":\"TXT\"}") - resources, err := d.client.ListDomainRecords(context.Background(), zone.domainID, listOpts) - if err != nil { - return err - } - - // Remove the specified resource, if it exists. - for _, resource := range resources { - if (resource.Name == strings.TrimSuffix(fqdn, ".") || resource.Name == zone.resourceName) && - resource.Target == value { - if err := d.client.DeleteDomainRecord(context.Background(), zone.domainID, resource.ID); err != nil { - return err - } - } - } - - return nil -} - -func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { - // Lookup the zone that handles the specified FQDN. - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return nil, err - } - - // Query the authority zone. - data, err := json.Marshal(map[string]string{"domain": dns01.UnFqdn(authZone)}) - if err != nil { - return nil, err - } - - listOpts := linodego.NewListOptions(0, string(data)) - domains, err := d.client.ListDomains(context.Background(), listOpts) - if err != nil { - return nil, err - } - - if len(domains) == 0 { - return nil, errors.New("domain not found") - } - - return &hostedZoneInfo{ - domainID: domains[0].ID, - resourceName: strings.TrimSuffix(fqdn, "."+authZone), - }, nil -} diff --git a/providers/dns/linodev4/linodev4.toml b/providers/dns/linodev4/linodev4.toml deleted file mode 100644 index 10865bb3..00000000 --- a/providers/dns/linodev4/linodev4.toml +++ /dev/null @@ -1,20 +0,0 @@ -Name = "Linode (v4)" -Description = '''''' -URL = "https://www.linode.com/" -Code = "linodev4" -Since = "v1.1.0" - -Example = '''''' - -[Configuration] - [Configuration.Credentials] - LINODE_TOKEN = "API token" - [Configuration.Additional] - LINODE_POLLING_INTERVAL = "Time between DNS propagation check" - LINODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LINODE_TTL = "The TTL of the TXT record used for the DNS challenge" - LINODE_HTTP_TIMEOUT = "API request timeout" - -[Links] - API = "https://developers.linode.com/api/v4" - GoClient = "https://github.com/linode/linodego" diff --git a/providers/dns/linodev4/linodev4_test.go b/providers/dns/linodev4/linodev4_test.go deleted file mode 100644 index 4193b319..00000000 --- a/providers/dns/linodev4/linodev4_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package linodev4 - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" - - "github.com/go-acme/lego/v3/platform/tester" - "github.com/linode/linodego" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type MockResponseMap map[string]interface{} - -var envTest = tester.NewEnvTest(EnvToken) - -func newMockServer(responses MockResponseMap) *httptest.Server { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Ensure that we support the requested action. - action := r.Method + ":" + r.URL.Path - resp, ok := responses[action] - if !ok { - http.Error(w, fmt.Sprintf("Unsupported mock action: %q", action), http.StatusInternalServerError) - return - } - - rawResponse, err := json.Marshal(resp) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to JSON encode response: %v", err), http.StatusInternalServerError) - return - } - - // Send the response. - w.Header().Set("Content-Type", "application/json") - if err, ok := resp.(linodego.APIError); ok { - if err.Errors[0].Reason == "Not found" { - w.WriteHeader(http.StatusNotFound) - } else { - w.WriteHeader(http.StatusBadRequest) - } - } else { - w.WriteHeader(http.StatusOK) - } - - _, err = w.Write(rawResponse) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - })) - - time.Sleep(100 * time.Millisecond) - return srv -} - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvToken: "123", - }, - }, - { - desc: "missing api key", - envVars: map[string]string{ - EnvToken: "", - }, - expected: "linodev4: some credentials information are missing: LINODE_TOKEN", - }, - } - - 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) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - expected string - }{ - { - desc: "success", - apiKey: "123", - }, - { - desc: "missing credentials", - expected: "linodev4: Linode Access Token missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Token = test.apiKey - - 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) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestDNSProvider_Present(t *testing.T) { - defer envTest.RestoreEnv() - os.Setenv(EnvToken, "testing") - - p, err := NewDNSProvider() - require.NoError(t, err) - require.NotNil(t, p) - - domain := "example.com" - keyAuth := "dGVzdGluZw==" - - testCases := []struct { - desc string - mockResponses MockResponseMap - expectedError string - }{ - { - desc: "Success", - mockResponses: MockResponseMap{ - "GET:/domains": linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - Domain: domain, - ID: 1234, - }}, - }, - "POST:/domains/1234/records": linodego.DomainRecord{ - ID: 1234, - }, - }, - }, - { - desc: "NoDomain", - mockResponses: MockResponseMap{ - "GET:/domains": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Not found", - }}, - }, - }, - expectedError: "[404] Not found", - }, - { - desc: "CreateFailed", - mockResponses: MockResponseMap{ - "GET:/domains": &linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - Domain: "foobar.com", - ID: 1234, - }}, - }, - "POST:/domains/1234/records": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Failed to create domain resource", - Field: "somefield", - }}, - }, - }, - expectedError: "[400] [somefield] Failed to create domain resource", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - server := newMockServer(test.mockResponses) - defer server.Close() - - assert.NotNil(t, p.client) - p.client.SetBaseURL(server.URL) - - err = p.Present(domain, "", keyAuth) - if len(test.expectedError) == 0 { - assert.NoError(t, err) - } else { - assert.EqualError(t, err, test.expectedError) - } - }) - } -} - -func TestDNSProvider_CleanUp(t *testing.T) { - defer envTest.RestoreEnv() - os.Setenv(EnvToken, "testing") - - p, err := NewDNSProvider() - require.NoError(t, err) - - domain := "example.com" - keyAuth := "dGVzdGluZw==" - - testCases := []struct { - desc string - mockResponses MockResponseMap - expectedError string - }{ - { - desc: "Success", - mockResponses: MockResponseMap{ - "GET:/domains": &linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - Domain: "foobar.com", - ID: 1234, - }}, - }, - "GET:/domains/1234/records": &linodego.DomainRecordsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.DomainRecord{{ - ID: 1234, - Name: "_acme-challenge", - Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", - Type: "TXT", - }}, - }, - "DELETE:/domains/1234/records/1234": struct{}{}, - }, - }, - { - desc: "NoDomain", - mockResponses: MockResponseMap{ - "GET:/domains": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Not found", - }}, - }, - "GET:/domains/1234/records": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Not found", - }}, - }, - }, - expectedError: "[404] Not found", - }, - { - desc: "DeleteFailed", - mockResponses: MockResponseMap{ - "GET:/domains": linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - ID: 1234, - Domain: "example.com", - }}, - }, - "GET:/domains/1234/records": linodego.DomainRecordsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.DomainRecord{{ - ID: 1234, - Name: "_acme-challenge", - Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", - Type: "TXT", - }}, - }, - "DELETE:/domains/1234/records/1234": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Failed to delete domain resource", - }}, - }, - }, - expectedError: "[400] Failed to delete domain resource", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - server := newMockServer(test.mockResponses) - defer server.Close() - - p.client.SetBaseURL(server.URL) - - err = p.CleanUp(domain, "", keyAuth) - if len(test.expectedError) == 0 { - assert.NoError(t, err) - } else { - assert.EqualError(t, err, test.expectedError) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("Skipping live test") - } - // TODO implement this test -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("Skipping live test") - } - // TODO implement this test -}