diff --git a/builtin/providers/powerdns/client.go b/builtin/providers/powerdns/client.go index ca54ac718c..9b53011fe1 100644 --- a/builtin/providers/powerdns/client.go +++ b/builtin/providers/powerdns/client.go @@ -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) diff --git a/builtin/providers/powerdns/resource_powerdns_record.go b/builtin/providers/powerdns/resource_powerdns_record.go index 11f88a5828..b5f9e06876 100644 --- a/builtin/providers/powerdns/resource_powerdns_record.go +++ b/builtin/providers/powerdns/resource_powerdns_record.go @@ -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) diff --git a/website/source/docs/providers/powerdns/index.html.markdown b/website/source/docs/providers/powerdns/index.html.markdown index 172a7ea207..edd82d255c 100644 --- a/website/source/docs/providers/powerdns/index.html.markdown +++ b/website/source/docs/providers/powerdns/index.html.markdown @@ -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. diff --git a/website/source/docs/providers/powerdns/r/record.html.markdown b/website/source/docs/providers/powerdns/r/record.html.markdown index 785c0cd487..8d9502604d 100644 --- a/website/source/docs/providers/powerdns/r/record.html.markdown +++ b/website/source/docs/providers/powerdns/r/record.html.markdown @@ -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" {