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 }