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 }