godaddy: fix cleanup (#2270)
This commit is contained in:
parent
b95f03d3b3
commit
253e3305bc
5 changed files with 118 additions and 24 deletions
|
@ -119,13 +119,13 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
records, err := d.client.GetRecords(ctx, authZone, "TXT", subDomain)
|
existingRecords, err := d.client.GetRecords(ctx, authZone, "TXT", subDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("godaddy: failed to get TXT records: %w", err)
|
return fmt.Errorf("godaddy: failed to get TXT records: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRecords []internal.DNSRecord
|
var newRecords []internal.DNSRecord
|
||||||
for _, record := range records {
|
for _, record := range existingRecords {
|
||||||
if record.Data != "" {
|
if record.Data != "" {
|
||||||
newRecords = append(newRecords, record)
|
newRecords = append(newRecords, record)
|
||||||
}
|
}
|
||||||
|
@ -165,34 +165,28 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
records, err := d.client.GetRecords(ctx, authZone, "TXT", subDomain)
|
existingRecords, err := d.client.GetRecords(ctx, authZone, "TXT", subDomain)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("godaddy: failed to get TXT records: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(records) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
allTxtRecords, err := d.client.GetRecords(ctx, authZone, "TXT", "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("godaddy: failed to get all TXT records: %w", err)
|
return fmt.Errorf("godaddy: failed to get all TXT records: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var recordsKeep []internal.DNSRecord
|
var recordsToKeep []internal.DNSRecord
|
||||||
for _, record := range allTxtRecords {
|
for _, record := range existingRecords {
|
||||||
if record.Data != info.Value && record.Data != "" {
|
if record.Data != info.Value && record.Data != "" {
|
||||||
recordsKeep = append(recordsKeep, record)
|
recordsToKeep = append(recordsToKeep, record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoDaddy API don't provide a way to delete a record, an "empty" record must be added.
|
if len(recordsToKeep) == 0 {
|
||||||
if len(recordsKeep) == 0 {
|
err = d.client.DeleteTxtRecords(ctx, authZone, subDomain)
|
||||||
emptyRecord := internal.DNSRecord{Name: "empty", Data: ""}
|
if err != nil {
|
||||||
recordsKeep = append(recordsKeep, emptyRecord)
|
return fmt.Errorf("godaddy: failed to delete TXT record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.client.UpdateTxtRecords(ctx, recordsKeep, authZone, "")
|
err = d.client.UpdateTxtRecords(ctx, recordsToKeep, authZone, subDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("godaddy: failed to remove TXT record: %w", err)
|
return fmt.Errorf("godaddy: failed to remove TXT record: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ func NewClient(apiKey string, apiSecret string) *Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRecords retrieves DNS Records for the specified Domain.
|
||||||
|
// https://developer.godaddy.com/doc/endpoint/domains#/v1/recordGet
|
||||||
func (c *Client) GetRecords(ctx context.Context, domainZone, rType, recordName string) ([]DNSRecord, error) {
|
func (c *Client) GetRecords(ctx context.Context, domainZone, rType, recordName string) ([]DNSRecord, error) {
|
||||||
endpoint := c.baseURL.JoinPath("v1", "domains", domainZone, "records", rType, recordName)
|
endpoint := c.baseURL.JoinPath("v1", "domains", domainZone, "records", rType, recordName)
|
||||||
|
|
||||||
|
@ -54,6 +56,8 @@ func (c *Client) GetRecords(ctx context.Context, domainZone, rType, recordName s
|
||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateTxtRecords replaces all DNS Records for the specified Domain with the specified Type.
|
||||||
|
// https://developer.godaddy.com/doc/endpoint/domains#/v1/recordReplaceType
|
||||||
func (c *Client) UpdateTxtRecords(ctx context.Context, records []DNSRecord, domainZone, recordName string) error {
|
func (c *Client) UpdateTxtRecords(ctx context.Context, records []DNSRecord, domainZone, recordName string) error {
|
||||||
endpoint := c.baseURL.JoinPath("v1", "domains", domainZone, "records", "TXT", recordName)
|
endpoint := c.baseURL.JoinPath("v1", "domains", domainZone, "records", "TXT", recordName)
|
||||||
|
|
||||||
|
@ -65,6 +69,19 @@ func (c *Client) UpdateTxtRecords(ctx context.Context, records []DNSRecord, doma
|
||||||
return c.do(req, nil)
|
return c.do(req, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteTxtRecords deletes all DNS Records for the specified Domain with the specified Type and Name.
|
||||||
|
// https://developer.godaddy.com/doc/endpoint/domains#/v1/recordDeleteTypeName
|
||||||
|
func (c *Client) DeleteTxtRecords(ctx context.Context, domainZone, recordName string) error {
|
||||||
|
endpoint := c.baseURL.JoinPath("v1", "domains", domainZone, "records", "TXT", recordName)
|
||||||
|
|
||||||
|
req, err := newJSONRequest(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 {
|
func (c *Client) do(req *http.Request, result any) error {
|
||||||
req.Header.Set(authorizationHeader, fmt.Sprintf("sso-key %s:%s", c.apiKey, c.apiSecret))
|
req.Header.Set(authorizationHeader, fmt.Sprintf("sso-key %s:%s", c.apiKey, c.apiSecret))
|
||||||
|
|
||||||
|
@ -75,8 +92,8 @@ func (c *Client) do(req *http.Request, result any) error {
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode/100 != 2 {
|
||||||
return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
|
return parseError(req, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
|
@ -119,3 +136,15 @@ func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, paylo
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseError(req *http.Request, resp *http.Response) error {
|
||||||
|
raw, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var errAPI APIError
|
||||||
|
err := json.Unmarshal(raw, &errAPI)
|
||||||
|
if err != nil {
|
||||||
|
return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("[status code: %d] %w", resp.StatusCode, &errAPI)
|
||||||
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ func TestClient_GetRecords_errors(t *testing.T) {
|
||||||
mux.HandleFunc("/v1/domains/example.com/records/TXT/", testHandler(http.MethodGet, http.StatusUnprocessableEntity, "errors.json"))
|
mux.HandleFunc("/v1/domains/example.com/records/TXT/", testHandler(http.MethodGet, http.StatusUnprocessableEntity, "errors.json"))
|
||||||
|
|
||||||
records, err := client.GetRecords(context.Background(), "example.com", "TXT", "")
|
records, err := client.GetRecords(context.Background(), "example.com", "TXT", "")
|
||||||
require.Error(t, err)
|
require.EqualError(t, err, "[status code: 422] INVALID_BODY: Request body doesn't fulfill schema, see details in `fields`")
|
||||||
assert.Nil(t, records)
|
assert.Nil(t, records)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,25 @@ func TestClient_UpdateTxtRecords_errors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := client.UpdateTxtRecords(context.Background(), records, "example.com", "lego")
|
err := client.UpdateTxtRecords(context.Background(), records, "example.com", "lego")
|
||||||
require.Error(t, err)
|
require.EqualError(t, err, "[status code: 422] INVALID_BODY: Request body doesn't fulfill schema, see details in `fields`")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_DeleteTxtRecords(t *testing.T) {
|
||||||
|
client, mux := setupTest(t)
|
||||||
|
|
||||||
|
mux.HandleFunc("/v1/domains/example.com/records/TXT/foo", testHandler(http.MethodDelete, http.StatusNoContent, ""))
|
||||||
|
|
||||||
|
err := client.DeleteTxtRecords(context.Background(), "example.com", "foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_DeleteTxtRecords_errors(t *testing.T) {
|
||||||
|
client, mux := setupTest(t)
|
||||||
|
|
||||||
|
mux.HandleFunc("/v1/domains/example.com/records/TXT/foo", testHandler(http.MethodDelete, http.StatusConflict, "error-extended.json"))
|
||||||
|
|
||||||
|
err := client.DeleteTxtRecords(context.Background(), "example.com", "foo")
|
||||||
|
require.EqualError(t, err, "[status code: 409] ACCESS_DENIED: Authenticated user is not allowed access [test: content (path=/foo) (pathRelated=/bar)]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testHandler(method string, statusCode int, filename string) http.HandlerFunc {
|
func testHandler(method string, statusCode int, filename string) http.HandlerFunc {
|
||||||
|
|
12
providers/dns/godaddy/internal/fixtures/error-extended.json
Normal file
12
providers/dns/godaddy/internal/fixtures/error-extended.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"code": "ACCESS_DENIED",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"code": "test",
|
||||||
|
"message": "content",
|
||||||
|
"path": "/foo",
|
||||||
|
"pathRelated": "/bar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"message": "Authenticated user is not allowed access"
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// DNSRecord a DNS record.
|
// DNSRecord a DNS record.
|
||||||
type DNSRecord struct {
|
type DNSRecord struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
@ -13,3 +15,42 @@ type DNSRecord struct {
|
||||||
Service string `json:"service,omitempty"`
|
Service string `json:"service,omitempty"`
|
||||||
Weight int `json:"weight,omitempty"`
|
Weight int `json:"weight,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type APIError struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Fields []Field `json:"fields,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a APIError) Error() string {
|
||||||
|
msg := fmt.Sprintf("%s: %s", a.Code, a.Message)
|
||||||
|
|
||||||
|
for _, field := range a.Fields {
|
||||||
|
msg += " " + field.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
PathRelated string `json:"pathRelated,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) String() string {
|
||||||
|
msg := fmt.Sprintf("[%s: %s", f.Code, f.Message)
|
||||||
|
|
||||||
|
if f.Path != "" {
|
||||||
|
msg += fmt.Sprintf(" (path=%s)", f.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.PathRelated != "" {
|
||||||
|
msg += fmt.Sprintf(" (pathRelated=%s)", f.PathRelated)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg += "]"
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue