forked from TrueCloudLab/lego
easydns: fix zone detection (#2121)
This commit is contained in:
parent
a7ca3d7f1c
commit
6933296e2f
9 changed files with 366 additions and 61 deletions
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
"github.com/go-acme/lego/v4/platform/config/env"
|
"github.com/go-acme/lego/v4/platform/config/env"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/easydns/internal"
|
"github.com/go-acme/lego/v4/providers/dns/easydns/internal"
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Environment variables names.
|
// Environment variables names.
|
||||||
|
@ -117,20 +116,34 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
|
|
||||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
apiHost, apiDomain := splitFqdn(info.EffectiveFQDN)
|
authZone, err := d.findZone(ctx, dns01.UnFqdn(info.EffectiveFQDN))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("easydns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if authZone == "" {
|
||||||
|
return fmt.Errorf("easydns: could not find zone for domain %q", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("easydns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
record := internal.ZoneRecord{
|
record := internal.ZoneRecord{
|
||||||
Domain: apiDomain,
|
Domain: authZone,
|
||||||
Host: apiHost,
|
Host: subDomain,
|
||||||
Type: "TXT",
|
Type: "TXT",
|
||||||
Rdata: info.Value,
|
Rdata: info.Value,
|
||||||
TTL: strconv.Itoa(d.config.TTL),
|
TTL: strconv.Itoa(d.config.TTL),
|
||||||
Priority: "0",
|
Priority: "0",
|
||||||
}
|
}
|
||||||
|
|
||||||
recordID, err := d.client.AddRecord(context.Background(), apiDomain, record)
|
recordID, err := d.client.AddRecord(ctx, dns01.UnFqdn(authZone), record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("easydns: error adding zone record: %w", err)
|
return fmt.Errorf("easydns: error adding zone record: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -146,6 +159,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters.
|
// CleanUp removes the TXT record matching the specified parameters.
|
||||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
key := getMapKey(info.EffectiveFQDN, info.Value)
|
key := getMapKey(info.EffectiveFQDN, info.Value)
|
||||||
|
@ -158,9 +173,16 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, apiDomain := splitFqdn(info.EffectiveFQDN)
|
authZone, err := d.findZone(ctx, dns01.UnFqdn(info.EffectiveFQDN))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("easydns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
err := d.client.DeleteRecord(context.Background(), apiDomain, recordID)
|
if authZone == "" {
|
||||||
|
return fmt.Errorf("easydns: could not find zone for domain %q", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), recordID)
|
||||||
|
|
||||||
d.recordIDsMu.Lock()
|
d.recordIDsMu.Lock()
|
||||||
defer delete(d.recordIDs, key)
|
defer delete(d.recordIDs, key)
|
||||||
|
@ -185,15 +207,28 @@ func (d *DNSProvider) Sequential() time.Duration {
|
||||||
return d.config.SequenceInterval
|
return d.config.SequenceInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitFqdn(fqdn string) (host, domain string) {
|
|
||||||
parts := dns.SplitDomainName(fqdn)
|
|
||||||
length := len(parts)
|
|
||||||
|
|
||||||
host = strings.Join(parts[0:length-2], ".")
|
|
||||||
domain = strings.Join(parts[length-2:length], ".")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMapKey(fqdn, value string) string {
|
func getMapKey(fqdn, value string) string {
|
||||||
return fqdn + "|" + value
|
return fqdn + "|" + value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DNSProvider) findZone(ctx context.Context, domain string) (string, error) {
|
||||||
|
var errAll error
|
||||||
|
|
||||||
|
for {
|
||||||
|
i := strings.Index(domain, ".")
|
||||||
|
if i == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := d.client.ListZones(ctx, domain)
|
||||||
|
if err == nil {
|
||||||
|
return domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errAll = errors.Join(errAll, err)
|
||||||
|
|
||||||
|
domain = domain[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errAll
|
||||||
|
}
|
||||||
|
|
|
@ -147,6 +147,39 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
||||||
func TestDNSProvider_Present(t *testing.T) {
|
func TestDNSProvider_Present(t *testing.T) {
|
||||||
provider, mux := setupTest(t)
|
provider, mux := setupTest(t)
|
||||||
|
|
||||||
|
mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, http.MethodGet, r.Method, "method")
|
||||||
|
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
||||||
|
assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := fmt.Fprintf(w, `{
|
||||||
|
"msg": "string",
|
||||||
|
"status": 200,
|
||||||
|
"tm": 0,
|
||||||
|
"data": [{
|
||||||
|
"id": "60898922",
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": "300",
|
||||||
|
"prio": "0",
|
||||||
|
"geozone_id": "0",
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
}],
|
||||||
|
"count": 0,
|
||||||
|
"total": 0,
|
||||||
|
"start": 0,
|
||||||
|
"max": 0
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/zones/records/add/example.com/TXT", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/zones/records/add/example.com/TXT", func(w http.ResponseWriter, r *http.Request) {
|
||||||
assert.Equal(t, http.MethodPut, r.Method, "method")
|
assert.Equal(t, http.MethodPut, r.Method, "method")
|
||||||
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
||||||
|
@ -191,7 +224,40 @@ func TestDNSProvider_Present(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSProvider_Cleanup_WhenRecordIdNotSet_NoOp(t *testing.T) {
|
func TestDNSProvider_Cleanup_WhenRecordIdNotSet_NoOp(t *testing.T) {
|
||||||
provider, _ := setupTest(t)
|
provider, mux := setupTest(t)
|
||||||
|
|
||||||
|
mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, http.MethodGet, r.Method, "method")
|
||||||
|
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
||||||
|
assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := fmt.Fprintf(w, `{
|
||||||
|
"msg": "string",
|
||||||
|
"status": 200,
|
||||||
|
"tm": 0,
|
||||||
|
"data": [{
|
||||||
|
"id": "60898922",
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": "300",
|
||||||
|
"prio": "0",
|
||||||
|
"geozone_id": "0",
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
}],
|
||||||
|
"count": 0,
|
||||||
|
"total": 0,
|
||||||
|
"start": 0,
|
||||||
|
"max": 0
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
err := provider.CleanUp("example.com", "token", "keyAuth")
|
err := provider.CleanUp("example.com", "token", "keyAuth")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -200,6 +266,39 @@ func TestDNSProvider_Cleanup_WhenRecordIdNotSet_NoOp(t *testing.T) {
|
||||||
func TestDNSProvider_Cleanup_WhenRecordIdSet_DeletesTxtRecord(t *testing.T) {
|
func TestDNSProvider_Cleanup_WhenRecordIdSet_DeletesTxtRecord(t *testing.T) {
|
||||||
provider, mux := setupTest(t)
|
provider, mux := setupTest(t)
|
||||||
|
|
||||||
|
mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, http.MethodGet, r.Method, "method")
|
||||||
|
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
||||||
|
assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := fmt.Fprintf(w, `{
|
||||||
|
"msg": "string",
|
||||||
|
"status": 200,
|
||||||
|
"tm": 0,
|
||||||
|
"data": [{
|
||||||
|
"id": "60898922",
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": "300",
|
||||||
|
"prio": "0",
|
||||||
|
"geozone_id": "0",
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
}],
|
||||||
|
"count": 0,
|
||||||
|
"total": 0,
|
||||||
|
"start": 0,
|
||||||
|
"max": 0
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/zones/records/example.com/123456", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/zones/records/example.com/123456", func(w http.ResponseWriter, r *http.Request) {
|
||||||
assert.Equal(t, http.MethodDelete, r.Method, "method")
|
assert.Equal(t, http.MethodDelete, r.Method, "method")
|
||||||
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
||||||
|
@ -228,6 +327,39 @@ func TestDNSProvider_Cleanup_WhenRecordIdSet_DeletesTxtRecord(t *testing.T) {
|
||||||
func TestDNSProvider_Cleanup_WhenHttpError_ReturnsError(t *testing.T) {
|
func TestDNSProvider_Cleanup_WhenHttpError_ReturnsError(t *testing.T) {
|
||||||
provider, mux := setupTest(t)
|
provider, mux := setupTest(t)
|
||||||
|
|
||||||
|
mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, http.MethodGet, r.Method, "method")
|
||||||
|
assert.Equal(t, "format=json", r.URL.RawQuery, "query")
|
||||||
|
assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := fmt.Fprintf(w, `{
|
||||||
|
"msg": "string",
|
||||||
|
"status": 200,
|
||||||
|
"tm": 0,
|
||||||
|
"data": [{
|
||||||
|
"id": "60898922",
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": "300",
|
||||||
|
"prio": "0",
|
||||||
|
"geozone_id": "0",
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
}],
|
||||||
|
"count": 0,
|
||||||
|
"total": 0,
|
||||||
|
"start": 0,
|
||||||
|
"max": 0
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
errorMessage := `{
|
errorMessage := `{
|
||||||
"error": {
|
"error": {
|
||||||
"code": 406,
|
"code": 406,
|
||||||
|
@ -253,43 +385,6 @@ func TestDNSProvider_Cleanup_WhenHttpError_ReturnsError(t *testing.T) {
|
||||||
require.EqualError(t, err, expectedError)
|
require.EqualError(t, err, expectedError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSplitFqdn(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
fqdn string
|
|
||||||
expectedHost string
|
|
||||||
expectedDomain string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "domain only",
|
|
||||||
fqdn: "domain.com.",
|
|
||||||
expectedHost: "",
|
|
||||||
expectedDomain: "domain.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "single-part host",
|
|
||||||
fqdn: "_acme-challenge.domain.com.",
|
|
||||||
expectedHost: "_acme-challenge",
|
|
||||||
expectedDomain: "domain.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "multi-part host",
|
|
||||||
fqdn: "_acme-challenge.sub.domain.com.",
|
|
||||||
expectedHost: "_acme-challenge.sub",
|
|
||||||
expectedDomain: "domain.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
actualHost, actualDomain := splitFqdn(test.fqdn)
|
|
||||||
|
|
||||||
require.Equal(t, test.expectedHost, actualHost)
|
|
||||||
require.Equal(t, test.expectedDomain, actualDomain)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLivePresent(t *testing.T) {
|
func TestLivePresent(t *testing.T) {
|
||||||
if !envTest.IsLiveTest() {
|
if !envTest.IsLiveTest() {
|
||||||
t.Skip("skipping live test")
|
t.Skip("skipping live test")
|
||||||
|
|
|
@ -37,6 +37,27 @@ func NewClient(token string, key string) *Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListZones(ctx context.Context, domain string) ([]ZoneRecord, error) {
|
||||||
|
endpoint := c.BaseURL.JoinPath("zones", "records", "all", domain)
|
||||||
|
|
||||||
|
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &apiResponse[[]ZoneRecord]{}
|
||||||
|
err = c.do(req, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return nil, response.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) AddRecord(ctx context.Context, domain string, record ZoneRecord) (string, error) {
|
func (c *Client) AddRecord(ctx context.Context, domain string, record ZoneRecord) (string, error) {
|
||||||
endpoint := c.BaseURL.JoinPath("zones", "records", "add", domain, "TXT")
|
endpoint := c.BaseURL.JoinPath("zones", "records", "add", domain, "TXT")
|
||||||
|
|
||||||
|
@ -45,12 +66,16 @@ func (c *Client) AddRecord(ctx context.Context, domain string, record ZoneRecord
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &addRecordResponse{}
|
response := &apiResponse[*ZoneRecord]{}
|
||||||
err = c.do(req, response)
|
err = c.do(req, response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return "", response.Error
|
||||||
|
}
|
||||||
|
|
||||||
recordID := response.Data.ID
|
recordID := response.Data.ID
|
||||||
|
|
||||||
return recordID, nil
|
return recordID, nil
|
||||||
|
@ -64,7 +89,9 @@ func (c *Client) DeleteRecord(ctx context.Context, domain, recordID string) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.do(req, nil)
|
err = c.do(req, nil)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) do(req *http.Request, result any) error {
|
func (c *Client) do(req *http.Request, result any) error {
|
||||||
|
|
|
@ -67,6 +67,33 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClient_ListZones(t *testing.T) {
|
||||||
|
client := setupTest(t, http.MethodGet, "/zones/records/all/example.com", http.StatusOK, "list-zone.json")
|
||||||
|
|
||||||
|
zones, err := client.ListZones(context.Background(), "example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := []ZoneRecord{{
|
||||||
|
ID: "60898922",
|
||||||
|
Domain: "example.com",
|
||||||
|
Host: "hosta",
|
||||||
|
TTL: "300",
|
||||||
|
Priority: "0",
|
||||||
|
Type: "A",
|
||||||
|
Rdata: "1.2.3.4",
|
||||||
|
LastMod: "2019-08-28 19:09:50",
|
||||||
|
}}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, zones)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_ListZones_error(t *testing.T) {
|
||||||
|
client := setupTest(t, http.MethodGet, "/zones/records/all/example.com", http.StatusOK, "error1.json")
|
||||||
|
|
||||||
|
_, err := client.ListZones(context.Background(), "example.com")
|
||||||
|
require.EqualError(t, err, "code 420: Enhance Your Calm. Rate limit exceeded (too many requests) OR you did NOT provide any credentials with your request!")
|
||||||
|
}
|
||||||
|
|
||||||
func TestClient_AddRecord(t *testing.T) {
|
func TestClient_AddRecord(t *testing.T) {
|
||||||
client := setupTest(t, http.MethodPut, "/zones/records/add/example.com/TXT", http.StatusCreated, "add-record.json")
|
client := setupTest(t, http.MethodPut, "/zones/records/add/example.com/TXT", http.StatusCreated, "add-record.json")
|
||||||
|
|
||||||
|
@ -85,6 +112,22 @@ func TestClient_AddRecord(t *testing.T) {
|
||||||
assert.Equal(t, "xxx", recordID)
|
assert.Equal(t, "xxx", recordID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClient_AddRecord_error(t *testing.T) {
|
||||||
|
client := setupTest(t, http.MethodPut, "/zones/records/add/example.com/TXT", http.StatusCreated, "error1.json")
|
||||||
|
|
||||||
|
record := ZoneRecord{
|
||||||
|
Domain: "example.com",
|
||||||
|
Host: "test631",
|
||||||
|
Type: "TXT",
|
||||||
|
Rdata: "txt",
|
||||||
|
TTL: "300",
|
||||||
|
Priority: "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.AddRecord(context.Background(), "example.com", record)
|
||||||
|
require.EqualError(t, err, "code 420: Enhance Your Calm. Rate limit exceeded (too many requests) OR you did NOT provide any credentials with your request!")
|
||||||
|
}
|
||||||
|
|
||||||
func TestClient_DeleteRecord(t *testing.T) {
|
func TestClient_DeleteRecord(t *testing.T) {
|
||||||
client := setupTest(t, http.MethodDelete, "/zones/records/example.com/xxx", http.StatusOK, "")
|
client := setupTest(t, http.MethodDelete, "/zones/records/example.com/xxx", http.StatusOK, "")
|
||||||
|
|
||||||
|
|
4
providers/dns/easydns/internal/fixtures/error.json
Normal file
4
providers/dns/easydns/internal/fixtures/error.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"msg": "Enhance your calm",
|
||||||
|
"status": 403
|
||||||
|
}
|
6
providers/dns/easydns/internal/fixtures/error1.json
Normal file
6
providers/dns/easydns/internal/fixtures/error1.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": 420,
|
||||||
|
"message": "Enhance Your Calm. Rate limit exceeded (too many requests) OR you did NOT provide any credentials with your request!"
|
||||||
|
}
|
||||||
|
}
|
22
providers/dns/easydns/internal/fixtures/list-zone.json
Normal file
22
providers/dns/easydns/internal/fixtures/list-zone.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"msg": "message",
|
||||||
|
"status": 200,
|
||||||
|
"tm": 0,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "60898922",
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": "300",
|
||||||
|
"prio": "0",
|
||||||
|
"geozone_id": "0",
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 43,
|
||||||
|
"total": 43,
|
||||||
|
"start": 0,
|
||||||
|
"max": 1000
|
||||||
|
}
|
57
providers/dns/easydns/internal/readme.md
Normal file
57
providers/dns/easydns/internal/readme.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
The API doc is mainly wrong on the response schema:
|
||||||
|
|
||||||
|
ex:
|
||||||
|
|
||||||
|
- the doc for `/zones/records/all/{domain}`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": "string",
|
||||||
|
"status": 200,
|
||||||
|
"tm": 1709190001,
|
||||||
|
"data": {
|
||||||
|
"id": 60898922,
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": 300,
|
||||||
|
"prio": 0,
|
||||||
|
"geozone_id": 0,
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
},
|
||||||
|
"count": 0,
|
||||||
|
"total": 0,
|
||||||
|
"start": 0,
|
||||||
|
"max": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The reality:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tm": 1709190001,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "60898922",
|
||||||
|
"domain": "example.com",
|
||||||
|
"host": "hosta",
|
||||||
|
"ttl": "300",
|
||||||
|
"prio": "0",
|
||||||
|
"geozone_id": "0",
|
||||||
|
"type": "A",
|
||||||
|
"rdata": "1.2.3.4",
|
||||||
|
"last_mod": "2019-08-28 19:09:50"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 0,
|
||||||
|
"total": 0,
|
||||||
|
"start": 0,
|
||||||
|
"max": 0,
|
||||||
|
"status": 200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`data` is an array.
|
||||||
|
`id`, `ttl`, `geozone_id` are strings.
|
|
@ -1,5 +1,19 @@
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type apiResponse[T any] struct {
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Tm int `json:"tm"`
|
||||||
|
Data T `json:"data"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Start int `json:"start"`
|
||||||
|
Max int `json:"max"`
|
||||||
|
Error *Error `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ZoneRecord struct {
|
type ZoneRecord struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
|
@ -13,9 +27,11 @@ type ZoneRecord struct {
|
||||||
NewHost string `json:"new_host,omitempty"`
|
NewHost string `json:"new_host,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type addRecordResponse struct {
|
type Error struct {
|
||||||
Msg string `json:"msg"`
|
Code int `json:"code"`
|
||||||
Tm int `json:"tm"`
|
Message string `json:"message"`
|
||||||
Data ZoneRecord `json:"data"`
|
}
|
||||||
Status int `json:"status"`
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return fmt.Sprintf("code %d: %s", e.Code, e.Message)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue