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 all 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
151 changes: 146 additions & 5 deletions powerdns/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,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 @@ -234,6 +240,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 @@ -269,7 +280,7 @@ func (client *Client) detectAPIVersion() (int, error) {
}

defer resp.Body.Close()
if resp.StatusCode == 200 {
if resp.StatusCode == http.StatusOK {
return 1, nil
}
return 0, nil
Expand Down Expand Up @@ -429,7 +440,7 @@ func (client *Client) DeleteZone(name string) error {
}
defer resp.Body.Close()

if resp.StatusCode != 204 {
if resp.StatusCode != http.StatusNoContent {
errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return fmt.Errorf("Error deleting zone: %s", name)
Expand Down Expand Up @@ -582,7 +593,7 @@ func (client *Client) ReplaceRecordSet(zone string, rrSet ResourceRecordSet) (st
}
defer resp.Body.Close()

if resp.StatusCode != 200 && resp.StatusCode != 204 {
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return "", fmt.Errorf("Error creating record set: %s", rrSet.ID())
Expand Down Expand Up @@ -615,7 +626,7 @@ func (client *Client) DeleteRecordSet(zone string, name string, tpe string) erro
}
defer resp.Body.Close()

if resp.StatusCode != 200 && resp.StatusCode != 204 {
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
errorResp := new(errorResponse)
if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
return fmt.Errorf("Error deleting record: %s %s", name, tpe)
Expand All @@ -634,6 +645,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 != http.StatusOK {
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 All @@ -645,7 +786,7 @@ func (client *Client) setServerVersion() error {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Invalid response code from server: '%d'. Response body: %v",
resp.StatusCode, resp.Body)
}
Expand Down
5 changes: 3 additions & 2 deletions powerdns/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,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
128 changes: 128 additions & 0 deletions powerdns/resource_powerdns_zone_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
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.TrimSpace(mt.(string))) == 0 {
log.Printf("[WARN] One or more values in 'metadata' contain empty value(s)")
break
}
}
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