diff --git a/README.md b/README.md index 3e5e8ed6..def4aa4c 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,6 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | [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/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | +| [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 0e3723ce..8fc07f6d 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -82,6 +82,7 @@ func allDNSCodes() string { "versio", "vscale", "vultr", + "yandex", "zoneee", "zonomi", } @@ -1540,6 +1541,27 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`) + case "yandex": + // generated from: providers/dns/yandex/yandex.toml + ew.writeln(`Configuration for Yandex.`) + ew.writeln(`Code: 'yandex'`) + ew.writeln(`Since: 'v3.7.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "YANDEX_PDD_TOKEN": Basic authentication username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "YANDEX_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "YANDEX_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "YANDEX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "YANDEX_SEQUENCE_INTERVAL": Interval between iteration, default 60s`) + ew.writeln(` - "YANDEX_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/yandex`) + case "zoneee": // generated from: providers/dns/zoneee/zoneee.toml ew.writeln(`Configuration for Zone.ee.`) diff --git a/docs/content/dns/zz_gen_yandex.md b/docs/content/dns/zz_gen_yandex.md new file mode 100644 index 00000000..24e1d732 --- /dev/null +++ b/docs/content/dns/zz_gen_yandex.md @@ -0,0 +1,63 @@ +--- +title: "Yandex" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: yandex +--- + + + + + +Since: v3.7.0 + +Configuration for [Yandex](https://yandex.com/). + + + + +- Code: `yandex` + +Here is an example bash command using the Yandex provider: + +```bash +YANDEX_PDD_TOKEN= \ +lego --dns yandex --domains my.domain.com --email my@email.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `YANDEX_PDD_TOKEN` | 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 | +|--------------------------------|-------------| +| `YANDEX_HTTP_TIMEOUT` | API request timeout | +| `YANDEX_POLLING_INTERVAL` | Time between DNS propagation check | +| `YANDEX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `YANDEX_SEQUENCE_INTERVAL` | Interval between iteration, default 60s | +| `YANDEX_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://tech.yandex.com/domain/doc/concepts/api-dns-docpage/) + + + + diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index 8a1fddc7..a17a1eca 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -73,6 +73,7 @@ import ( "github.com/go-acme/lego/v3/providers/dns/versio" "github.com/go-acme/lego/v3/providers/dns/vscale" "github.com/go-acme/lego/v3/providers/dns/vultr" + "github.com/go-acme/lego/v3/providers/dns/yandex" "github.com/go-acme/lego/v3/providers/dns/zoneee" "github.com/go-acme/lego/v3/providers/dns/zonomi" ) @@ -218,6 +219,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return vultr.NewDNSProvider() case "vscale": return vscale.NewDNSProvider() + case "yandex": + return yandex.NewDNSProvider() case "zoneee": return zoneee.NewDNSProvider() case "zonomi": diff --git a/providers/dns/yandex/internal/client.go b/providers/dns/yandex/internal/client.go new file mode 100644 index 00000000..be8735b0 --- /dev/null +++ b/providers/dns/yandex/internal/client.go @@ -0,0 +1,136 @@ +package internal + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/google/go-querystring/query" +) + +const defaultBaseURL = "https://pddimp.yandex.ru/api2/admin/dns" + +const successCode = "ok" + +const pddTokenHeader = "PddToken" + +type Client struct { + HTTPClient *http.Client + BaseURL string + pddToken string +} + +func NewClient(pddToken string) (*Client, error) { + if pddToken == "" { + return nil, errors.New("PDD token is required") + } + return &Client{ + HTTPClient: &http.Client{}, + BaseURL: defaultBaseURL, + pddToken: pddToken, + }, nil +} + +func (c *Client) AddRecord(data Record) (*Record, error) { + resp, err := c.postForm("/add", data) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API response error: %d", resp.StatusCode) + } + + r := AddResponse{} + err = json.NewDecoder(resp.Body).Decode(&r) + if err != nil { + return nil, err + } + + if r.Success != successCode { + return nil, fmt.Errorf("error during record addition: %s", r.Error) + } + + return r.Record, nil +} + +func (c *Client) RemoveRecord(data Record) (int, error) { + resp, err := c.postForm("/del", data) + if err != nil { + return 0, err + } + defer func() { _ = resp.Body.Close() }() + + r := RemoveResponse{} + err = json.NewDecoder(resp.Body).Decode(&r) + if err != nil { + return 0, err + } + + if r.Success != successCode { + return 0, fmt.Errorf("error during record addition: %s", r.Error) + } + + return r.RecordID, nil +} + +func (c *Client) GetRecords(domain string) ([]Record, error) { + resp, err := c.get("/list", struct { + Domain string `url:"domain"` + }{Domain: domain}) + + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + + r := ListResponse{} + err = json.NewDecoder(resp.Body).Decode(&r) + if err != nil { + return nil, err + } + + if r.Success != successCode { + return nil, fmt.Errorf("error during record addition: %s", r.Error) + } + + return r.Records, nil +} + +func (c *Client) postForm(uri string, data interface{}) (*http.Response, error) { + values, err := query.Values(data) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, c.BaseURL+uri, strings.NewReader(values.Encode())) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set(pddTokenHeader, c.pddToken) + + return c.HTTPClient.Do(req) +} + +func (c *Client) get(uri string, data interface{}) (*http.Response, error) { + req, err := http.NewRequest(http.MethodGet, c.BaseURL+uri, nil) + if err != nil { + return nil, err + } + + req.Header.Set(pddTokenHeader, c.pddToken) + + values, err := query.Values(data) + if err != nil { + return nil, err + } + + req.URL.RawQuery = values.Encode() + + return c.HTTPClient.Do(req) +} diff --git a/providers/dns/yandex/internal/client_test.go b/providers/dns/yandex/internal/client_test.go new file mode 100644 index 00000000..ce0e9a8f --- /dev/null +++ b/providers/dns/yandex/internal/client_test.go @@ -0,0 +1,305 @@ +package internal + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupClientTest() (*http.ServeMux, *Client, func()) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + client, err := NewClient("lego") + if err != nil { + panic(err) + } + + client.BaseURL = server.URL + return mux, client, server.Close +} + +func TestAddRecord(t *testing.T) { + testCases := []struct { + desc string + handler http.HandlerFunc + data Record + expectError bool + }{ + { + desc: "success", + handler: func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + assert.Equal(t, `content=txtTXTtxtTXTtxtTXT&domain=example.com&subdomain=foo&ttl=300&type=TXT`, r.PostForm.Encode()) + + response := AddResponse{ + Domain: "example.com", + Record: &Record{ + ID: 1, + Type: "TXT", + Domain: "example.com", + SubDomain: "foo", + FQDN: "foo.example.com.", + Content: "txtTXTtxtTXTtxtTXT", + TTL: 300, + }, + Success: "ok", + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + data: Record{ + Domain: "example.com", + Type: "TXT", + Content: "txtTXTtxtTXTtxtTXT", + SubDomain: "foo", + TTL: 300, + }, + }, + { + desc: "error", + handler: func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + assert.Equal(t, `content=txtTXTtxtTXTtxtTXT&domain=example.com&subdomain=foo&ttl=300&type=TXT`, r.PostForm.Encode()) + + response := AddResponse{ + Domain: "example.com", + Success: "error", + Error: "bad things", + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + data: Record{ + Domain: "example.com", + Type: "TXT", + Content: "txtTXTtxtTXTtxtTXT", + SubDomain: "foo", + TTL: 300, + }, + expectError: true, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mux, client, tearDown := setupClientTest() + defer tearDown() + + mux.HandleFunc("/add", test.handler) + + record, err := client.AddRecord(test.data) + if test.expectError { + require.Error(t, err) + require.Nil(t, record) + } else { + require.NoError(t, err) + require.NotNil(t, record) + } + }) + } +} + +func TestRemoveRecord(t *testing.T) { + testCases := []struct { + desc string + handler http.HandlerFunc + data Record + expectError bool + }{ + { + desc: "success", + handler: func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + assert.Equal(t, `domain=example.com&record_id=6`, r.PostForm.Encode()) + + response := RemoveResponse{ + Domain: "example.com", + RecordID: 6, + Success: "ok", + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + data: Record{ + ID: 6, + Domain: "example.com", + }, + }, + { + desc: "error", + handler: func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + assert.Equal(t, `domain=example.com&record_id=6`, r.PostForm.Encode()) + + response := RemoveResponse{ + Domain: "example.com", + RecordID: 6, + Success: "error", + Error: "bad things", + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + data: Record{ + ID: 6, + Domain: "example.com", + }, + expectError: true, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mux, client, tearDown := setupClientTest() + defer tearDown() + + mux.HandleFunc("/del", test.handler) + + id, err := client.RemoveRecord(test.data) + if test.expectError { + require.Error(t, err) + require.Equal(t, 0, id) + } else { + require.NoError(t, err) + require.Equal(t, 6, id) + } + }) + } +} + +func TestGetRecords(t *testing.T) { + testCases := []struct { + desc string + handler http.HandlerFunc + domain string + expectError bool + }{ + { + desc: "success", + handler: func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + + assert.Equal(t, "domain=example.com", r.URL.RawQuery) + + response := ListResponse{ + Domain: "example.com", + Records: []Record{ + { + ID: 1, + Type: "TXT", + Domain: "example.com", + SubDomain: "foo", + FQDN: "foo.example.com.", + Content: "txtTXTtxtTXTtxtTXT", + TTL: 300, + }, + { + ID: 2, + Type: "NS", + Domain: "example.com", + SubDomain: "foo", + FQDN: "foo.example.com.", + Content: "bar", + TTL: 300, + }, + }, + Success: "ok", + } + + err := json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + domain: "example.com", + }, + { + desc: "error", + handler: func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + + assert.Equal(t, "domain=example.com", r.URL.RawQuery) + + response := ListResponse{ + Domain: "example.com", + Success: "error", + Error: "bad things", + } + + err := json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + domain: "example.com", + expectError: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + mux, client, tearDown := setupClientTest() + defer tearDown() + + mux.HandleFunc("/list", test.handler) + + records, err := client.GetRecords(test.domain) + if test.expectError { + require.Error(t, err) + require.Empty(t, records) + } else { + require.NoError(t, err) + require.Len(t, records, 2) + } + }) + } +} diff --git a/providers/dns/yandex/internal/types.go b/providers/dns/yandex/internal/types.go new file mode 100644 index 00000000..3432d7eb --- /dev/null +++ b/providers/dns/yandex/internal/types.go @@ -0,0 +1,32 @@ +package internal + +type Record struct { + ID int `json:"record_id,omitempty" url:"record_id,omitempty"` + Domain string `json:"domain,omitempty" url:"domain,omitempty"` + SubDomain string `json:"subdomain,omitempty" url:"subdomain,omitempty"` + FQDN string `json:"fqdn,omitempty" url:"fqdn,omitempty"` + TTL int `json:"ttl,omitempty" url:"ttl,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Content string `json:"content,omitempty" url:"content,omitempty"` +} + +type AddResponse struct { + Domain string `json:"domain,omitempty"` + Record *Record `json:"record,omitempty"` + Success string `json:"success"` + Error string `json:"error,omitempty"` +} + +type RemoveResponse struct { + Domain string `json:"domain,omitempty"` + RecordID int `json:"record_id,omitempty"` + Success string `json:"success"` + Error string `json:"error,omitempty"` +} + +type ListResponse struct { + Domain string `json:"domain,omitempty"` + Records []Record `json:"records,omitempty"` + Success string `json:"success"` + Error string `json:"error,omitempty"` +} diff --git a/providers/dns/yandex/yandex.go b/providers/dns/yandex/yandex.go new file mode 100644 index 00000000..88a9f1ba --- /dev/null +++ b/providers/dns/yandex/yandex.go @@ -0,0 +1,175 @@ +// Package yandex implements a DNS provider for solving the DNS-01 challenge using Yandex. +package yandex + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v3/challenge/dns01" + "github.com/go-acme/lego/v3/platform/config/env" + "github.com/go-acme/lego/v3/providers/dns/yandex/internal" + "github.com/miekg/dns" +) + +const defaultTTL = 21600 + +// Environment variables names. +const ( + envNamespace = "YANDEX_" + + EnvPddToken = envNamespace + "PDD_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 { + PddToken string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider is an implementation of the challenge.Provider interface. +type DNSProvider struct { + client *internal.Client + config *Config +} + +// NewDNSProvider returns a DNSProvider instance configured for Yandex. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvPddToken) + if err != nil { + return nil, fmt.Errorf("yandex: %v", err) + } + + config := NewDefaultConfig() + config.PddToken = values[EnvPddToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Yandex. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("yandex: the configuration of the DNS provider is nil") + } + + if config.PddToken == "" { + return nil, fmt.Errorf("yandex: credentials missing") + } + + client, err := internal.NewClient(config.PddToken) + if err != nil { + return nil, fmt.Errorf("yandex: %v", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{client: client, config: config}, nil +} + +// 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) + + rootDomain, subDomain, err := splitDomain(fqdn) + if err != nil { + return fmt.Errorf("yandex: %v", err) + } + + data := internal.Record{ + Domain: rootDomain, + SubDomain: subDomain, + Type: "TXT", + TTL: d.config.TTL, + Content: value, + } + + _, err = d.client.AddRecord(data) + if err != nil { + return fmt.Errorf("yandex: %v", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + rootDomain, subDomain, err := splitDomain(fqdn) + if err != nil { + return fmt.Errorf("yandex: %v", err) + } + + records, err := d.client.GetRecords(rootDomain) + if err != nil { + return fmt.Errorf("yandex: %v", err) + } + + var record *internal.Record + for _, rcd := range records { + if rcd.Type == "TXT" && rcd.SubDomain == subDomain && rcd.Content == value { + record = &rcd + break + } + } + + if record == nil { + return fmt.Errorf("yandex: TXT record not found for domain: %s", domain) + } + + data := internal.Record{ + ID: record.ID, + Domain: rootDomain, + } + + _, err = d.client.RemoveRecord(data) + if err != nil { + return fmt.Errorf("yandex: %v", err) + } + return 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 +} + +func splitDomain(full string) (string, string, error) { + split := dns.Split(full) + if len(split) < 2 { + return "", "", fmt.Errorf("unsupported domain: %s", full) + } + + if len(split) == 2 { + return full, "", nil + } + + domain := full[split[len(split)-2]:] + subDomain := full[:split[len(split)-2]-1] + + return domain, subDomain, nil +} diff --git a/providers/dns/yandex/yandex.toml b/providers/dns/yandex/yandex.toml new file mode 100644 index 00000000..4500fd29 --- /dev/null +++ b/providers/dns/yandex/yandex.toml @@ -0,0 +1,23 @@ +Name = "Yandex" +Description = '''''' +URL = "https://yandex.com/" +Code = "yandex" +Since = "v3.7.0" + +Example = ''' +YANDEX_PDD_TOKEN= \ +lego --dns yandex --domains my.domain.com --email my@email.com run +''' + +[Configuration] + [Configuration.Credentials] + YANDEX_PDD_TOKEN = "Basic authentication username" + [Configuration.Additional] + YANDEX_POLLING_INTERVAL = "Time between DNS propagation check" + YANDEX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + YANDEX_HTTP_TIMEOUT = "API request timeout" + YANDEX_SEQUENCE_INTERVAL = "Interval between iteration, default 60s" + YANDEX_TTL = "The TTL of the TXT record used for the DNS challenge" + +[Links] + API = "https://tech.yandex.com/domain/doc/concepts/api-dns-docpage/" diff --git a/providers/dns/yandex/yandex_test.go b/providers/dns/yandex/yandex_test.go new file mode 100644 index 00000000..ac31f15f --- /dev/null +++ b/providers/dns/yandex/yandex_test.go @@ -0,0 +1,116 @@ +package yandex + +import ( + "testing" + + "github.com/go-acme/lego/v3/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvPddToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvPddToken: "SECRET", + }, + }, + { + desc: "missing token", + envVars: map[string]string{}, + expected: "yandex: some credentials information are missing: YANDEX_PDD_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) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + config *Config + expected string + }{ + { + desc: "success", + config: &Config{ + PddToken: "secret", + }, + }, + { + desc: "nil config", + config: nil, + expected: "yandex: the configuration of the DNS provider is nil", + }, + { + desc: "missing token", + config: &Config{}, + expected: "yandex: credentials 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 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) +}