|
|
@@ -1,4 +1,4 @@
|
|
|
-package bind
|
|
|
+package powerdns
|
|
|
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
@@ -10,57 +10,96 @@ import (
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
-// Client contains an API client for a Bind DNS server wrapped
|
|
|
-// with a lightweight API
|
|
|
+// Client contains an API client for a PowerDNS server
|
|
|
type Client struct {
|
|
|
apiKey string
|
|
|
serverURL string
|
|
|
+ runDomain string
|
|
|
|
|
|
httpClient *http.Client
|
|
|
}
|
|
|
|
|
|
// NewClient creates a new bind API client
|
|
|
-func NewClient(serverURL, apiKey string) *Client {
|
|
|
+func NewClient(serverURL, apiKey, runDomain string) *Client {
|
|
|
httpClient := &http.Client{
|
|
|
Timeout: time.Minute,
|
|
|
}
|
|
|
|
|
|
- return &Client{apiKey, serverURL, httpClient}
|
|
|
+ return &Client{apiKey, serverURL, runDomain, httpClient}
|
|
|
}
|
|
|
|
|
|
// RecordData represents the data required to create or delete an A/CNAME record
|
|
|
// for the nameserver
|
|
|
type RecordData struct {
|
|
|
- Value string `json:"value"`
|
|
|
+ RRSets []RR `json:"rrsets"`
|
|
|
+}
|
|
|
+
|
|
|
+type RR struct {
|
|
|
+ Name string `json:"name"`
|
|
|
+ Type string `json:"type"`
|
|
|
+ ChangeType string `json:"changetype"`
|
|
|
+ TTL uint `json:"ttl"`
|
|
|
+ Records []Record `json:"record"`
|
|
|
+}
|
|
|
+
|
|
|
+type Record struct {
|
|
|
+ Content string `json:"content"`
|
|
|
+ Disabled bool `json:"disabled"`
|
|
|
+ Name string `json:"name"`
|
|
|
Type string `json:"type"`
|
|
|
- Hostname string `json:"hostname"`
|
|
|
+ Priority uint `json:"priority"`
|
|
|
}
|
|
|
|
|
|
// CreateCNAMERecord creates a new CNAME record for the nameserver
|
|
|
func (c *Client) CreateCNAMERecord(value, hostname string) error {
|
|
|
- return c.sendRequest("POST", &RecordData{
|
|
|
- Value: value,
|
|
|
- Type: "CNAME",
|
|
|
- Hostname: hostname,
|
|
|
+ valueC := canonicalize(value)
|
|
|
+ hostnameC := canonicalize(hostname)
|
|
|
+
|
|
|
+ return c.sendRequest("PATCH", &RecordData{
|
|
|
+ RRSets: []RR{{
|
|
|
+ Name: hostnameC,
|
|
|
+ Type: "CNAME",
|
|
|
+ ChangeType: "REPLACE",
|
|
|
+ TTL: 300,
|
|
|
+ Records: []Record{{
|
|
|
+ Content: valueC,
|
|
|
+ Disabled: false,
|
|
|
+ Name: hostnameC,
|
|
|
+ Type: "CNAME",
|
|
|
+ Priority: 0,
|
|
|
+ }},
|
|
|
+ }},
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// CreateARecord creates a new A record for the nameserver
|
|
|
func (c *Client) CreateARecord(value, hostname string) error {
|
|
|
- return c.sendRequest("POST", &RecordData{
|
|
|
- Value: value,
|
|
|
- Type: "A",
|
|
|
- Hostname: hostname,
|
|
|
+ hostnameC := canonicalize(hostname)
|
|
|
+
|
|
|
+ return c.sendRequest("PATCH", &RecordData{
|
|
|
+ RRSets: []RR{{
|
|
|
+ Name: hostnameC,
|
|
|
+ Type: "A",
|
|
|
+ ChangeType: "REPLACE",
|
|
|
+ TTL: 300,
|
|
|
+ Records: []Record{{
|
|
|
+ Content: value,
|
|
|
+ Disabled: false,
|
|
|
+ Name: hostnameC,
|
|
|
+ Type: "A",
|
|
|
+ Priority: 0,
|
|
|
+ }},
|
|
|
+ }},
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-// DeleteARecord deletes a new A record for the nameserver
|
|
|
-func (c *Client) DeleteARecord(value, hostname string) error {
|
|
|
- return c.sendRequest("DELETE", &RecordData{
|
|
|
- Value: value,
|
|
|
- Type: "A",
|
|
|
- Hostname: hostname,
|
|
|
- })
|
|
|
+func canonicalize(value string) string {
|
|
|
+ // if the string ends in a period, return
|
|
|
+ if value[len(value)-1:] == "." {
|
|
|
+ return value
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Sprintf("%s.", value)
|
|
|
}
|
|
|
|
|
|
func (c *Client) sendRequest(method string, data *RecordData) error {
|
|
|
@@ -70,7 +109,7 @@ func (c *Client) sendRequest(method string, data *RecordData) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- reqURL.Path = "/dns"
|
|
|
+ reqURL.Path = fmt.Sprintf("/api/v1/servers/localhost/zones/%s", c.runDomain)
|
|
|
|
|
|
strData, err := json.Marshal(data)
|
|
|
|