Skip to content

Commit

Permalink
Add DNS Provider for Vscale (#705)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstdfx authored and ldez committed Nov 8, 2018
1 parent 1837a3b commit e89afae
Show file tree
Hide file tree
Showing 7 changed files with 523 additions and 19 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ owners to license your work under the terms of the [MIT License](LICENSE).
| Stackpath | `stackpath` | [documentation](https://developer.stackpath.com/en/api/dns/#tag/Zone) | - |
| VegaDNS | `vegadns` | [documentation](https://github.com/shupp/VegaDNS-API) | [Go client](https://github.com/OpenDNS/vegadns2client) |
| Vultr | `vultr` | [documentation](https://www.vultr.com/api/#dns) | [Go client](https://github.com/JamesClonk/vultr) |
| Vscale | `vscale` | [documentation](https://developers.vscale.io/documentation/api/v1/#api-Domains_Records) | - |
2 changes: 2 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tselectel:\tSELECTEL_API_TOKEN")
fmt.Fprintln(w, "\tvegadns:\tSECRET_VEGADNS_KEY, SECRET_VEGADNS_SECRET, VEGADNS_URL")
fmt.Fprintln(w, "\tvultr:\tVULTR_API_KEY")
fmt.Fprintln(w, "\tvscale:\tVSCALE_API_TOKEN")
fmt.Fprintln(w)
fmt.Fprintln(w, "Additional configuration environment variables:")
fmt.Fprintln(w)
Expand Down Expand Up @@ -295,6 +296,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tselectel:\tSELECTEL_BASE_URL, SELECTEL_TTL, SELECTEL_PROPAGATION_TIMEOUT, SELECTEL_POLLING_INTERVAL, SELECTEL_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tvegadns:\tVEGADNS_POLLING_INTERVAL, VEGADNS_PROPAGATION_TIMEOUT, VEGADNS_TTL")
fmt.Fprintln(w, "\tvultr:\tVULTR_POLLING_INTERVAL, VULTR_PROPAGATION_TIMEOUT, VULTR_TTL, VULTR_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tvscale:\tVSCALE_BASE_URL, VSCALE_TTL, VSCALE_PROPAGATION_TIMEOUT, VSCALE_POLLING_INTERVAL, VSCALE_HTTP_TIMEOUT")

w.Flush()

Expand Down
3 changes: 3 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
"github.com/xenolf/lego/providers/dns/stackpath"
"github.com/xenolf/lego/providers/dns/transip"
"github.com/xenolf/lego/providers/dns/vegadns"
"github.com/xenolf/lego/providers/dns/vscale"
"github.com/xenolf/lego/providers/dns/vultr"
)

Expand Down Expand Up @@ -152,6 +153,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return vegadns.NewDNSProvider()
case "vultr":
return vultr.NewDNSProvider()
case "vscale":
return vscale.NewDNSProvider()
default:
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
}
Expand Down
43 changes: 24 additions & 19 deletions providers/dns/selectel/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ type Record struct {
Content string `json:"content,omitempty"` // Record content (not for SRV)
}

// Client represents DNS client.
type Client struct {
baseURL string
token string
userAgent string
httpClient *http.Client
// APIError API error message
type APIError struct {
Description string `json:"error"`
Code int `json:"code"`
Field string `json:"field"`
}

func (a *APIError) Error() string {
return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
}

// ClientOpts represents options to init client.
Expand All @@ -40,15 +43,12 @@ type ClientOpts struct {
HTTPClient *http.Client
}

// APIError API error message
type APIError struct {
Description string `json:"error"`
Code int `json:"code"`
Field string `json:"field"`
}

func (a *APIError) Error() string {
return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
// Client represents DNS client.
type Client struct {
baseURL string
token string
userAgent string
httpClient *http.Client
}

// NewClient returns a client instance.
Expand Down Expand Up @@ -161,8 +161,8 @@ func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) {
}

if to != nil {
if err = extractResult(resp, to); err != nil {
return resp, fmt.Errorf("failed to extract request body into interface with error: %v", err)
if err = unmarshalBody(resp, to); err != nil {
return resp, err
}
}

Expand Down Expand Up @@ -195,12 +195,17 @@ func checkResponse(resp *http.Response) error {
return nil
}

func extractResult(resp *http.Response, to interface{}) error {
func unmarshalBody(resp *http.Response, to interface{}) error {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()

return json.Unmarshal(body, to)
err = json.Unmarshal(body, to)
if err != nil {
return fmt.Errorf("unmarshaling error: %v: %s", err, string(body))
}

return nil
}
211 changes: 211 additions & 0 deletions providers/dns/vscale/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package vscale

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

// Domain represents domain name.
type Domain struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}

// Record represents DNS record.
type Record struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF)
TTL int `json:"ttl,omitempty"`
Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records)
Content string `json:"content,omitempty"` // Record content (not for SRV)
}

// APIError API error message
type APIError struct {
Description string `json:"error"`
Code int `json:"code"`
Field string `json:"field"`
}

func (a *APIError) Error() string {
return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
}

// ClientOpts represents options to init client.
type ClientOpts struct {
BaseURL string
Token string
UserAgent string
HTTPClient *http.Client
}

// Client represents DNS client.
type Client struct {
baseURL string
token string
userAgent string
httpClient *http.Client
}

// NewClient returns a client instance.
func NewClient(opts ClientOpts) *Client {
if opts.HTTPClient == nil {
opts.HTTPClient = &http.Client{}
}

return &Client{
token: opts.Token,
baseURL: opts.BaseURL,
httpClient: opts.HTTPClient,
userAgent: opts.UserAgent,
}
}

// GetDomainByName gets Domain object by its name.
func (c *Client) GetDomainByName(domainName string) (*Domain, error) {
uri := fmt.Sprintf("/%s", domainName)
req, err := c.newRequest(http.MethodGet, uri, nil)
if err != nil {
return nil, err
}

domain := &Domain{}
_, err = c.do(req, domain)
if err != nil {
return nil, err
}

return domain, nil
}

// AddRecord adds Record for given domain.
func (c *Client) AddRecord(domainID int, body Record) (*Record, error) {
uri := fmt.Sprintf("/%d/records/", domainID)
req, err := c.newRequest(http.MethodPost, uri, body)
if err != nil {
return nil, err
}

record := &Record{}
_, err = c.do(req, record)
if err != nil {
return nil, err
}

return record, nil
}

// ListRecords returns list records for specific domain.
func (c *Client) ListRecords(domainID int) ([]*Record, error) {
uri := fmt.Sprintf("/%d/records/", domainID)
req, err := c.newRequest(http.MethodGet, uri, nil)
if err != nil {
return nil, err
}

var records []*Record
_, err = c.do(req, &records)
if err != nil {
return nil, err
}
return records, nil
}

// DeleteRecord deletes specific record.
func (c *Client) DeleteRecord(domainID, recordID int) error {
uri := fmt.Sprintf("/%d/records/%d", domainID, recordID)
req, err := c.newRequest(http.MethodDelete, uri, nil)
if err != nil {
return err
}

_, err = c.do(req, nil)
return err
}

func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) {
buf := new(bytes.Buffer)

if body != nil {
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, fmt.Errorf("failed to encode request body with error: %v", err)
}
}

req, err := http.NewRequest(method, c.baseURL+uri, buf)
if err != nil {
return nil, fmt.Errorf("failed to create new http request with error: %v", err)
}

req.Header.Add("X-Token", c.token)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")

return req, nil
}

func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) {
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed with error: %v", err)
}

err = checkResponse(resp)
if err != nil {
return resp, err
}

if to != nil {
if err = unmarshalBody(resp, to); err != nil {
return resp, err
}
}

return resp, nil
}

func checkResponse(resp *http.Response) error {
if resp.StatusCode >= http.StatusBadRequest &&
resp.StatusCode <= http.StatusNetworkAuthenticationRequired {

if resp.Body == nil {
return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode)
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()

apiError := APIError{}
err = json.Unmarshal(body, &apiError)
if err != nil {
return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body))
}

return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError)
}

return nil
}

func unmarshalBody(resp *http.Response, to interface{}) error {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()

err = json.Unmarshal(body, to)
if err != nil {
return fmt.Errorf("unmarshaling error: %v: %s", err, string(body))
}

return nil
}
Loading

0 comments on commit e89afae

Please sign in to comment.