provider/powerdns: Add support for PowerDNS 4 API (#7819)
* Auto-detect the API version
and update the endpoint URL accordingly
* Typo fix
* Make client and resource work with the 4.X API
* Update documentation
* Fix typos
* 204 now counts as a "success" response
See
f0e76cee2c
for the change in the pdns repository.
* Add a note about a possible pitfall when defining some records
This commit is contained in:
parent
14f19aff1b
commit
bbd9b2c944
|
@ -7,17 +7,17 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
// Location of PowerDNS server to use
|
||||
ServerUrl string
|
||||
// REST API Static authentication key
|
||||
ApiKey string
|
||||
Http *http.Client
|
||||
ServerUrl string // Location of PowerDNS server to use
|
||||
ApiKey string // REST API Static authentication key
|
||||
ApiVersion int // API version to use
|
||||
Http *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a new PowerDNS client
|
||||
|
@ -27,15 +27,26 @@ func NewClient(serverUrl string, apiKey string) (*Client, error) {
|
|||
ApiKey: apiKey,
|
||||
Http: cleanhttp.DefaultClient(),
|
||||
}
|
||||
var err error
|
||||
client.ApiVersion, err = client.detectApiVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
// Creates a new request with necessary headers
|
||||
func (c *Client) newRequest(method string, endpoint string, body []byte) (*http.Request, error) {
|
||||
|
||||
url, err := url.Parse(c.ServerUrl + endpoint)
|
||||
var urlStr string
|
||||
if c.ApiVersion > 0 {
|
||||
urlStr = c.ServerUrl + "/api/v" + strconv.Itoa(c.ApiVersion) + endpoint
|
||||
} else {
|
||||
urlStr = c.ServerUrl + endpoint
|
||||
}
|
||||
url, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error during parting request URL: %s", err)
|
||||
return nil, fmt.Errorf("Error during parsing request URL: %s", err)
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
|
@ -59,20 +70,21 @@ func (c *Client) newRequest(method string, endpoint string, body []byte) (*http.
|
|||
}
|
||||
|
||||
type ZoneInfo struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Kind string `json:"kind"`
|
||||
DnsSec bool `json:"dnsssec"`
|
||||
Serial int64 `json:"serial"`
|
||||
Records []Record `json:"records,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Kind string `json:"kind"`
|
||||
DnsSec bool `json:"dnsssec"`
|
||||
Serial int64 `json:"serial"`
|
||||
Records []Record `json:"records,omitempty"`
|
||||
ResourceRecordSets []ResourceRecordSet `json:"rrsets,omitempty"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
TTL int `json:"ttl"`
|
||||
TTL int `json:"ttl"` // For API v0
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
|
@ -80,6 +92,7 @@ type ResourceRecordSet struct {
|
|||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
ChangeType string `json:"changetype"`
|
||||
TTL int `json:"ttl"` // For API v1
|
||||
Records []Record `json:"records,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -111,6 +124,26 @@ func parseId(recId string) (string, string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Detects the API version in use on the server
|
||||
// Uses int to represent the API version: 0 is the legacy AKA version 3.4 API
|
||||
// Any other integer correlates with the same API version
|
||||
func (client *Client) detectApiVersion() (int, error) {
|
||||
req, err := client.newRequest("GET", "/api/v1/servers", nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
resp, err := client.Http.Do(req)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == 200 {
|
||||
return 1, nil
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Returns all Zones of server, without records
|
||||
func (client *Client) ListZones() ([]ZoneInfo, error) {
|
||||
|
||||
|
@ -154,7 +187,20 @@ func (client *Client) ListRecords(zone string) ([]Record, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return zoneInfo.Records, nil
|
||||
records := zoneInfo.Records
|
||||
// Convert the API v1 response to v0 record structure
|
||||
for _, rrs := range zoneInfo.ResourceRecordSets {
|
||||
for _, record := range rrs.Records {
|
||||
records = append(records, Record{
|
||||
Name: rrs.Name,
|
||||
Type: rrs.Type,
|
||||
Content: record.Content,
|
||||
TTL: rrs.TTL,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Returns only records of specified name and type
|
||||
|
@ -232,7 +278,7 @@ func (client *Client) CreateRecord(zone string, record Record) (string, error) {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 204 {
|
||||
errorResp := new(errorResponse)
|
||||
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
|
||||
return "", fmt.Errorf("Error creating record: %s", record.Id())
|
||||
|
@ -263,7 +309,7 @@ func (client *Client) ReplaceRecordSet(zone string, rrSet ResourceRecordSet) (st
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 204 {
|
||||
errorResp := new(errorResponse)
|
||||
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
|
||||
return "", fmt.Errorf("Error creating record set: %s", rrSet.Id())
|
||||
|
@ -298,7 +344,7 @@ func (client *Client) DeleteRecordSet(zone string, name string, tpe string) erro
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 204 {
|
||||
errorResp := new(errorResponse)
|
||||
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
|
||||
return fmt.Errorf("Error deleting record: %s %s", name, tpe)
|
||||
|
|
|
@ -57,6 +57,7 @@ func resourcePDNSRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
rrSet := ResourceRecordSet{
|
||||
Name: d.Get("name").(string),
|
||||
Type: d.Get("type").(string),
|
||||
TTL: d.Get("ttl").(int),
|
||||
}
|
||||
|
||||
zone := d.Get("zone").(string)
|
||||
|
|
|
@ -9,7 +9,7 @@ description: |-
|
|||
# PowerDNS Provider
|
||||
|
||||
The PowerDNS provider is used manipulate DNS records supported by PowerDNS server. The provider needs to be configured
|
||||
with the proper credentials before it can be used.
|
||||
with the proper credentials before it can be used. It supports both the [legacy API](https://doc.powerdns.com/3/httpapi/api_spec/) and the new [version 1 API](https://doc.powerdns.com/md/httpapi/api_spec/), however resources may need to be configured differently.
|
||||
|
||||
Use the navigation to the left to read about the available resources.
|
||||
|
||||
|
|
|
@ -12,6 +12,21 @@ Provides a PowerDNS record resource.
|
|||
|
||||
## Example Usage
|
||||
|
||||
Note that PowerDNS internally lowercases certain records (e.g. CNAME and AAAA), which can lead to resources being marked for a change in every singe plan.
|
||||
|
||||
For the v1 API (PowerDNS version 4):
|
||||
```
|
||||
# Add a record to the zone
|
||||
resource "powerdns_record" "foobar" {
|
||||
zone = "example.com."
|
||||
name = "www.example.com"
|
||||
type = "A"
|
||||
ttl = 300
|
||||
records = ["192.168.0.11"]
|
||||
}
|
||||
```
|
||||
|
||||
For the legacy API (PowerDNS version 3.4):
|
||||
```
|
||||
# Add a record to the zone
|
||||
resource "powerdns_record" "foobar" {
|
||||
|
|
Loading…
Reference in New Issue