2019-03-11 16:56:48 +00:00
|
|
|
package internal
|
2018-07-01 00:13:22 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2019-01-22 07:34:45 +00:00
|
|
|
defaultBaseURL = "https://dns.api.nifcloud.com"
|
2018-09-15 17:07:24 +00:00
|
|
|
apiVersion = "2012-12-12N2013-12-16"
|
2020-08-09 14:39:44 +00:00
|
|
|
// XMLNs XML NS of Route53.
|
2018-12-06 21:50:17 +00:00
|
|
|
XMLNs = "https://route53.amazonaws.com/doc/2012-12-12/"
|
2018-07-01 00:13:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ChangeResourceRecordSetsRequest is a complex type that contains change information for the resource record set.
|
|
|
|
type ChangeResourceRecordSetsRequest struct {
|
|
|
|
XMLNs string `xml:"xmlns,attr"`
|
|
|
|
ChangeBatch ChangeBatch `xml:"ChangeBatch"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeResourceRecordSetsResponse is a complex type containing the response for the request.
|
|
|
|
type ChangeResourceRecordSetsResponse struct {
|
|
|
|
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetChangeResponse is a complex type that contains the ChangeInfo element.
|
|
|
|
type GetChangeResponse struct {
|
|
|
|
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorResponse is the information for any errors.
|
|
|
|
type ErrorResponse struct {
|
|
|
|
Error struct {
|
|
|
|
Type string `xml:"Type"`
|
|
|
|
Message string `xml:"Message"`
|
|
|
|
Code string `xml:"Code"`
|
|
|
|
} `xml:"Error"`
|
|
|
|
RequestID string `xml:"RequestId"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeBatch is the information for a change request.
|
|
|
|
type ChangeBatch struct {
|
|
|
|
Changes Changes `xml:"Changes"`
|
|
|
|
Comment string `xml:"Comment"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Changes is array of Change.
|
|
|
|
type Changes struct {
|
|
|
|
Change []Change `xml:"Change"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change is the information for each resource record set that you want to change.
|
|
|
|
type Change struct {
|
|
|
|
Action string `xml:"Action"`
|
|
|
|
ResourceRecordSet ResourceRecordSet `xml:"ResourceRecordSet"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResourceRecordSet is the information about the resource record set to create or delete.
|
|
|
|
type ResourceRecordSet struct {
|
|
|
|
Name string `xml:"Name"`
|
|
|
|
Type string `xml:"Type"`
|
|
|
|
TTL int `xml:"TTL"`
|
|
|
|
ResourceRecords ResourceRecords `xml:"ResourceRecords"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResourceRecords is array of ResourceRecord.
|
|
|
|
type ResourceRecords struct {
|
|
|
|
ResourceRecord []ResourceRecord `xml:"ResourceRecord"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResourceRecord is the information specific to the resource record.
|
|
|
|
type ResourceRecord struct {
|
|
|
|
Value string `xml:"Value"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeInfo is A complex type that describes change information about changes made to your hosted zone.
|
|
|
|
type ChangeInfo struct {
|
|
|
|
ID string `xml:"Id"`
|
|
|
|
Status string `xml:"Status"`
|
|
|
|
SubmittedAt string `xml:"SubmittedAt"`
|
|
|
|
}
|
|
|
|
|
2020-05-08 17:35:25 +00:00
|
|
|
// NewClient Creates a new client of NIFCLOUD DNS.
|
2020-07-09 23:48:18 +00:00
|
|
|
func NewClient(accessKey, secretKey string) (*Client, error) {
|
2021-03-04 19:16:59 +00:00
|
|
|
if accessKey == "" || secretKey == "" {
|
2018-10-12 17:29:18 +00:00
|
|
|
return nil, errors.New("credentials missing")
|
|
|
|
}
|
|
|
|
|
2018-07-01 00:13:22 +00:00
|
|
|
return &Client{
|
2018-09-15 17:07:24 +00:00
|
|
|
accessKey: accessKey,
|
|
|
|
secretKey: secretKey,
|
|
|
|
BaseURL: defaultBaseURL,
|
|
|
|
HTTPClient: &http.Client{},
|
2018-10-12 17:29:18 +00:00
|
|
|
}, nil
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 17:35:25 +00:00
|
|
|
// Client client of NIFCLOUD DNS.
|
2018-07-01 00:13:22 +00:00
|
|
|
type Client struct {
|
2018-09-15 17:07:24 +00:00
|
|
|
accessKey string
|
|
|
|
secretKey string
|
|
|
|
BaseURL string
|
|
|
|
HTTPClient *http.Client
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeResourceRecordSets Call ChangeResourceRecordSets API and return response.
|
|
|
|
func (c *Client) ChangeResourceRecordSets(hostedZoneID string, input ChangeResourceRecordSetsRequest) (*ChangeResourceRecordSetsResponse, error) {
|
2018-09-15 17:07:24 +00:00
|
|
|
requestURL := fmt.Sprintf("%s/%s/hostedzone/%s/rrset", c.BaseURL, apiVersion, hostedZoneID)
|
2018-07-01 00:13:22 +00:00
|
|
|
|
|
|
|
body := &bytes.Buffer{}
|
|
|
|
body.Write([]byte(xml.Header))
|
|
|
|
err := xml.NewEncoder(body).Encode(input)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodPost, requestURL, body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
|
|
|
|
|
|
|
|
err = c.sign(req)
|
|
|
|
if err != nil {
|
2020-02-27 18:14:46 +00:00
|
|
|
return nil, fmt.Errorf("an error occurred during the creation of the signature: %w", err)
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
2018-09-15 17:07:24 +00:00
|
|
|
res, err := c.HTTPClient.Do(req)
|
2018-07-01 00:13:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if res.Body == nil {
|
|
|
|
return nil, errors.New("the response body is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
errResp := &ErrorResponse{}
|
|
|
|
err = xml.NewDecoder(res.Body).Decode(errResp)
|
|
|
|
if err != nil {
|
2020-02-27 18:14:46 +00:00
|
|
|
return nil, fmt.Errorf("an error occurred while unmarshaling the error body to XML: %w", err)
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("an error occurred: %s", errResp.Error.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
output := &ChangeResourceRecordSetsResponse{}
|
|
|
|
err = xml.NewDecoder(res.Body).Decode(output)
|
|
|
|
if err != nil {
|
2020-02-27 18:14:46 +00:00
|
|
|
return nil, fmt.Errorf("an error occurred while unmarshaling the response body to XML: %w", err)
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return output, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetChange Call GetChange API and return response.
|
|
|
|
func (c *Client) GetChange(statusID string) (*GetChangeResponse, error) {
|
2018-09-15 17:07:24 +00:00
|
|
|
requestURL := fmt.Sprintf("%s/%s/change/%s", c.BaseURL, apiVersion, statusID)
|
2018-07-01 00:13:22 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = c.sign(req)
|
|
|
|
if err != nil {
|
2020-02-27 18:14:46 +00:00
|
|
|
return nil, fmt.Errorf("an error occurred during the creation of the signature: %w", err)
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
2018-09-15 17:07:24 +00:00
|
|
|
res, err := c.HTTPClient.Do(req)
|
2018-07-01 00:13:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if res.Body == nil {
|
|
|
|
return nil, errors.New("the response body is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
errResp := &ErrorResponse{}
|
|
|
|
err = xml.NewDecoder(res.Body).Decode(errResp)
|
|
|
|
if err != nil {
|
2020-02-27 18:14:46 +00:00
|
|
|
return nil, fmt.Errorf("an error occurred while unmarshaling the error body to XML: %w", err)
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("an error occurred: %s", errResp.Error.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
output := &GetChangeResponse{}
|
|
|
|
err = xml.NewDecoder(res.Body).Decode(output)
|
|
|
|
if err != nil {
|
2020-02-27 18:14:46 +00:00
|
|
|
return nil, fmt.Errorf("an error occurred while unmarshaling the response body to XML: %w", err)
|
2018-07-01 00:13:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return output, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) sign(req *http.Request) error {
|
|
|
|
if req.Header.Get("Date") == "" {
|
|
|
|
location, err := time.LoadLocation("GMT")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Date", time.Now().In(location).Format(time.RFC1123))
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.URL.Path == "" {
|
|
|
|
req.URL.Path += "/"
|
|
|
|
}
|
|
|
|
|
|
|
|
mac := hmac.New(sha1.New, []byte(c.secretKey))
|
|
|
|
_, err := mac.Write([]byte(req.Header.Get("Date")))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
hashed := mac.Sum(nil)
|
|
|
|
signature := base64.StdEncoding.EncodeToString(hashed)
|
|
|
|
|
|
|
|
auth := fmt.Sprintf("NIFTY3-HTTPS NiftyAccessKeyId=%s,Algorithm=HmacSHA1,Signature=%s", c.accessKey, signature)
|
|
|
|
req.Header.Set("X-Nifty-Authorization", auth)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|