github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/builtin/providers/powerdns/client.go (about)

     1  package powerdns
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/go-cleanhttp"
    13  )
    14  
    15  type Client struct {
    16  	// Location of PowerDNS server to use
    17  	ServerUrl string
    18  	//	REST API Static authentication key
    19  	ApiKey string
    20  	Http   *http.Client
    21  }
    22  
    23  // NewClient returns a new PowerDNS client
    24  func NewClient(serverUrl string, apiKey string) (*Client, error) {
    25  	client := Client{
    26  		ServerUrl: serverUrl,
    27  		ApiKey:    apiKey,
    28  		Http:      cleanhttp.DefaultClient(),
    29  	}
    30  	return &client, nil
    31  }
    32  
    33  // Creates a new request with necessary headers
    34  func (c *Client) newRequest(method string, endpoint string, body []byte) (*http.Request, error) {
    35  
    36  	url, err := url.Parse(c.ServerUrl + endpoint)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("Error during parting request URL: %s", err)
    39  	}
    40  
    41  	var bodyReader io.Reader
    42  	if body != nil {
    43  		bodyReader = bytes.NewReader(body)
    44  	}
    45  
    46  	req, err := http.NewRequest(method, url.String(), bodyReader)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("Error during creation of request: %s", err)
    49  	}
    50  
    51  	req.Header.Add("X-API-Key", c.ApiKey)
    52  	req.Header.Add("Accept", "application/json")
    53  
    54  	if method != "GET" {
    55  		req.Header.Add("Content-Type", "application/json")
    56  	}
    57  
    58  	return req, nil
    59  }
    60  
    61  type ZoneInfo struct {
    62  	Id      string   `json:"id"`
    63  	Name    string   `json:"name"`
    64  	URL     string   `json:"url"`
    65  	Kind    string   `json:"kind"`
    66  	DnsSec  bool     `json:"dnsssec"`
    67  	Serial  int64    `json:"serial"`
    68  	Records []Record `json:"records,omitempty"`
    69  }
    70  
    71  type Record struct {
    72  	Name     string `json:"name"`
    73  	Type     string `json:"type"`
    74  	Content  string `json:"content"`
    75  	TTL      int    `json:"ttl"`
    76  	Disabled bool   `json:"disabled"`
    77  }
    78  
    79  type ResourceRecordSet struct {
    80  	Name       string   `json:"name"`
    81  	Type       string   `json:"type"`
    82  	ChangeType string   `json:"changetype"`
    83  	Records    []Record `json:"records,omitempty"`
    84  }
    85  
    86  type zonePatchRequest struct {
    87  	RecordSets []ResourceRecordSet `json:"rrsets"`
    88  }
    89  
    90  type errorResponse struct {
    91  	ErrorMsg string `json:"error"`
    92  }
    93  
    94  func (record *Record) Id() string {
    95  	return fmt.Sprintf("%s-%s", record.Name, record.Type)
    96  }
    97  
    98  func (rrSet *ResourceRecordSet) Id() string {
    99  	return fmt.Sprintf("%s-%s", rrSet.Name, rrSet.Type)
   100  }
   101  
   102  // Returns name and type of record or record set based on it's ID
   103  func parseId(recId string) (string, string, error) {
   104  	s := strings.Split(recId, "-")
   105  	if len(s) == 2 {
   106  		return s[0], s[1], nil
   107  	} else {
   108  		return "", "", fmt.Errorf("Unknown record ID format")
   109  	}
   110  }
   111  
   112  // Returns all Zones of server, without records
   113  func (client *Client) ListZones() ([]ZoneInfo, error) {
   114  
   115  	req, err := client.newRequest("GET", "/servers/localhost/zones", nil)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	resp, err := client.Http.Do(req)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	defer resp.Body.Close()
   125  
   126  	var zoneInfos []ZoneInfo
   127  
   128  	err = json.NewDecoder(resp.Body).Decode(&zoneInfos)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	return zoneInfos, nil
   134  }
   135  
   136  // Returns all records in Zone
   137  func (client *Client) ListRecords(zone string) ([]Record, error) {
   138  	req, err := client.newRequest("GET", fmt.Sprintf("/servers/localhost/zones/%s", zone), nil)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	resp, err := client.Http.Do(req)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	defer resp.Body.Close()
   148  
   149  	zoneInfo := new(ZoneInfo)
   150  	err = json.NewDecoder(resp.Body).Decode(zoneInfo)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return zoneInfo.Records, nil
   156  }
   157  
   158  // Returns only records of specified name and type
   159  func (client *Client) ListRecordsInRRSet(zone string, name string, tpe string) ([]Record, error) {
   160  	allRecords, err := client.ListRecords(zone)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	records := make([]Record, 0, 10)
   166  	for _, r := range allRecords {
   167  		if r.Name == name && r.Type == tpe {
   168  			records = append(records, r)
   169  		}
   170  	}
   171  
   172  	return records, nil
   173  }
   174  
   175  func (client *Client) ListRecordsByID(zone string, recId string) ([]Record, error) {
   176  	name, tpe, err := parseId(recId)
   177  	if err != nil {
   178  		return nil, err
   179  	} else {
   180  		return client.ListRecordsInRRSet(zone, name, tpe)
   181  	}
   182  }
   183  
   184  // Checks if requested record exists in Zone
   185  func (client *Client) RecordExists(zone string, name string, tpe string) (bool, error) {
   186  	allRecords, err := client.ListRecords(zone)
   187  	if err != nil {
   188  		return false, err
   189  	}
   190  
   191  	for _, record := range allRecords {
   192  		if record.Name == name && record.Type == tpe {
   193  			return true, nil
   194  		}
   195  	}
   196  	return false, nil
   197  }
   198  
   199  // Checks if requested record exists in Zone by it's ID
   200  func (client *Client) RecordExistsByID(zone string, recId string) (bool, error) {
   201  	name, tpe, err := parseId(recId)
   202  	if err != nil {
   203  		return false, err
   204  	} else {
   205  		return client.RecordExists(zone, name, tpe)
   206  	}
   207  }
   208  
   209  // Creates new record with single content entry
   210  func (client *Client) CreateRecord(zone string, record Record) (string, error) {
   211  	reqBody, _ := json.Marshal(zonePatchRequest{
   212  		RecordSets: []ResourceRecordSet{
   213  			{
   214  				Name:       record.Name,
   215  				Type:       record.Type,
   216  				ChangeType: "REPLACE",
   217  				Records:    []Record{record},
   218  			},
   219  		},
   220  	})
   221  
   222  	req, err := client.newRequest("PATCH", fmt.Sprintf("/servers/localhost/zones/%s", zone), reqBody)
   223  	if err != nil {
   224  		return "", err
   225  	}
   226  
   227  	resp, err := client.Http.Do(req)
   228  	if err != nil {
   229  		return "", err
   230  	}
   231  	defer resp.Body.Close()
   232  
   233  	if resp.StatusCode != 200 {
   234  		errorResp := new(errorResponse)
   235  		if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
   236  			return "", fmt.Errorf("Error creating record: %s", record.Id())
   237  		} else {
   238  			return "", fmt.Errorf("Error creating record: %s, reason: %q", record.Id(), errorResp.ErrorMsg)
   239  		}
   240  	} else {
   241  		return record.Id(), nil
   242  	}
   243  }
   244  
   245  // Creates new record set in Zone
   246  func (client *Client) ReplaceRecordSet(zone string, rrSet ResourceRecordSet) (string, error) {
   247  	rrSet.ChangeType = "REPLACE"
   248  
   249  	reqBody, _ := json.Marshal(zonePatchRequest{
   250  		RecordSets: []ResourceRecordSet{rrSet},
   251  	})
   252  
   253  	req, err := client.newRequest("PATCH", fmt.Sprintf("/servers/localhost/zones/%s", zone), reqBody)
   254  	if err != nil {
   255  		return "", err
   256  	}
   257  
   258  	resp, err := client.Http.Do(req)
   259  	if err != nil {
   260  		return "", err
   261  	}
   262  	defer resp.Body.Close()
   263  
   264  	if resp.StatusCode != 200 {
   265  		errorResp := new(errorResponse)
   266  		if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
   267  			return "", fmt.Errorf("Error creating record set: %s", rrSet.Id())
   268  		} else {
   269  			return "", fmt.Errorf("Error creating record set: %s, reason: %q", rrSet.Id(), errorResp.ErrorMsg)
   270  		}
   271  	} else {
   272  		return rrSet.Id(), nil
   273  	}
   274  }
   275  
   276  // Deletes record set from Zone
   277  func (client *Client) DeleteRecordSet(zone string, name string, tpe string) error {
   278  	reqBody, _ := json.Marshal(zonePatchRequest{
   279  		RecordSets: []ResourceRecordSet{
   280  			{
   281  				Name:       name,
   282  				Type:       tpe,
   283  				ChangeType: "DELETE",
   284  			},
   285  		},
   286  	})
   287  
   288  	req, err := client.newRequest("PATCH", fmt.Sprintf("/servers/localhost/zones/%s", zone), reqBody)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	resp, err := client.Http.Do(req)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	defer resp.Body.Close()
   298  
   299  	if resp.StatusCode != 200 {
   300  		errorResp := new(errorResponse)
   301  		if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
   302  			return fmt.Errorf("Error deleting record: %s %s", name, tpe)
   303  		} else {
   304  			return fmt.Errorf("Error deleting record: %s %s, reason: %q", name, tpe, errorResp.ErrorMsg)
   305  		}
   306  	} else {
   307  		return nil
   308  	}
   309  }
   310  
   311  // Deletes record from Zone by it's ID
   312  func (client *Client) DeleteRecordSetByID(zone string, recId string) error {
   313  	name, tpe, err := parseId(recId)
   314  	if err != nil {
   315  		return err
   316  	} else {
   317  		return client.DeleteRecordSet(zone, name, tpe)
   318  	}
   319  }