2023-02-26 23:53:57 +00:00
|
|
|
package internal
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-05-05 07:49:38 +00:00
|
|
|
"context"
|
2023-02-26 23:53:57 +00:00
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
2023-05-05 07:49:38 +00:00
|
|
|
|
|
|
|
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
2023-02-26 23:53:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Client the Plesk API client.
|
|
|
|
type Client struct {
|
2023-05-05 07:49:38 +00:00
|
|
|
login string
|
|
|
|
password string
|
|
|
|
|
2023-02-26 23:53:57 +00:00
|
|
|
baseURL *url.URL
|
2023-05-05 07:49:38 +00:00
|
|
|
HTTPClient *http.Client
|
2023-02-26 23:53:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewClient created a new Client.
|
|
|
|
func NewClient(baseURL *url.URL, login string, password string) *Client {
|
|
|
|
return &Client{
|
|
|
|
login: login,
|
|
|
|
password: password,
|
2023-05-05 07:49:38 +00:00
|
|
|
baseURL: baseURL,
|
|
|
|
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
2023-02-26 23:53:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSite gets a site.
|
|
|
|
// https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-sites-domains/getting-information-about-sites.66583/
|
2023-05-05 07:49:38 +00:00
|
|
|
func (c Client) GetSite(ctx context.Context, domain string) (int, error) {
|
2023-02-26 23:53:57 +00:00
|
|
|
payload := RequestPacketType{Site: &SiteTypeRequest{Get: SiteGetRequest{Filter: &SiteFilterType{
|
|
|
|
Name: domain,
|
|
|
|
}}}}
|
|
|
|
|
2023-05-05 07:49:38 +00:00
|
|
|
response, err := c.doRequest(ctx, payload)
|
2023-02-26 23:53:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.System != nil {
|
|
|
|
return 0, response.System
|
|
|
|
}
|
|
|
|
|
|
|
|
if response == nil || response.Site.Get.Result == nil {
|
|
|
|
return 0, errors.New("unexpected empty result")
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.Site.Get.Result.Status != StatusOK {
|
|
|
|
return 0, response.Site.Get.Result
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.Site.Get.Result.ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddRecord adds a TXT record.
|
|
|
|
// https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798/
|
2023-05-05 07:49:38 +00:00
|
|
|
func (c Client) AddRecord(ctx context.Context, siteID int, host, value string) (int, error) {
|
2023-02-26 23:53:57 +00:00
|
|
|
payload := RequestPacketType{DNS: &DNSInputType{AddRec: []AddRecRequest{{
|
|
|
|
SiteID: siteID,
|
|
|
|
Type: "TXT",
|
|
|
|
Host: host,
|
|
|
|
Value: value,
|
|
|
|
}}}}
|
|
|
|
|
2023-05-05 07:49:38 +00:00
|
|
|
response, err := c.doRequest(ctx, payload)
|
2023-02-26 23:53:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.System != nil {
|
|
|
|
return 0, response.System
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(response.DNS.AddRec) < 1 {
|
|
|
|
return 0, errors.New("unexpected empty result")
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.DNS.AddRec[0].Result.Status != StatusOK {
|
|
|
|
return 0, response.DNS.AddRec[0].Result
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.DNS.AddRec[0].Result.ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteRecord Deletes a TXT record.
|
|
|
|
// https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/deleting-dns-records.34864/
|
2023-05-05 07:49:38 +00:00
|
|
|
func (c Client) DeleteRecord(ctx context.Context, recordID int) (int, error) {
|
2023-02-26 23:53:57 +00:00
|
|
|
payload := RequestPacketType{DNS: &DNSInputType{DelRec: []DelRecRequest{{Filter: DNSSelectionFilterType{
|
|
|
|
ID: recordID,
|
|
|
|
}}}}}
|
|
|
|
|
2023-05-05 07:49:38 +00:00
|
|
|
response, err := c.doRequest(ctx, payload)
|
2023-02-26 23:53:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.System != nil {
|
|
|
|
return 0, response.System
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(response.DNS.DelRec) < 1 {
|
|
|
|
return 0, errors.New("unexpected empty result")
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.DNS.DelRec[0].Result.Status != StatusOK {
|
|
|
|
return 0, response.DNS.DelRec[0].Result
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.DNS.DelRec[0].Result.ID, nil
|
|
|
|
}
|
|
|
|
|
2023-05-05 07:49:38 +00:00
|
|
|
func (c Client) doRequest(ctx context.Context, payload RequestPacketType) (*ResponsePacketType, error) {
|
2023-02-26 23:53:57 +00:00
|
|
|
endpoint := c.baseURL.JoinPath("/enterprise/control/agent.php")
|
|
|
|
|
2023-05-05 07:49:38 +00:00
|
|
|
body := new(bytes.Buffer)
|
2023-02-26 23:53:57 +00:00
|
|
|
err := xml.NewEncoder(body).Encode(payload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-05-05 07:49:38 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to create request: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-02-26 23:53:57 +00:00
|
|
|
req.Header.Set("Content-Type", "text/xml")
|
2023-05-05 07:49:38 +00:00
|
|
|
|
2023-02-26 23:53:57 +00:00
|
|
|
req.Header.Set("Http_auth_login", c.login)
|
|
|
|
req.Header.Set("Http_auth_passwd", c.password)
|
|
|
|
|
|
|
|
resp, err := c.HTTPClient.Do(req)
|
|
|
|
if err != nil {
|
2023-05-05 07:49:38 +00:00
|
|
|
return nil, errutils.NewHTTPDoError(req, err)
|
2023-02-26 23:53:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
|
|
|
|
if resp.StatusCode/100 != 2 {
|
2023-05-05 07:49:38 +00:00
|
|
|
return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
raw, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
|
2023-02-26 23:53:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var response ResponsePacketType
|
2023-05-05 07:49:38 +00:00
|
|
|
err = xml.Unmarshal(raw, &response)
|
2023-02-26 23:53:57 +00:00
|
|
|
if err != nil {
|
2023-05-05 07:49:38 +00:00
|
|
|
return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
|
2023-02-26 23:53:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|