Skip to content

Add zone metadata resource #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
terraform-provider-dns
terraform-provider-powerdns

*.dll
*.exe
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3"
version: "3.7"
services:
nginx:
image: nginx:1.17.2
Expand Down
143 changes: 142 additions & 1 deletion powerdns/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ type ResourceRecordSet struct {
Records []Record `json:"records,omitempty"`
}

// ResourceZoneMetadata represents a PowerDNS Zone Metadata object
type ResourceZoneMetadata struct {
Kind string `json:"kind"`
Metadata []string `json:"metadata"`
}

type zonePatchRequest struct {
RecordSets []ResourceRecordSet `json:"rrsets"`
}
Expand Down Expand Up @@ -215,6 +221,11 @@ func (rrSet *ResourceRecordSet) ID() string {
return rrSet.Name + idSeparator + rrSet.Type
}

// ID returns a zoneMetadata with the ID format
func (metadata *ResourceZoneMetadata) ID(zone string) string {
return zone + idSeparator + metadata.Kind
}

// Returns name and type of record or record set based on its ID
func parseID(recID string) (string, string, error) {
s := strings.Split(recID, idSeparator)
Expand Down Expand Up @@ -292,7 +303,7 @@ func (client *Client) GetZone(name string) (ZoneInfo, error) {
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
if resp.StatusCode != 200 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would suggest to change it back here. Also at some other places(I will point). Let keep the same style. http.StatusOK looks more clear at least for me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I got it, It was unclear for me because both styles are exists in the code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I am about to refactor the project a bit in near feature. 🙂

errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return ZoneInfo{}, fmt.Errorf("Error getting zone: %s", name)
Expand Down Expand Up @@ -576,6 +587,136 @@ func (client *Client) DeleteRecordSetByID(zone string, recID string) error {
return client.DeleteRecordSet(zone, name, tpe)
}

// GetZoneMetadata get metadata from zone by its ID
func (client *Client) GetZoneMetadata(id string) (ResourceZoneMetadata, error) {
zone, kind, err := parseID(id)
if err != nil {
return ResourceZoneMetadata{}, err
}

req, err := client.newRequest("GET", fmt.Sprintf("/servers/localhost/zones/%s/metadata/%s", zone, kind), nil)
if err != nil {
return ResourceZoneMetadata{}, err
}

resp, err := client.HTTP.Do(req)
if err != nil {
return ResourceZoneMetadata{}, err
}
defer resp.Body.Close()

var zoneMetadata ResourceZoneMetadata
err = json.NewDecoder(resp.Body).Decode(&zoneMetadata)
if err != nil {
return ResourceZoneMetadata{}, err
}

return zoneMetadata, nil
}

// UpdateZoneMetadata creates new record set in Zone
func (client *Client) UpdateZoneMetadata(zone string, zoneMetadata ResourceZoneMetadata) (string, error) {
body, err := json.Marshal(zoneMetadata)
if err != nil {
return "", err
}

req, err := client.newRequest("PUT", fmt.Sprintf("/servers/localhost/zones/%s/metadata/%s", zone, zoneMetadata.Kind), body)
if err != nil {
return "", err
}

resp, err := client.HTTP.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return "", fmt.Errorf("Error updating zone metadata: %s", zoneMetadata.Kind)
}
return "", fmt.Errorf("Error updating zone metadata: %s, reason: %q", zoneMetadata.Kind, errorResp.ErrorMsg)
}

var createdZoneMetadata ResourceZoneMetadata
err = json.NewDecoder(resp.Body).Decode(&createdZoneMetadata)
if err != nil {
return "", err
}

return createdZoneMetadata.ID(zone), nil
}

// DeleteZoneMetadata deletes zone metadata by its ID
func (client *Client) DeleteZoneMetadata(id string) error {
zone, kind, err := parseID(id)
if err != nil {
return err
}

req, err := client.newRequest("DELETE", fmt.Sprintf("/servers/localhost/zones/%s/metadata/%s", zone, kind), nil)
if err != nil {
return err
}

resp, err := client.HTTP.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != 200 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http.StatusOK

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return fmt.Errorf("Error deleting Zone Metadata: %s", kind)
}
return fmt.Errorf("Error deleting Zone Metadata: %s, reason: %q", kind, errorResp.ErrorMsg)
}
return nil
}

// ZoneMetadataExists checks if requested zone exists
func (client *Client) ZoneMetadataExists(id string) (bool, error) {
zone, kind, err := parseID(id)
if err != nil {
return false, err
}

req, err := client.newRequest("GET", fmt.Sprintf("/servers/localhost/zones/%s/metadata/%s", zone, kind), nil)
if err != nil {
return false, err
}

resp, err := client.HTTP.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return false, fmt.Errorf("Error getting Zone Metadata: %s", kind)
}
return false, fmt.Errorf("Error getting Zone Metadata: %s, reason: %q", kind, errorResp.ErrorMsg)
}

var ZoneMetadata ResourceZoneMetadata
err = json.NewDecoder(resp.Body).Decode(&ZoneMetadata)

if err != nil {
return false, err
}

if len(ZoneMetadata.Metadata) == 0 {
return false, err
}

return resp.StatusCode == http.StatusOK, nil
}

func (client *Client) setServerVersion() error {
req, err := client.newRequest("GET", "/servers/localhost", nil)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions powerdns/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"powerdns_zone": resourcePDNSZone(),
"powerdns_record": resourcePDNSRecord(),
"powerdns_zone": resourcePDNSZone(),
"powerdns_zone_metadata": resourcePDNSZoneMetadata(),
"powerdns_record": resourcePDNSRecord(),
},

ConfigureFunc: providerConfigure,
Expand Down
127 changes: 127 additions & 0 deletions powerdns/resource_powerdns_zone_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package powerdns

import (
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourcePDNSZoneMetadata() *schema.Resource {
return &schema.Resource{
Create: resourcePDNSZoneMetadataCreate,
Read: resourcePDNSZoneMetadataRead,
Delete: resourcePDNSZoneMetadataDelete,
Exists: resourcePDNSZoneMetadataExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"zone": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"kind": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"metadata": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Required: true,
ForceNew: true,
},
},
}
}

func resourcePDNSZoneMetadataCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)

zone := d.Get("zone").(string)
mtdata := d.Get("metadata").(*schema.Set).List()

for _, mt := range mtdata {
if len(strings.Trim(mt.(string), " ")) == 0 {
log.Printf("[WARN] One or more values in 'metadata' contain empty '' value(s)")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume here was something in single quotes but you deleted it but quotes are left.
Plus you don't break this loop if you find empty metadata.
In case we will have N objects in list empty - it will through the message N times.
I would suggest add a break after this log.

Copy link

@PetrusHahol PetrusHahol Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also there is a function TrimSpace in strings package. We can use it instead of regular trim :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it, thanks!

}
}
if !(len(mtdata) > 0) {
return fmt.Errorf("'metadata' must not be empty")
}

metadata := make([]string, 0, len(mtdata))
for _, mt := range mtdata {
metadata = append(metadata, mt.(string))
}

zoneMetadata := ResourceZoneMetadata{
Kind: d.Get("kind").(string),
Metadata: metadata,
}

log.Printf("[DEBUG] Creating PowerDNS Zone Metadata: %#v", zoneMetadata)

metaid, err := client.UpdateZoneMetadata(zone, zoneMetadata)
if err != nil {
return fmt.Errorf("Failed to create PowerDNS Zone Metadata: %s", err)
}

d.SetId(metaid)
log.Printf("[INFO] Created PowerDNS Zone Metadata with ID: %s", d.Id())

return nil
}

func resourcePDNSZoneMetadataRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)

log.Printf("[DEBUG] Reading PowerDNS Zone Metadata: %s", d.Id())
record, err := client.GetZoneMetadata(d.Id())
if err != nil {
return fmt.Errorf("Couldn't fetch PowerDNS Zone Metadata: %s", err)
}

zone, _, err := parseID(d.Id())
if err != nil {
return err
}

d.SetId(d.Id())
d.Set("kind", record.Kind)
d.Set("metadata", record.Metadata)
d.Set("zone", zone)

return nil
}

func resourcePDNSZoneMetadataDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)

log.Printf("[INFO] Deleting PowerDNS Zone Metadata: %s", d.Id())
err := client.DeleteZoneMetadata(d.Id())

if err != nil {
return fmt.Errorf("Error deleting PowerDNS Zone Metadata: %s", err)
}

return nil
}

func resourcePDNSZoneMetadataExists(d *schema.ResourceData, meta interface{}) (bool, error) {
log.Printf("[INFO] Checking existence of PowerDNS Zone Metadata: %s", d.Id())

client := meta.(*Client)
exists, err := client.ZoneMetadataExists(d.Id())

if err != nil {
return false, fmt.Errorf("Error checking PowerDNS Zone Metadata: %s", err)
}
return exists, nil
}
Loading