mirror of
https://github.com/go-acme/lego.git
synced 2024-12-25 11:02:56 +02:00
cloudxns: provider deprecation (#2324)
This commit is contained in:
parent
af7e2edd4e
commit
67230e268a
@ -77,7 +77,7 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
|
||||
<td><a href="https://go-acme.github.io/lego/dns/cloudflare/">Cloudflare</a></td>
|
||||
<td><a href="https://go-acme.github.io/lego/dns/cloudns/">ClouDNS</a></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://go-acme.github.io/lego/dns/cloudxns/">CloudXNS</a></td>
|
||||
<td><a href="https://go-acme.github.io/lego/dns/cloudxns/">CloudXNS (Deprecated)</a></td>
|
||||
<td><a href="https://go-acme.github.io/lego/dns/conoha/">ConoHa</a></td>
|
||||
<td><a href="https://go-acme.github.io/lego/dns/constellix/">Constellix</a></td>
|
||||
<td><a href="https://go-acme.github.io/lego/dns/corenetworks/">Core-Networks</a></td>
|
||||
|
@ -563,7 +563,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||
|
||||
case "cloudxns":
|
||||
// generated from: providers/dns/cloudxns/cloudxns.toml
|
||||
ew.writeln(`Configuration for CloudXNS.`)
|
||||
ew.writeln(`Configuration for CloudXNS (Deprecated).`)
|
||||
ew.writeln(`Code: 'cloudxns'`)
|
||||
ew.writeln(`Since: 'v0.5.0'`)
|
||||
ew.writeln()
|
||||
|
@ -1,20 +1,20 @@
|
||||
---
|
||||
title: "CloudXNS"
|
||||
title: "CloudXNS (Deprecated)"
|
||||
date: 2019-03-03T16:39:46+01:00
|
||||
draft: false
|
||||
slug: cloudxns
|
||||
dnsprovider:
|
||||
since: "v0.5.0"
|
||||
code: "cloudxns"
|
||||
url: "https://www.cloudxns.net/"
|
||||
url: "https://github.com/go-acme/lego/issues/2323"
|
||||
---
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
<!-- providers/dns/cloudxns/cloudxns.toml -->
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
|
||||
The CloudXNS DNS provider has shut down.
|
||||
|
||||
Configuration for [CloudXNS](https://www.cloudxns.net/).
|
||||
|
||||
|
||||
<!--more-->
|
||||
@ -23,7 +23,7 @@ Configuration for [CloudXNS](https://www.cloudxns.net/).
|
||||
- Since: v0.5.0
|
||||
|
||||
|
||||
Here is an example bash command using the CloudXNS provider:
|
||||
Here is an example bash command using the CloudXNS (Deprecated) provider:
|
||||
|
||||
```bash
|
||||
CLOUDXNS_API_KEY=xxxx \
|
||||
@ -60,9 +60,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
|
||||
## More information
|
||||
|
||||
- [API documentation](https://www.cloudxns.net/Public/Doc/CloudXNS_api2.0_doc_zh-cn.zip)
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
<!-- providers/dns/cloudxns/cloudxns.toml -->
|
||||
|
@ -2,15 +2,11 @@
|
||||
package cloudxns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudxns/internal"
|
||||
)
|
||||
|
||||
// Environment variables names.
|
||||
@ -38,101 +34,34 @@ type Config struct {
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
},
|
||||
}
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
// DNSProvider implements the challenge.Provider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
type DNSProvider struct{}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for CloudXNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDXNS_API_KEY and CLOUDXNS_SECRET_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAPIKey, EnvSecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values[EnvAPIKey]
|
||||
config.SecretKey = values[EnvSecretKey]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
return NewDNSProviderConfig(&Config{})
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for CloudXNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("cloudxns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.APIKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
if config.HTTPClient != nil {
|
||||
client.HTTPClient = config.HTTPClient
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
func NewDNSProviderConfig(_ *Config) (*DNSProvider, error) {
|
||||
return nil, errors.New("cloudxns: provider has shut down")
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
info, err := d.client.GetDomainInformation(ctx, challengeInfo.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
err = d.client.AddTxtRecord(ctx, info, challengeInfo.EffectiveFQDN, challengeInfo.Value, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Present(_, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
info, err := d.client.GetDomainInformation(ctx, challengeInfo.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
record, err := d.client.FindTxtRecord(ctx, info.ID, challengeInfo.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
err = d.client.RemoveTxtRecord(ctx, record.RecordID, info.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudxns: %w", err)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) CleanUp(_, _, _ string) error {
|
||||
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
|
||||
return dns01.DefaultPropagationTimeout, dns01.DefaultPollingInterval
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
Name = "CloudXNS"
|
||||
Description = """"""
|
||||
URL = "https://www.cloudxns.net/"
|
||||
Name = "CloudXNS (Deprecated)"
|
||||
Description = '''
|
||||
The CloudXNS DNS provider has shut down.
|
||||
'''
|
||||
URL = "https://github.com/go-acme/lego/issues/2323"
|
||||
Code = "cloudxns"
|
||||
Since = "v0.5.0"
|
||||
|
||||
@ -19,6 +21,3 @@ lego --email you@example.com --dns cloudxns --domains my.example.org run
|
||||
CLOUDXNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||
CLOUDXNS_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||
CLOUDXNS_HTTP_TIMEOUT = "API request timeout"
|
||||
|
||||
[Links]
|
||||
API = "https://www.cloudxns.net/Public/Doc/CloudXNS_api2.0_doc_zh-cn.zip"
|
||||
|
@ -1,152 +0,0 @@
|
||||
package cloudxns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const envDomain = envNamespace + "DOMAIN"
|
||||
|
||||
var envTest = tester.NewEnvTest(
|
||||
EnvAPIKey,
|
||||
EnvSecretKey).
|
||||
WithDomain(envDomain)
|
||||
|
||||
func TestNewDNSProvider(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
envVars map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "success",
|
||||
envVars: map[string]string{
|
||||
EnvAPIKey: "123",
|
||||
EnvSecretKey: "456",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing credentials",
|
||||
envVars: map[string]string{
|
||||
EnvAPIKey: "",
|
||||
EnvSecretKey: "",
|
||||
},
|
||||
expected: "cloudxns: some credentials information are missing: CLOUDXNS_API_KEY,CLOUDXNS_SECRET_KEY",
|
||||
},
|
||||
{
|
||||
desc: "missing API key",
|
||||
envVars: map[string]string{
|
||||
EnvAPIKey: "",
|
||||
EnvSecretKey: "456",
|
||||
},
|
||||
expected: "cloudxns: some credentials information are missing: CLOUDXNS_API_KEY",
|
||||
},
|
||||
{
|
||||
desc: "missing secret key",
|
||||
envVars: map[string]string{
|
||||
EnvAPIKey: "123",
|
||||
EnvSecretKey: "",
|
||||
},
|
||||
expected: "cloudxns: some credentials information are missing: CLOUDXNS_SECRET_KEY",
|
||||
},
|
||||
}
|
||||
|
||||
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 test.expected == "" {
|
||||
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
|
||||
secretKey string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "success",
|
||||
apiKey: "123",
|
||||
secretKey: "456",
|
||||
},
|
||||
{
|
||||
desc: "missing credentials",
|
||||
expected: "cloudxns: credentials missing: apiKey",
|
||||
},
|
||||
{
|
||||
desc: "missing api key",
|
||||
secretKey: "456",
|
||||
expected: "cloudxns: credentials missing: apiKey",
|
||||
},
|
||||
{
|
||||
desc: "missing secret key",
|
||||
apiKey: "123",
|
||||
expected: "cloudxns: credentials missing: secretKey",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = test.apiKey
|
||||
config.SecretKey = test.secretKey
|
||||
|
||||
p, err := NewDNSProviderConfig(config)
|
||||
|
||||
if test.expected == "" {
|
||||
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 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)
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://www.cloudxns.net/api2/"
|
||||
|
||||
// Client CloudXNS client.
|
||||
type Client struct {
|
||||
apiKey string
|
||||
secretKey string
|
||||
|
||||
baseURL *url.URL
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a CloudXNS client.
|
||||
func NewClient(apiKey, secretKey string) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("credentials missing: apiKey")
|
||||
}
|
||||
|
||||
if secretKey == "" {
|
||||
return nil, errors.New("credentials missing: secretKey")
|
||||
}
|
||||
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
secretKey: secretKey,
|
||||
baseURL: baseURL,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDomainInformation Get domain name information for a FQDN.
|
||||
func (c *Client) GetDomainInformation(ctx context.Context, fqdn string) (*Data, error) {
|
||||
endpoint := c.baseURL.JoinPath("domain")
|
||||
|
||||
req, err := c.newRequest(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find zone: %w", err)
|
||||
}
|
||||
|
||||
var domains []Data
|
||||
err = c.do(req, &domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, data := range domains {
|
||||
if data.Domain == authZone {
|
||||
return &data, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("zone %s not found for domain %s", authZone, fqdn)
|
||||
}
|
||||
|
||||
// FindTxtRecord return the TXT record a zone ID and a FQDN.
|
||||
func (c *Client) FindTxtRecord(ctx context.Context, zoneID, fqdn string) (*TXTRecord, error) {
|
||||
endpoint := c.baseURL.JoinPath("record", zoneID)
|
||||
|
||||
query := endpoint.Query()
|
||||
query.Set("host_id", "0")
|
||||
query.Set("offset", "0")
|
||||
query.Set("row_num", "2000")
|
||||
endpoint.RawQuery = query.Encode()
|
||||
|
||||
req, err := c.newRequest(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []TXTRecord
|
||||
err = c.do(req, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if record.Host == dns01.UnFqdn(fqdn) && record.Type == "TXT" {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no existing record found for %q", fqdn)
|
||||
}
|
||||
|
||||
// AddTxtRecord add a TXT record.
|
||||
func (c *Client) AddTxtRecord(ctx context.Context, info *Data, fqdn, value string, ttl int) error {
|
||||
id, err := strconv.Atoi(info.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid zone ID: %w", err)
|
||||
}
|
||||
|
||||
endpoint := c.baseURL.JoinPath("record")
|
||||
|
||||
subDomain, err := dns01.ExtractSubDomain(fqdn, info.Domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := TXTRecord{
|
||||
ID: id,
|
||||
Host: subDomain,
|
||||
Value: value,
|
||||
Type: "TXT",
|
||||
LineID: 1,
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
req, err := c.newRequest(ctx, http.MethodPost, endpoint, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.do(req, nil)
|
||||
}
|
||||
|
||||
// RemoveTxtRecord remove a TXT record.
|
||||
func (c *Client) RemoveTxtRecord(ctx context.Context, recordID, zoneID string) error {
|
||||
endpoint := c.baseURL.JoinPath("record", recordID, zoneID)
|
||||
|
||||
req, err := c.newRequest(ctx, http.MethodDelete, endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.do(req, nil)
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request, result any) error {
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return errutils.NewHTTPDoError(req, err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errutils.NewReadResponseError(req, resp.StatusCode, err)
|
||||
}
|
||||
|
||||
var response apiResponse
|
||||
err = json.Unmarshal(raw, &response)
|
||||
if err != nil {
|
||||
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
|
||||
}
|
||||
|
||||
if response.Code != 1 {
|
||||
return fmt.Errorf("[status code %d] invalid code (%v) error: %s", resp.StatusCode, response.Code, response.Message)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(response.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal(response.Data, result)
|
||||
if err != nil {
|
||||
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if payload != nil {
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request JSON body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create request: %w", err)
|
||||
}
|
||||
|
||||
requestDate := time.Now().Format(time.RFC1123Z)
|
||||
|
||||
req.Header.Set("API-KEY", c.apiKey)
|
||||
req.Header.Set("API-REQUEST-DATE", requestDate)
|
||||
req.Header.Set("API-HMAC", c.hmac(endpoint.String(), requestDate, buf.String()))
|
||||
req.Header.Set("API-FORMAT", "json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) hmac(endpoint, date, body string) string {
|
||||
sum := md5.Sum([]byte(c.apiKey + endpoint + body + date + c.secretKey))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupTest(t *testing.T, handler http.HandlerFunc) *Client {
|
||||
t.Helper()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client, _ := NewClient("myKey", "mySecret")
|
||||
client.baseURL, _ = url.Parse(server.URL + "/")
|
||||
client.HTTPClient = server.Client()
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func handlerMock(method string, response *apiResponse, data interface{}) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != method {
|
||||
content, err := json.Marshal(apiResponse{
|
||||
Code: 999, // random code only for the test
|
||||
Message: fmt.Sprintf("invalid method: got %s want %s", req.Method, method),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(rw, string(content), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response.Data = jsonData
|
||||
|
||||
content, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = rw.Write(content)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_GetDomainInformation(t *testing.T) {
|
||||
type result struct {
|
||||
domain *Data
|
||||
error bool
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
fqdn string
|
||||
response *apiResponse
|
||||
data []Data
|
||||
expected result
|
||||
}{
|
||||
{
|
||||
desc: "domain found",
|
||||
fqdn: "_acme-challenge.example.org.",
|
||||
response: &apiResponse{
|
||||
Code: 1,
|
||||
},
|
||||
data: []Data{
|
||||
{
|
||||
ID: "1",
|
||||
Domain: "example.com.",
|
||||
},
|
||||
{
|
||||
ID: "2",
|
||||
Domain: "example.org.",
|
||||
},
|
||||
},
|
||||
expected: result{domain: &Data{
|
||||
ID: "2",
|
||||
Domain: "example.org.",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "domains not found",
|
||||
fqdn: "_acme-challenge.huu.com.",
|
||||
response: &apiResponse{
|
||||
Code: 1,
|
||||
},
|
||||
data: []Data{
|
||||
{
|
||||
ID: "5",
|
||||
Domain: "example.com.",
|
||||
},
|
||||
{
|
||||
ID: "6",
|
||||
Domain: "example.org.",
|
||||
},
|
||||
},
|
||||
expected: result{error: true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
client := setupTest(t, handlerMock(http.MethodGet, test.response, test.data))
|
||||
|
||||
domain, err := client.GetDomainInformation(context.Background(), test.fqdn)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected.domain, domain)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_FindTxtRecord(t *testing.T) {
|
||||
type result struct {
|
||||
txtRecord *TXTRecord
|
||||
error bool
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
fqdn string
|
||||
zoneID string
|
||||
txtRecords []TXTRecord
|
||||
response *apiResponse
|
||||
expected result
|
||||
}{
|
||||
{
|
||||
desc: "record found",
|
||||
fqdn: "_acme-challenge.example.org.",
|
||||
zoneID: "test-zone",
|
||||
txtRecords: []TXTRecord{
|
||||
{
|
||||
ID: 1,
|
||||
RecordID: "Record-A",
|
||||
Host: "_acme-challenge.example.org",
|
||||
Value: "txtTXTtxtTXTtxtTXTtxtTXT",
|
||||
Type: "TXT",
|
||||
LineID: 6,
|
||||
TTL: 30,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
RecordID: "Record-B",
|
||||
Host: "_acme-challenge.example.com",
|
||||
Value: "TXTtxtTXTtxtTXTtxtTXTtxt",
|
||||
Type: "TXT",
|
||||
LineID: 6,
|
||||
TTL: 30,
|
||||
},
|
||||
},
|
||||
response: &apiResponse{
|
||||
Code: 1,
|
||||
},
|
||||
expected: result{
|
||||
txtRecord: &TXTRecord{
|
||||
ID: 1,
|
||||
RecordID: "Record-A",
|
||||
Host: "_acme-challenge.example.org",
|
||||
Value: "txtTXTtxtTXTtxtTXTtxtTXT",
|
||||
Type: "TXT",
|
||||
LineID: 6,
|
||||
TTL: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "record not found",
|
||||
fqdn: "_acme-challenge.huu.com.",
|
||||
zoneID: "test-zone",
|
||||
txtRecords: []TXTRecord{
|
||||
{
|
||||
ID: 1,
|
||||
RecordID: "Record-A",
|
||||
Host: "_acme-challenge.example.org",
|
||||
Value: "txtTXTtxtTXTtxtTXTtxtTXT",
|
||||
Type: "TXT",
|
||||
LineID: 6,
|
||||
TTL: 30,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
RecordID: "Record-B",
|
||||
Host: "_acme-challenge.example.com",
|
||||
Value: "TXTtxtTXTtxtTXTtxtTXTtxt",
|
||||
Type: "TXT",
|
||||
LineID: 6,
|
||||
TTL: 30,
|
||||
},
|
||||
},
|
||||
response: &apiResponse{
|
||||
Code: 1,
|
||||
},
|
||||
expected: result{error: true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
client := setupTest(t, handlerMock(http.MethodGet, test.response, test.txtRecords))
|
||||
|
||||
txtRecord, err := client.FindTxtRecord(context.Background(), test.zoneID, test.fqdn)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected.txtRecord, txtRecord)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AddTxtRecord(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domain *Data
|
||||
fqdn string
|
||||
value string
|
||||
ttl int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "sub-domain",
|
||||
domain: &Data{
|
||||
ID: "1",
|
||||
Domain: "example.com.",
|
||||
},
|
||||
fqdn: "_acme-challenge.foo.example.com.",
|
||||
value: "txtTXTtxtTXTtxtTXTtxtTXT",
|
||||
ttl: 30,
|
||||
expected: `{"domain_id":1,"host":"_acme-challenge.foo","value":"txtTXTtxtTXTtxtTXTtxtTXT","type":"TXT","line_id":"1","ttl":"30"}`,
|
||||
},
|
||||
{
|
||||
desc: "main domain",
|
||||
domain: &Data{
|
||||
ID: "2",
|
||||
Domain: "example.com.",
|
||||
},
|
||||
fqdn: "_acme-challenge.example.com.",
|
||||
value: "TXTtxtTXTtxtTXTtxtTXTtxt",
|
||||
ttl: 30,
|
||||
expected: `{"domain_id":2,"host":"_acme-challenge","value":"TXTtxtTXTtxtTXTtxtTXTtxt","type":"TXT","line_id":"1","ttl":"30"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
response := &apiResponse{
|
||||
Code: 1,
|
||||
}
|
||||
|
||||
client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) {
|
||||
assert.NotNil(t, req.Body)
|
||||
content, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, string(bytes.TrimSpace(content)))
|
||||
|
||||
handlerMock(http.MethodPost, response, nil).ServeHTTP(rw, req)
|
||||
})
|
||||
|
||||
err := client.AddTxtRecord(context.Background(), test.domain, test.fqdn, test.value, test.ttl)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package internal
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type apiResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Data Domain information.
|
||||
type Data struct {
|
||||
ID string `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// TXTRecord a TXT record.
|
||||
type TXTRecord struct {
|
||||
ID int `json:"domain_id,omitempty"`
|
||||
RecordID string `json:"record_id,omitempty"`
|
||||
|
||||
Host string `json:"host"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
LineID int `json:"line_id,string"`
|
||||
TTL int `json:"ttl,string"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user