diff --git a/CHANGELOG.md b/CHANGELOG.md index 562db828..10477b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## 1.5.0 (Unreleased) FEATURES: - * **Added option to cache PowerDNS API response ** ([#81](https://github.com/pan-net/terraform-provider-powerdns/pull/81), @menai34) + * **Added option to cache PowerDNS API response** ([#81](https://github.com/pan-net/terraform-provider-powerdns/pull/81), @menai34) + * **Added comments attribute for powerdns zone resource** ([#87](https://github.com/pan-net/terraform-provider-powerdns/pull/87), @PetrusHahol) ## 1.4.1 (January 21, 2021) diff --git a/powerdns/client.go b/powerdns/client.go index db3b16f5..4ef5220e 100644 --- a/powerdns/client.go +++ b/powerdns/client.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" - freecache "github.com/coocood/freecache" - cleanhttp "github.com/hashicorp/go-cleanhttp" + "github.com/coocood/freecache" + "github.com/hashicorp/go-cleanhttp" ) // DefaultSchema is the value used for the URL in case @@ -197,11 +197,17 @@ type Record struct { // ResourceRecordSet represents a PowerDNS RRSet object 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"` + Name string `json:"name"` + Type string `json:"type"` + ChangeType string `json:"changetype"` + TTL int `json:"ttl"` // For API v1 + Records []Record `json:"records,omitempty"` + Comments []Comment `json:"comments,omitempty"` +} + +type Comment struct { + Content string `json:"content,omitempty"` + Account string `json:"account,omitempty"` } type zonePatchRequest struct { @@ -460,11 +466,31 @@ func (client *Client) GetZoneInfoFromCache(zone string) (*ZoneInfo, error) { // ListRecords returns all records in Zone func (client *Client) ListRecords(zone string) ([]Record, error) { + rrsets, err := client.ListRRSets(zone) + if err != nil { + return nil, err + } + + var records []Record + for _, rrs := range rrsets { + for _, record := range rrs.Records { + records = append(records, Record{ + Name: rrs.Name, + Type: rrs.Type, + Content: record.Content, + TTL: rrs.TTL, + }) + } + } + + return records, nil +} + +func (client *Client) getZoneInfo(zone string) (*ZoneInfo, error) { zoneInfo, err := client.GetZoneInfoFromCache(zone) if err != nil { log.Printf("[WARN] module.freecache: %s: %s", zone, err) } - if zoneInfo == nil { req, err := client.newRequest("GET", fmt.Sprintf("/servers/localhost/zones/%s", zone), nil) if err != nil { @@ -491,26 +517,12 @@ func (client *Client) ListRecords(zone string) ([]Record, error) { err = client.Cache.Set([]byte(zone), cacheValue, client.CacheTTL) if err != nil { - return nil, fmt.Errorf("The cache for REST API requests is enabled but the size isn't enough: cacheSize: %db \n %s", - DefaultCacheSize, err) + return nil, err } } } - 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 + return zoneInfo, nil } // ListRecordsInRRSet returns only records of specified name and type @@ -539,6 +551,35 @@ func (client *Client) ListRecordsByID(zone string, recID string) ([]Record, erro return client.ListRecordsInRRSet(zone, name, tpe) } +//GetResourceRecordSet by zone and id +func (client *Client) GetRRSetOfRecord(zone string, recID string) (*ResourceRecordSet, error) { + name, tpe, err := parseID(recID) + if err != nil { + return nil, err + } + + rrsets, err := client.ListRRSets(zone) + if err != nil { + return nil, err + } + + for _, rrset := range rrsets { + if rrset.Name == name && rrset.Type == tpe { + return &rrset, nil + } + } + return nil, fmt.Errorf("Error getting rrset %s. Not found.", name) +} + +//ListRRSets by zone +func (client *Client) ListRRSets(zone string) ([]ResourceRecordSet, error) { + zoneInfo, err := client.getZoneInfo(zone) + if err != nil { + return nil, err + } + return zoneInfo.ResourceRecordSets, nil +} + // RecordExists checks if requested record exists in Zone func (client *Client) RecordExists(zone string, name string, tpe string) (bool, error) { allRecords, err := client.ListRecords(zone) diff --git a/powerdns/resource_powerdns_record.go b/powerdns/resource_powerdns_record.go index d2be002c..c484c562 100644 --- a/powerdns/resource_powerdns_record.go +++ b/powerdns/resource_powerdns_record.go @@ -57,6 +57,27 @@ func resourcePDNSRecord() *schema.Resource { ForceNew: true, Description: "For A and AAAA records, if true, create corresponding PTR.", }, + + "comment": { + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "account": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + Optional: true, + ForceNew: true, + Description: "A comment about an RRSet.", + }, }, } } @@ -70,6 +91,21 @@ func resourcePDNSRecordCreate(d *schema.ResourceData, meta interface{}) error { TTL: d.Get("ttl").(int), } + comments := d.Get("comment").(*schema.Set).List() + if len(comments) > 0 { + commentObjs := make([]Comment, 0, len(comments)) + for _, comment := range comments { + commentMap := comment.(map[string]interface{}) + commentObjs = append( + commentObjs, + Comment{ + Content: commentMap["content"].(string), + Account: commentMap["account"].(string), + }) + } + rrSet.Comments = commentObjs + } + zone := d.Get("zone").(string) ttl := d.Get("ttl").(int) recs := d.Get("records").(*schema.Set).List() @@ -195,6 +231,8 @@ func resourcePDNSRecordImport(d *schema.ResourceData, meta interface{}) ([]*sche log.Printf("[INFO] importing PowerDNS Record %s in Zone: %s", recordID, zoneName) records, err := client.ListRecordsByID(zoneName, recordID) + rrset, err := client.GetRRSetOfRecord(zoneName, recordID) + if err != nil { return nil, fmt.Errorf("couldn't fetch PowerDNS Record: %s", err) } @@ -208,11 +246,20 @@ func resourcePDNSRecordImport(d *schema.ResourceData, meta interface{}) ([]*sche recs = append(recs, r.Content) } + commentsSet := make([]map[string]interface{}, len(rrset.Comments)) + for i, comment := range rrset.Comments { + commentsSet[i] = map[string]interface{}{ + "content": comment.Content, + "account": comment.Account, + } + } + d.Set("zone", zoneName) d.Set("name", records[0].Name) d.Set("ttl", records[0].TTL) d.Set("type", records[0].Type) d.Set("records", recs) + d.Set("comment", commentsSet) d.SetId(recordID) return []*schema.ResourceData{d}, nil diff --git a/powerdns/resource_powerdns_record_test.go b/powerdns/resource_powerdns_record_test.go index 4641387d..dcd099e4 100644 --- a/powerdns/resource_powerdns_record_test.go +++ b/powerdns/resource_powerdns_record_test.go @@ -394,6 +394,31 @@ func TestAccPDNSRecord_TXT(t *testing.T) { }) } +func TestAccPDNSRecord_WithComments(t *testing.T) { + resourceName := "powerdns_record.test-comments" + resourceID := `{"zone":"sysa.xyz.","id":"comment.sysa.xyz.:::A"}` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPDNSRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testPDSNRecordWithComments, + Check: resource.ComposeTestCheckFunc( + testAccCheckPDNSRecordExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportStateId: resourceID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccPDNSRecord_ALIAS(t *testing.T) { resourceName := "powerdns_record.test-alias" resourceID := `{"zone":"sysa.xyz.","id":"alias.sysa.xyz.:::ALIAS"}` @@ -654,3 +679,22 @@ resource "powerdns_record" "test-soa" { ttl = 3600 records = [ "something.something. hostmaster.sysa.xyz. 2019090301 10800 3600 604800 3600" ] }` + +const testPDSNRecordWithComments = ` +resource "powerdns_record" "test-comments" { + zone = "sysa.xyz." + name = "comment.sysa.xyz." + type = "A" + ttl = 60 + records = [ "1.1.1.1" ] + + comment { + content = "Test comment #1" + account = "Test account #1" + } + + comment { + content = "Test comment #2" + account = "Test account #2" + } +}` diff --git a/website/docs/r/record.html.markdown b/website/docs/r/record.html.markdown index aab47d04..448a5a24 100644 --- a/website/docs/r/record.html.markdown +++ b/website/docs/r/record.html.markdown @@ -42,6 +42,30 @@ resource "powerdns_record" "foobar" { } ``` +### Record with comments +An example creating a record with comments: + +```hcl +# Add a record with comments +resource "powerdns_record" "foobar" { + zone = "example.com." + name = "www.example.com." + type = "A" + ttl = 60 + records = [ "1.1.1.1" ] + + comment { + content = "Example comment #1" + account = "Example account #1" + } + + comment { + content = "Example comment #2" + account = "Example account #2" + } +} +``` + ### MX record example The following example shows, how to setup MX record with a priority of `10`. Please note that priority is not set as other `powerdns_record` properties; rather, it's part of the string that goes into `records` list. @@ -145,6 +169,9 @@ The following arguments are supported: * `ttl` - (Required) The TTL of the record. * `records` - (Required) A string list of records. * `set_ptr` - (Optional) [**_Deprecated in PowerDNS 4.3.0_**] A boolean (true/false), determining whether API server should automatically create PTR record in the matching reverse zone. Existing PTR records are replaced. If no matching reverse zone, an error is thrown. +* `comment` - (Optional) A comment about an RRSet. + * `content` - (Required) The content of the comment. + * `account` - (Required) The account of the comment. ### Attribute Reference