github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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  const idSeparator string = ":::"
    95  
    96  func (record *Record) Id() string {
    97  	return record.Name + idSeparator + record.Type
    98  }
    99  
   100  func (rrSet *ResourceRecordSet) Id() string {
   101  	return rrSet.Name + idSeparator + rrSet.Type
   102  }
   103  
   104  // Returns name and type of record or record set based on it's ID
   105  func parseId(recId string) (string, string, error) {
   106  	s := strings.Split(recId, idSeparator)
   107  	if len(s) == 2 {
   108  		return s[0], s[1], nil
   109  	} else {
   110  		return "", "", fmt.Errorf("Unknown record ID format")
   111  	}
   112  }
   113  
   114  // Returns all Zones of server, without records
   115  func (client *Client) ListZones() ([]ZoneInfo, error) {
   116  
   117  	req, err := client.newRequest("GET", "/servers/localhost/zones", nil)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	resp, err := client.Http.Do(req)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	defer resp.Body.Close()
   127  
   128  	var zoneInfos []ZoneInfo
   129  
   130  	err = json.NewDecoder(resp.Body).Decode(&zoneInfos)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	return zoneInfos, nil
   136  }
   137  
   138  // Returns all records in Zone
   139  func (client *Client) ListRecords(zone string) ([]Record, error) {
   140  	req, err := client.newRequest("GET", fmt.Sprintf("/servers/localhost/zones/%s", zone), nil)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	resp, err := client.Http.Do(req)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	defer resp.Body.Close()
   150  
   151  	zoneInfo := new(ZoneInfo)
   152  	err = json.NewDecoder(resp.Body).Decode(zoneInfo)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	return zoneInfo.Records, nil
   158  }
   159  
   160  // Returns only records of specified name and type
   161  func (client *Client) ListRecordsInRRSet(zone string, name string, tpe string) ([]Record, error) {
   162  	allRecords, err := client.ListRecords(zone)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	records := make([]Record, 0, 10)
   168  	for _, r := range allRecords {
   169  		if r.Name == name && r.Type == tpe {
   170  			records = append(records, r)
   171  		}
   172  	}
   173  
   174  	return records, nil
   175  }
   176  
   177  func (client *Client) ListRecordsByID(zone string, recId string) ([]Record, error) {
   178  	name, tpe, err := parseId(recId)
   179  	if err != nil {
   180  		return nil, err
   181  	} else {
   182  		return client.ListRecordsInRRSet(zone, name, tpe)
   183  	}
   184  }
   185  
   186  // Checks if requested record exists in Zone
   187  func (client *Client) RecordExists(zone string, name string, tpe string) (bool, error) {
   188  	allRecords, err := client.ListRecords(zone)
   189  	if err != nil {
   190  		return false, err
   191  	}
   192  
   193  	for _, record := range allRecords {
   194  		if record.Name == name && record.Type == tpe {
   195  			return true, nil
   196  		}
   197  	}
   198  	return false, nil
   199  }
   200  
   201  // Checks if requested record exists in Zone by it's ID
   202  func (client *Client) RecordExistsByID(zone string, recId string) (bool, error) {
   203  	name, tpe, err := parseId(recId)
   204  	if err != nil {
   205  		return false, err
   206  	} else {
   207  		return client.RecordExists(zone, name, tpe)
   208  	}
   209  }
   210  
   211  // Creates new record with single content entry
   212  func (client *Client) CreateRecord(zone string, record Record) (string, error) {
   213  	reqBody, _ := json.Marshal(zonePatchRequest{
   214  		RecordSets: []ResourceRecordSet{
   215  			{
   216  				Name:       record.Name,
   217  				Type:       record.Type,
   218  				ChangeType: "REPLACE",
   219  				Records:    []Record{record},
   220  			},
   221  		},
   222  	})
   223  
   224  	req, err := client.newRequest("PATCH", fmt.Sprintf("/servers/localhost/zones/%s", zone), reqBody)
   225  	if err != nil {
   226  		return "", err
   227  	}
   228  
   229  	resp, err := client.Http.Do(req)
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  	defer resp.Body.Close()
   234  
   235  	if resp.StatusCode != 200 {
   236  		errorResp := new(errorResponse)
   237  		if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
   238  			return "", fmt.Errorf("Error creating record: %s", record.Id())
   239  		} else {
   240  			return "", fmt.Errorf("Error creating record: %s, reason: %q", record.Id(), errorResp.ErrorMsg)
   241  		}
   242  	} else {
   243  		return record.Id(), nil
   244  	}
   245  }
   246  
   247  // Creates new record set in Zone
   248  func (client *Client) ReplaceRecordSet(zone string, rrSet ResourceRecordSet) (string, error) {
   249  	rrSet.ChangeType = "REPLACE"
   250  
   251  	reqBody, _ := json.Marshal(zonePatchRequest{
   252  		RecordSets: []ResourceRecordSet{rrSet},
   253  	})
   254  
   255  	req, err := client.newRequest("PATCH", fmt.Sprintf("/servers/localhost/zones/%s", zone), reqBody)
   256  	if err != nil {
   257  		return "", err
   258  	}
   259  
   260  	resp, err := client.Http.Do(req)
   261  	if err != nil {
   262  		return "", err
   263  	}
   264  	defer resp.Body.Close()
   265  
   266  	if resp.StatusCode != 200 {
   267  		errorResp := new(errorResponse)
   268  		if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
   269  			return "", fmt.Errorf("Error creating record set: %s", rrSet.Id())
   270  		} else {
   271  			return "", fmt.Errorf("Error creating record set: %s, reason: %q", rrSet.Id(), errorResp.ErrorMsg)
   272  		}
   273  	} else {
   274  		return rrSet.Id(), nil
   275  	}
   276  }
   277  
   278  // Deletes record set from Zone
   279  func (client *Client) DeleteRecordSet(zone string, name string, tpe string) error {
   280  	reqBody, _ := json.Marshal(zonePatchRequest{
   281  		RecordSets: []ResourceRecordSet{
   282  			{
   283  				Name:       name,
   284  				Type:       tpe,
   285  				ChangeType: "DELETE",
   286  			},
   287  		},
   288  	})
   289  
   290  	req, err := client.newRequest("PATCH", fmt.Sprintf("/servers/localhost/zones/%s", zone), reqBody)
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	resp, err := client.Http.Do(req)
   296  	if err != nil {
   297  		return err
   298  	}
   299  	defer resp.Body.Close()
   300  
   301  	if resp.StatusCode != 200 {
   302  		errorResp := new(errorResponse)
   303  		if err = json.NewDecoder(resp.Body).Decode(errorResp); err != nil {
   304  			return fmt.Errorf("Error deleting record: %s %s", name, tpe)
   305  		} else {
   306  			return fmt.Errorf("Error deleting record: %s %s, reason: %q", name, tpe, errorResp.ErrorMsg)
   307  		}
   308  	} else {
   309  		return nil
   310  	}
   311  }
   312  
   313  // Deletes record from Zone by it's ID
   314  func (client *Client) DeleteRecordSetByID(zone string, recId string) error {
   315  	name, tpe, err := parseId(recId)
   316  	if err != nil {
   317  		return err
   318  	} else {
   319  		return client.DeleteRecordSet(zone, name, tpe)
   320  	}
   321  }