Skip to content

Commit d4febc3

Browse files
committed
Add DNS provider for ManageEngine CloudDNS
1 parent c2f179f commit d4febc3

12 files changed

+1023
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"net/url"
11+
"strconv"
12+
13+
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14+
)
15+
16+
const defaultBaseURL = "https://clouddns.manageengine.com/v1"
17+
18+
// Client the ManageEngine CloudDNS API client.
19+
type Client struct {
20+
baseURL *url.URL
21+
httpClient *http.Client
22+
}
23+
24+
// NewClient creates a new Client.
25+
func NewClient(ctx context.Context, clientID, clientSecret string) *Client {
26+
baseURL, _ := url.Parse(defaultBaseURL)
27+
28+
return &Client{
29+
baseURL: baseURL,
30+
httpClient: createOAuthClient(ctx, clientID, clientSecret),
31+
}
32+
}
33+
34+
// GetAllZones gets all zones.
35+
// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#GET_All
36+
func (c *Client) GetAllZones(ctx context.Context) ([]Zone, error) {
37+
endpoint := c.baseURL.JoinPath("dns", "domain")
38+
39+
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
var results []Zone
45+
46+
err = c.do(req, &results)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
return results, nil
52+
}
53+
54+
// GetAllZoneRecords gets all "zone records" for a zone.
55+
// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#GET_All_9
56+
func (c *Client) GetAllZoneRecords(ctx context.Context, zoneID int) ([]ZoneRecord, error) {
57+
endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT")
58+
59+
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
var results []ZoneRecord
65+
66+
err = c.do(req, &results)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
return results, nil
72+
}
73+
74+
// DeleteZoneRecord deletes a "zone record".
75+
// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#DEL_Delete_10
76+
func (c *Client) DeleteZoneRecord(ctx context.Context, zoneID int, recordID int) error {
77+
endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT", strconv.Itoa(recordID))
78+
79+
req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
80+
if err != nil {
81+
return err
82+
}
83+
84+
var results APIResponse
85+
86+
err = c.do(req, &results)
87+
if err != nil {
88+
return err
89+
}
90+
91+
return nil
92+
}
93+
94+
// CreateZoneRecord creates a "zone record".
95+
// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#POST_Create_10
96+
func (c *Client) CreateZoneRecord(ctx context.Context, zoneID int, record ZoneRecord) error {
97+
endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT")
98+
99+
req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
100+
if err != nil {
101+
return err
102+
}
103+
104+
var results APIResponse
105+
106+
err = c.do(req, &results)
107+
if err != nil {
108+
return err
109+
}
110+
111+
return nil
112+
}
113+
114+
func (c *Client) do(req *http.Request, result any) error {
115+
resp, err := c.httpClient.Do(req)
116+
if err != nil {
117+
return errutils.NewHTTPDoError(req, err)
118+
}
119+
120+
defer func() { _ = resp.Body.Close() }()
121+
122+
if resp.StatusCode/100 != 2 {
123+
return parseError(req, resp)
124+
}
125+
126+
if result == nil {
127+
return nil
128+
}
129+
130+
raw, err := io.ReadAll(resp.Body)
131+
if err != nil {
132+
return errutils.NewReadResponseError(req, resp.StatusCode, err)
133+
}
134+
135+
err = json.Unmarshal(raw, result)
136+
if err != nil {
137+
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
138+
}
139+
140+
return nil
141+
}
142+
143+
func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
144+
buf := new(bytes.Buffer)
145+
146+
if payload != nil {
147+
err := json.NewEncoder(buf).Encode(payload)
148+
if err != nil {
149+
return nil, fmt.Errorf("failed to create request JSON body: %w", err)
150+
}
151+
}
152+
153+
req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
154+
if err != nil {
155+
return nil, fmt.Errorf("unable to create request: %w", err)
156+
}
157+
158+
req.Header.Set("Accept", "application/json")
159+
160+
if payload != nil {
161+
req.Header.Set("Content-Type", "application/json")
162+
}
163+
164+
return req, nil
165+
}
166+
167+
func parseError(req *http.Request, resp *http.Response) error {
168+
raw, _ := io.ReadAll(resp.Body)
169+
170+
println(string(raw))
171+
172+
var errAPI APIError
173+
err := json.Unmarshal(raw, &errAPI)
174+
if err != nil {
175+
return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
176+
}
177+
178+
return fmt.Errorf("[status code: %d] %w", resp.StatusCode, &errAPI)
179+
}

0 commit comments

Comments
 (0)