client.go raw
1 package internal
2
3 import (
4 "bytes"
5 "context"
6 "encoding/xml"
7 "errors"
8 "fmt"
9 "io"
10 "net/http"
11 "net/url"
12 "time"
13
14 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
15 )
16
17 // Client the Plesk API client.
18 type Client struct {
19 login string
20 password string
21
22 baseURL *url.URL
23 HTTPClient *http.Client
24 }
25
26 // NewClient created a new Client.
27 func NewClient(baseURL *url.URL, login, password string) *Client {
28 return &Client{
29 login: login,
30 password: password,
31 baseURL: baseURL,
32 HTTPClient: &http.Client{Timeout: 10 * time.Second},
33 }
34 }
35
36 // GetSite gets a site.
37 // https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-sites-domains/getting-information-about-sites.66583/
38 func (c *Client) GetSite(ctx context.Context, domain string) (int, error) {
39 payload := RequestPacketType{Site: &SiteTypeRequest{Get: SiteGetRequest{Filter: &SiteFilterType{
40 Name: domain,
41 }}}}
42
43 response, err := c.doRequest(ctx, payload)
44 if err != nil {
45 return 0, err
46 }
47
48 if response.System != nil {
49 return 0, response.System
50 }
51
52 if response == nil || response.Site.Get.Result == nil {
53 return 0, errors.New("unexpected empty result")
54 }
55
56 if response.Site.Get.Result.Status != StatusOK {
57 return 0, response.Site.Get.Result
58 }
59
60 return response.Site.Get.Result.ID, nil
61 }
62
63 // AddRecord adds a TXT record.
64 // https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798/
65 func (c *Client) AddRecord(ctx context.Context, siteID int, host, value string) (int, error) {
66 payload := RequestPacketType{DNS: &DNSInputType{AddRec: []AddRecRequest{{
67 SiteID: siteID,
68 Type: "TXT",
69 Host: host,
70 Value: value,
71 }}}}
72
73 response, err := c.doRequest(ctx, payload)
74 if err != nil {
75 return 0, err
76 }
77
78 if response.System != nil {
79 return 0, response.System
80 }
81
82 if len(response.DNS.AddRec) < 1 {
83 return 0, errors.New("unexpected empty result")
84 }
85
86 if response.DNS.AddRec[0].Result.Status != StatusOK {
87 return 0, response.DNS.AddRec[0].Result
88 }
89
90 return response.DNS.AddRec[0].Result.ID, nil
91 }
92
93 // DeleteRecord Deletes a TXT record.
94 // https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/deleting-dns-records.34864/
95 func (c *Client) DeleteRecord(ctx context.Context, recordID int) (int, error) {
96 payload := RequestPacketType{DNS: &DNSInputType{DelRec: []DelRecRequest{{Filter: DNSSelectionFilterType{
97 ID: recordID,
98 }}}}}
99
100 response, err := c.doRequest(ctx, payload)
101 if err != nil {
102 return 0, err
103 }
104
105 if response.System != nil {
106 return 0, response.System
107 }
108
109 if len(response.DNS.DelRec) < 1 {
110 return 0, errors.New("unexpected empty result")
111 }
112
113 if response.DNS.DelRec[0].Result.Status != StatusOK {
114 return 0, response.DNS.DelRec[0].Result
115 }
116
117 return response.DNS.DelRec[0].Result.ID, nil
118 }
119
120 func (c *Client) doRequest(ctx context.Context, payload RequestPacketType) (*ResponsePacketType, error) {
121 endpoint := c.baseURL.JoinPath("/enterprise/control/agent.php")
122
123 body := new(bytes.Buffer)
124
125 err := xml.NewEncoder(body).Encode(payload)
126 if err != nil {
127 return nil, err
128 }
129
130 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), body)
131 if err != nil {
132 return nil, fmt.Errorf("unable to create request: %w", err)
133 }
134
135 req.Header.Set("Content-Type", "text/xml")
136
137 req.Header.Set("Http_auth_login", c.login)
138 req.Header.Set("Http_auth_passwd", c.password)
139
140 resp, err := c.HTTPClient.Do(req)
141 if err != nil {
142 return nil, errutils.NewHTTPDoError(req, err)
143 }
144
145 defer func() { _ = resp.Body.Close() }()
146
147 if resp.StatusCode/100 != 2 {
148 return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
149 }
150
151 raw, err := io.ReadAll(resp.Body)
152 if err != nil {
153 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
154 }
155
156 var response ResponsePacketType
157
158 err = xml.Unmarshal(raw, &response)
159 if err != nil {
160 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
161 }
162
163 return &response, nil
164 }
165