From 320b2dfc6d71470983ee9e71019b8aa0a0cb1069 Mon Sep 17 00:00:00 2001 From: jgreat Date: Fri, 10 Jun 2016 23:08:56 -0500 Subject: [PATCH] Add deps for powerdns --- Godeps/Godeps.json | 30 +- .../src/github.com/dghubble/sling/.travis.yml | 13 + .../src/github.com/dghubble/sling/CHANGES.md | 52 +++ .../src/github.com/dghubble/sling/LICENSE | 21 + .../src/github.com/dghubble/sling/README.md | 272 ++++++++++++ .../src/github.com/dghubble/sling/doc.go | 179 ++++++++ .../src/github.com/dghubble/sling/sling.go | 390 ++++++++++++++++++ .../github.com/google/go-querystring/LICENSE | 27 ++ .../google/go-querystring/query/encode.go | 320 ++++++++++++++ .../src/github.com/jgreat/powerdns/.gitignore | 24 ++ .../src/github.com/jgreat/powerdns/LICENSE | 202 +++++++++ .../src/github.com/jgreat/powerdns/README.md | 6 + .../github.com/jgreat/powerdns/powerdns.go | 273 ++++++++++++ 13 files changed, 1808 insertions(+), 1 deletion(-) create mode 100644 Godeps/_workspace/src/github.com/dghubble/sling/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/dghubble/sling/CHANGES.md create mode 100644 Godeps/_workspace/src/github.com/dghubble/sling/LICENSE create mode 100644 Godeps/_workspace/src/github.com/dghubble/sling/README.md create mode 100644 Godeps/_workspace/src/github.com/dghubble/sling/doc.go create mode 100644 Godeps/_workspace/src/github.com/dghubble/sling/sling.go create mode 100644 Godeps/_workspace/src/github.com/google/go-querystring/LICENSE create mode 100644 Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go create mode 100644 Godeps/_workspace/src/github.com/jgreat/powerdns/.gitignore create mode 100644 Godeps/_workspace/src/github.com/jgreat/powerdns/LICENSE create mode 100644 Godeps/_workspace/src/github.com/jgreat/powerdns/README.md create mode 100644 Godeps/_workspace/src/github.com/jgreat/powerdns/powerdns.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index c40c28f..c899572 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,10 @@ { "ImportPath": "github.com/rancher/external-dns", "GoVersion": "go1.5.1", - "GodepVersion": "v62", + "GodepVersion": "v74", + "Packages": [ + "./..." + ], "Deps": [ { "ImportPath": "github.com/Sirupsen/logrus", @@ -127,11 +130,20 @@ "ImportPath": "github.com/crackcomm/cloudflare", "Rev": "a60ec0b4c5e4c44b522040a54293f7f93cf5b887" }, + { + "ImportPath": "github.com/dghubble/sling", + "Comment": "v1.0-14-g5765fe1", + "Rev": "5765fe1bdc176ae984b074c8a89980d1f16e6dd8" + }, { "ImportPath": "github.com/go-ini/ini", "Comment": "v1.11.0", "Rev": "12f418cc7edc5a618a51407b7ac1f1f512139df3" }, + { + "ImportPath": "github.com/google/go-querystring/query", + "Rev": "9235644dd9e52eeae6fa48efd539fdc351a0af53" + }, { "ImportPath": "github.com/gorilla/context", "Rev": "1c83b3eabd45b6d76072b66b746c20815fb2872d" @@ -144,6 +156,10 @@ "ImportPath": "github.com/gorilla/websocket", "Rev": "5ed2f4547d7b9c036541b27a67ecf54e41756997" }, + { + "ImportPath": "github.com/jgreat/powerdns", + "Rev": "2682135eb5431af55792e331ce6d3cb43cda00a4" + }, { "ImportPath": "github.com/jmespath/go-jmespath", "Comment": "0.2.2-12-g0b12d6b", @@ -165,6 +181,18 @@ "ImportPath": "github.com/prasmussen/gandi-api/domain", "Rev": "47911529ecd365be607afe09a4fe0799e16c3ed7" }, + { + "ImportPath": "github.com/prasmussen/gandi-api/domain/zone", + "Rev": "47911529ecd365be607afe09a4fe0799e16c3ed7" + }, + { + "ImportPath": "github.com/prasmussen/gandi-api/domain/zone/record", + "Rev": "47911529ecd365be607afe09a4fe0799e16c3ed7" + }, + { + "ImportPath": "github.com/prasmussen/gandi-api/domain/zone/version", + "Rev": "47911529ecd365be607afe09a4fe0799e16c3ed7" + }, { "ImportPath": "github.com/prasmussen/gandi-api/operation", "Rev": "47911529ecd365be607afe09a4fe0799e16c3ed7" diff --git a/Godeps/_workspace/src/github.com/dghubble/sling/.travis.yml b/Godeps/_workspace/src/github.com/dghubble/sling/.travis.yml new file mode 100644 index 0000000..e2d7cd0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dghubble/sling/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - 1.5.3 + - 1.6 + - tip +before_install: + - go get github.com/golang/lint/golint +install: + - go get -v . +script: + - go test -v . + - go vet ./... + - golint ./... \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/dghubble/sling/CHANGES.md b/Godeps/_workspace/src/github.com/dghubble/sling/CHANGES.md new file mode 100644 index 0000000..d93cfd5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dghubble/sling/CHANGES.md @@ -0,0 +1,52 @@ +# Sling Changelog + +Notable changes between releases. + +## latest + +* Added Sling `Body` setter to set an `io.Reader` on the Request + +## v1.0.0 (2015-05-23) + +* Added support for receiving and decoding error JSON structs +* Renamed Sling `JsonBody` setter to `BodyJSON` (breaking) +* Renamed Sling `BodyStruct` setter to `BodyForm` (breaking) +* Renamed Sling fields `httpClient`, `method`, `rawURL`, and `header` to be internal (breaking) +* Changed `Do` and `Receive` to skip response JSON decoding if "application/json" Content-Type is missing +* Changed `Sling.Receive(v interface{})` to `Sling.Receive(successV, failureV interface{})` (breaking) + * Previously `Receive` attempted to decode the response Body in all cases + * Updated `Receive` will decode the response Body into successV for 2XX responses or decode the Body into failureV for other status codes. Pass a nil `successV` or `failureV` to skip JSON decoding into that value. + * To upgrade, pass nil for the `failureV` argument or consider defining a JSON tagged struct appropriate for the API endpoint. (e.g. `s.Receive(&issue, nil)`, `s.Receive(&issue, &githubError)`) + * To retain the old behavior, duplicate the first argument (e.g. s.Receive(&tweet, &tweet)) +* Changed `Sling.Do(http.Request, v interface{})` to `Sling.Do(http.Request, successV, failureV interface{})` (breaking) + * See the changelog entry about `Receive`, the upgrade path is the same. +* Removed HEAD, GET, POST, PUT, PATCH, DELETE constants, no reason to export them (breaking) + +## v0.4.0 (2015-04-26) + +* Improved golint compliance +* Fixed typos and test printouts + +## v0.3.0 (2015-04-21) + +* Added BodyStruct method for setting a url encoded form body on the Request +* Added Add and Set methods for adding or setting Request Headers +* Added JsonBody method for setting JSON Request Body +* Improved examples and documentation + +## v0.2.0 (2015-04-05) + +* Added http.Client setter +* Added Sling.New() method to return a copy of a Sling +* Added Base setter and Path extension support +* Added method setters (Get, Post, Put, Patch, Delete, Head) +* Added support for encoding URL Query parameters +* Added example tiny Github API +* Changed v0.1.0 method signatures and names (breaking) +* Removed Go 1.0 support + +## v0.1.0 (2015-04-01) + +* Support decoding JSON responses. + + diff --git a/Godeps/_workspace/src/github.com/dghubble/sling/LICENSE b/Godeps/_workspace/src/github.com/dghubble/sling/LICENSE new file mode 100644 index 0000000..2718840 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dghubble/sling/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Dalton Hubble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/dghubble/sling/README.md b/Godeps/_workspace/src/github.com/dghubble/sling/README.md new file mode 100644 index 0000000..f3d781a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dghubble/sling/README.md @@ -0,0 +1,272 @@ + +# Sling [![Build Status](https://travis-ci.org/dghubble/sling.png?branch=master)](https://travis-ci.org/dghubble/sling) [![Coverage](https://gocover.io/_badge/github.com/dghubble/sling)](https://gocover.io/github.com/dghubble/sling) [![GoDoc](https://godoc.org/github.com/dghubble/sling?status.png)](https://godoc.org/github.com/dghubble/sling) + + +Sling is a Go HTTP client library for creating and sending API requests. + +Slings store HTTP Request properties to simplify sending requests and decoding responses. Check [usage](#usage) or the [examples](examples) to learn how to compose a Sling into your API client. + +### Features + +* Method Setters: Get/Post/Put/Patch/Delete/Head +* Add or Set Request Headers +* Base/Path: Extend a Sling for different endpoints +* Encode structs into URL query parameters +* Encode a form or JSON into the Request Body +* Receive JSON success or failure responses + +## Install + + go get github.com/dghubble/sling + +## Documentation + +Read [GoDoc](https://godoc.org/github.com/dghubble/sling) + +## Usage + +Use a Sling to set path, method, header, query, or body properties and create an `http.Request`. + +```go +type Params struct { + Count int `url:"count,omitempty"` +} +params := &Params{Count: 5} + +req, err := sling.New().Get("https://example.com").QueryStruct(params).Request() +client.Do(req) +``` + +### Path + +Use `Path` to set or extend the URL for created Requests. Extension means the path will be resolved relative to the existing URL. + +```go +// creates a GET request to https://example.com/foo/bar +req, err := sling.New().Base("https://example.com/").Path("foo/").Path("bar").Request() +``` + +Use `Get`, `Post`, `Put`, `Patch`, `Delete`, or `Head` which are exactly the same as `Path` except they set the HTTP method too. + +```go +req, err := sling.New().Post("http://upload.com/gophers") +``` + +### Headers + +`Add` or `Set` headers for requests created by a Sling. + +```go +s := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client") +req, err := s.New().Get("gophergram/list").Request() +``` + +### Query + +#### QueryStruct + +Define [url tagged structs](https://godoc.org/github.com/google/go-querystring/query). Use `QueryStruct` to encode a struct as query parameters on requests. + +```go +// Github Issue Parameters +type IssueParams struct { + Filter string `url:"filter,omitempty"` + State string `url:"state,omitempty"` + Labels string `url:"labels,omitempty"` + Sort string `url:"sort,omitempty"` + Direction string `url:"direction,omitempty"` + Since string `url:"since,omitempty"` +} +``` + +```go +githubBase := sling.New().Base("https://api.github.com/").Client(httpClient) + +path := fmt.Sprintf("repos/%s/%s/issues", owner, repo) +params := &IssueParams{Sort: "updated", State: "open"} +req, err := githubBase.New().Get(path).QueryStruct(params).Request() +``` + +### Body + +#### JSON Body + +Define [JSON tagged structs](https://golang.org/pkg/encoding/json/). Use `BodyJSON` to JSON encode a struct as the Body on requests. + +```go +type IssueRequest struct { + Title string `json:"title,omitempty"` + Body string `json:"body,omitempty"` + Assignee string `json:"assignee,omitempty"` + Milestone int `json:"milestone,omitempty"` + Labels []string `json:"labels,omitempty"` +} +``` + +```go +githubBase := sling.New().Base("https://api.github.com/").Client(httpClient) +path := fmt.Sprintf("repos/%s/%s/issues", owner, repo) + +body := &IssueRequest{ + Title: "Test title", + Body: "Some issue", +} +req, err := githubBase.New().Post(path).BodyJSON(body).Request() +``` + +Requests will include an `application/json` Content-Type header. + +#### Form Body + +Define [url tagged structs](https://godoc.org/github.com/google/go-querystring/query). Use `BodyForm` to form url encode a struct as the Body on requests. + +```go +type StatusUpdateParams struct { + Status string `url:"status,omitempty"` + InReplyToStatusId int64 `url:"in_reply_to_status_id,omitempty"` + MediaIds []int64 `url:"media_ids,omitempty,comma"` +} +``` + +```go +tweetParams := &StatusUpdateParams{Status: "writing some Go"} +req, err := twitterBase.New().Post(path).BodyForm(tweetParams).Request() +``` + +Requests will include an `application/x-www-form-urlencoded` Content-Type header. + +#### Plain Body + +Use `Body` to set a plain `io.Reader` on requests created by a Sling. + +```go +body := strings.NewReader("raw body") +req, err := sling.New().Base("https://example.com").Body(body).Request() +``` + +Set a content type header, if desired (e.g. `Set("Content-Type", "text/plain")`). + +### Extend a Sling + +Each Sling creates a standard `http.Request` (e.g. with some path and query +params) each time `Request()` is called. You may wish to extend an existing Sling to minimize duplication (e.g. a common client or base url). + +Each Sling instance provides a `New()` method which creates an independent copy, so setting properties on the child won't mutate the parent Sling. + +```go +const twitterApi = "https://api.twitter.com/1.1/" +base := sling.New().Base(twitterApi).Client(authClient) + +// statuses/show.json Sling +tweetShowSling := base.New().Get("statuses/show.json").QueryStruct(params) +req, err := tweetShowSling.Request() + +// statuses/update.json Sling +tweetPostSling := base.New().Post("statuses/update.json").BodyForm(params) +req, err := tweetPostSling.Request() +``` + +Without the calls to `base.New()`, `tweetShowSling` and `tweetPostSling` would reference the base Sling and POST to +"https://api.twitter.com/1.1/statuses/show.json/statuses/update.json", which +is undesired. + +Recap: If you wish to *extend* a Sling, create a new child copy with `New()`. + +### Sending + +#### Receive + +Define a JSON struct to decode a type from 2XX success responses. Use `ReceiveSuccess(successV interface{})` to send a new Request and decode the response body into `successV` if it succeeds. + +```go +// Github Issue (abbreviated) +type Issue struct { + Title string `json:"title"` + Body string `json:"body"` +} +``` + +```go +issues := new([]Issue) +resp, err := githubBase.New().Get(path).QueryStruct(params).ReceiveSuccess(issues) +fmt.Println(issues, resp, err) +``` + +Most APIs return failure responses with JSON error details. To decode these, define success and failure JSON structs. Use `Receive(successV, failureV interface{})` to send a new Request that will automatically decode the response into the `successV` for 2XX responses or into `failureV` for non-2XX responses. + +```go +type GithubError struct { + Message string `json:"message"` + Errors []struct { + Resource string `json:"resource"` + Field string `json:"field"` + Code string `json:"code"` + } `json:"errors"` + DocumentationURL string `json:"documentation_url"` +} +``` + +```go +issues := new([]Issue) +githubError := new(GithubError) +resp, err := githubBase.New().Get(path).QueryStruct(params).Receive(issues, githubError) +fmt.Println(issues, githubError, resp, err) +``` + +Pass a nil `successV` or `failureV` argument to skip JSON decoding into that value. + +### Build an API + +APIs typically define an endpoint (also called a service) for each type of resource. For example, here is a tiny Github IssueService which [lists](https://developer.github.com/v3/issues/#list-issues-for-a-repository) repository issues. + +```go +const baseURL = "https://api.github.com/" + +type IssueService struct { + sling *sling.Sling +} + +func NewIssueService(httpClient *http.Client) *IssueService { + return &IssueService{ + sling: sling.New().Client(httpClient).Base(baseURL), + } +} + +func (s *IssueService) ListByRepo(owner, repo string, params *IssueListParams) ([]Issue, *http.Response, error) { + issues := new([]Issue) + githubError := new(GithubError) + path := fmt.Sprintf("repos/%s/%s/issues", owner, repo) + resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(issues, githubError) + if err == nil { + err = githubError + } + return *issues, resp, err +} +``` + +## Example APIs using Sling + +* Digits [dghubble/go-digits](https://github.com/dghubble/go-digits) +* GoSquared [drinkin/go-gosquared](https://github.com/drinkin/go-gosquared) +* Kala [ajvb/kala](https://github.com/ajvb/kala) +* Parse [fergstar/go-parse](https://github.com/fergstar/go-parse) +* Rdio [apriendeau/shares](https://github.com/apriendeau/shares) +* Swagger Generator [swagger-api/swagger-codegen](https://github.com/swagger-api/swagger-codegen) +* Twitter [dghubble/go-twitter](https://github.com/dghubble/go-twitter) +* Hacker News [mirceamironenco/go-hackernews](https://github.com/mirceamironenco/go-hackernews) + +Create a Pull Request to add a link to your own API. + +## Motivation + +Many client libraries follow the lead of [google/go-github](https://github.com/google/go-github) (our inspiration!), but do so by reimplementing logic common to all clients. + +This project borrows and abstracts those ideas into a Sling, an agnostic component any API client can use for creating and sending requests. + +## Contributing + +See the [Contributing Guide](https://gist.github.com/dghubble/be682c123727f70bcfe7). + +## License + +[MIT License](LICENSE) diff --git a/Godeps/_workspace/src/github.com/dghubble/sling/doc.go b/Godeps/_workspace/src/github.com/dghubble/sling/doc.go new file mode 100644 index 0000000..dd2efb7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dghubble/sling/doc.go @@ -0,0 +1,179 @@ +/* +Package sling is a Go HTTP client library for creating and sending API requests. + +Slings store HTTP Request properties to simplify sending requests and decoding +responses. Check the examples to learn how to compose a Sling into your API +client. + +Usage + +Use a Sling to set path, method, header, query, or body properties and create an +http.Request. + + type Params struct { + Count int `url:"count,omitempty"` + } + params := &Params{Count: 5} + + req, err := sling.New().Get("https://example.com").QueryStruct(params).Request() + client.Do(req) + +Path + +Use Path to set or extend the URL for created Requests. Extension means the +path will be resolved relative to the existing URL. + + // creates a GET request to https://example.com/foo/bar + req, err := sling.New().Base("https://example.com/").Path("foo/").Path("bar").Request() + +Use Get, Post, Put, Patch, Delete, or Head which are exactly the same as Path +except they set the HTTP method too. + + req, err := sling.New().Post("http://upload.com/gophers") + +Headers + +Add or Set headers for requests created by a Sling. + + s := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client") + req, err := s.New().Get("gophergram/list").Request() + +QueryStruct + +Define url parameter structs (https://godoc.org/github.com/google/go-querystring/query). +Use QueryStruct to encode a struct as query parameters on requests. + + // Github Issue Parameters + type IssueParams struct { + Filter string `url:"filter,omitempty"` + State string `url:"state,omitempty"` + Labels string `url:"labels,omitempty"` + Sort string `url:"sort,omitempty"` + Direction string `url:"direction,omitempty"` + Since string `url:"since,omitempty"` + } + + githubBase := sling.New().Base("https://api.github.com/").Client(httpClient) + + path := fmt.Sprintf("repos/%s/%s/issues", owner, repo) + params := &IssueParams{Sort: "updated", State: "open"} + req, err := githubBase.New().Get(path).QueryStruct(params).Request() + +Json Body + +Define JSON tagged structs (https://golang.org/pkg/encoding/json/). +Use BodyJSON to JSON encode a struct as the Body on requests. + + type IssueRequest struct { + Title string `json:"title,omitempty"` + Body string `json:"body,omitempty"` + Assignee string `json:"assignee,omitempty"` + Milestone int `json:"milestone,omitempty"` + Labels []string `json:"labels,omitempty"` + } + + githubBase := sling.New().Base("https://api.github.com/").Client(httpClient) + path := fmt.Sprintf("repos/%s/%s/issues", owner, repo) + + body := &IssueRequest{ + Title: "Test title", + Body: "Some issue", + } + req, err := githubBase.New().Post(path).BodyJSON(body).Request() + +Requests will include an "application/json" Content-Type header. + +Form Body + +Define url tagged structs (https://godoc.org/github.com/google/go-querystring/query). +Use BodyForm to form url encode a struct as the Body on requests. + + type StatusUpdateParams struct { + Status string `url:"status,omitempty"` + InReplyToStatusId int64 `url:"in_reply_to_status_id,omitempty"` + MediaIds []int64 `url:"media_ids,omitempty,comma"` + } + + tweetParams := &StatusUpdateParams{Status: "writing some Go"} + req, err := twitterBase.New().Post(path).BodyForm(tweetParams).Request() + +Requests will include an "application/x-www-form-urlencoded" Content-Type +header. + +Plain Body + +Use Body to set a plain io.Reader on requests created by a Sling. + + body := strings.NewReader("raw body") + req, err := sling.New().Base("https://example.com").Body(body).Request() + +Set a content type header, if desired (e.g. Set("Content-Type", "text/plain")). + +Extend a Sling + +Each Sling generates an http.Request (say with some path and query params) +each time Request() is called, based on its state. When creating +different slings, you may wish to extend an existing Sling to minimize +duplication (e.g. a common client). + +Each Sling instance provides a New() method which creates an independent copy, +so setting properties on the child won't mutate the parent Sling. + + const twitterApi = "https://api.twitter.com/1.1/" + base := sling.New().Base(twitterApi).Client(authClient) + + // statuses/show.json Sling + tweetShowSling := base.New().Get("statuses/show.json").QueryStruct(params) + req, err := tweetShowSling.Request() + + // statuses/update.json Sling + tweetPostSling := base.New().Post("statuses/update.json").BodyForm(params) + req, err := tweetPostSling.Request() + +Without the calls to base.New(), tweetShowSling and tweetPostSling would +reference the base Sling and POST to +"https://api.twitter.com/1.1/statuses/show.json/statuses/update.json", which +is undesired. + +Recap: If you wish to extend a Sling, create a new child copy with New(). + +Receive + +Define a JSON struct to decode a type from 2XX success responses. Use +ReceiveSuccess(successV interface{}) to send a new Request and decode the +response body into successV if it succeeds. + + // Github Issue (abbreviated) + type Issue struct { + Title string `json:"title"` + Body string `json:"body"` + } + + issues := new([]Issue) + resp, err := githubBase.New().Get(path).QueryStruct(params).ReceiveSuccess(issues) + fmt.Println(issues, resp, err) + +Most APIs return failure responses with JSON error details. To decode these, +define success and failure JSON structs. Use +Receive(successV, failureV interface{}) to send a new Request that will +automatically decode the response into the successV for 2XX responses or into +failureV for non-2XX responses. + + type GithubError struct { + Message string `json:"message"` + Errors []struct { + Resource string `json:"resource"` + Field string `json:"field"` + Code string `json:"code"` + } `json:"errors"` + DocumentationURL string `json:"documentation_url"` + } + + issues := new([]Issue) + githubError := new(GithubError) + resp, err := githubBase.New().Get(path).QueryStruct(params).Receive(issues, githubError) + fmt.Println(issues, githubError, resp, err) + +Pass a nil successV or failureV argument to skip JSON decoding into that value. +*/ +package sling diff --git a/Godeps/_workspace/src/github.com/dghubble/sling/sling.go b/Godeps/_workspace/src/github.com/dghubble/sling/sling.go new file mode 100644 index 0000000..b8ad05e --- /dev/null +++ b/Godeps/_workspace/src/github.com/dghubble/sling/sling.go @@ -0,0 +1,390 @@ +package sling + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + goquery "github.com/google/go-querystring/query" +) + +const ( + contentType = "Content-Type" + jsonContentType = "application/json" + formContentType = "application/x-www-form-urlencoded" +) + +// Sling is an HTTP Request builder and sender. +type Sling struct { + // http Client for doing requests + httpClient *http.Client + // HTTP method (GET, POST, etc.) + method string + // raw url string for requests + rawURL string + // stores key-values pairs to add to request's Headers + header http.Header + // url tagged query structs + queryStructs []interface{} + // json tagged body struct + bodyJSON interface{} + // url tagged body struct (form) + bodyForm interface{} + // simply assigned body + body io.ReadCloser +} + +// New returns a new Sling with an http DefaultClient. +func New() *Sling { + return &Sling{ + httpClient: http.DefaultClient, + method: "GET", + header: make(http.Header), + queryStructs: make([]interface{}, 0), + } +} + +// New returns a copy of a Sling for creating a new Sling with properties +// from a parent Sling. For example, +// +// parentSling := sling.New().Client(client).Base("https://api.io/") +// fooSling := parentSling.New().Get("foo/") +// barSling := parentSling.New().Get("bar/") +// +// fooSling and barSling will both use the same client, but send requests to +// https://api.io/foo/ and https://api.io/bar/ respectively. +// +// Note that query and body values are copied so if pointer values are used, +// mutating the original value will mutate the value within the child Sling. +func (s *Sling) New() *Sling { + // copy Headers pairs into new Header map + headerCopy := make(http.Header) + for k, v := range s.header { + headerCopy[k] = v + } + return &Sling{ + httpClient: s.httpClient, + method: s.method, + rawURL: s.rawURL, + header: headerCopy, + queryStructs: append([]interface{}{}, s.queryStructs...), + bodyJSON: s.bodyJSON, + bodyForm: s.bodyForm, + body: s.body, + } +} + +// Http Client + +// Client sets the http Client used to do requests. If a nil client is given, +// the http.DefaultClient will be used. +func (s *Sling) Client(httpClient *http.Client) *Sling { + if httpClient == nil { + s.httpClient = http.DefaultClient + } else { + s.httpClient = httpClient + } + return s +} + +// Method + +// Head sets the Sling method to HEAD and sets the given pathURL. +func (s *Sling) Head(pathURL string) *Sling { + s.method = "HEAD" + return s.Path(pathURL) +} + +// Get sets the Sling method to GET and sets the given pathURL. +func (s *Sling) Get(pathURL string) *Sling { + s.method = "GET" + return s.Path(pathURL) +} + +// Post sets the Sling method to POST and sets the given pathURL. +func (s *Sling) Post(pathURL string) *Sling { + s.method = "POST" + return s.Path(pathURL) +} + +// Put sets the Sling method to PUT and sets the given pathURL. +func (s *Sling) Put(pathURL string) *Sling { + s.method = "PUT" + return s.Path(pathURL) +} + +// Patch sets the Sling method to PATCH and sets the given pathURL. +func (s *Sling) Patch(pathURL string) *Sling { + s.method = "PATCH" + return s.Path(pathURL) +} + +// Delete sets the Sling method to DELETE and sets the given pathURL. +func (s *Sling) Delete(pathURL string) *Sling { + s.method = "DELETE" + return s.Path(pathURL) +} + +// Header + +// Add adds the key, value pair in Headers, appending values for existing keys +// to the key's values. Header keys are canonicalized. +func (s *Sling) Add(key, value string) *Sling { + s.header.Add(key, value) + return s +} + +// Set sets the key, value pair in Headers, replacing existing values +// associated with key. Header keys are canonicalized. +func (s *Sling) Set(key, value string) *Sling { + s.header.Set(key, value) + return s +} + +// Url + +// Base sets the rawURL. If you intend to extend the url with Path, +// baseUrl should be specified with a trailing slash. +func (s *Sling) Base(rawURL string) *Sling { + s.rawURL = rawURL + return s +} + +// Path extends the rawURL with the given path by resolving the reference to +// an absolute URL. If parsing errors occur, the rawURL is left unmodified. +func (s *Sling) Path(path string) *Sling { + baseURL, baseErr := url.Parse(s.rawURL) + pathURL, pathErr := url.Parse(path) + if baseErr == nil && pathErr == nil { + s.rawURL = baseURL.ResolveReference(pathURL).String() + return s + } + return s +} + +// QueryStruct appends the queryStruct to the Sling's queryStructs. The value +// pointed to by each queryStruct will be encoded as url query parameters on +// new requests (see Request()). +// The queryStruct argument should be a pointer to a url tagged struct. See +// https://godoc.org/github.com/google/go-querystring/query for details. +func (s *Sling) QueryStruct(queryStruct interface{}) *Sling { + if queryStruct != nil { + s.queryStructs = append(s.queryStructs, queryStruct) + } + return s +} + +// Body + +// BodyJSON sets the Sling's bodyJSON. The value pointed to by the bodyJSON +// will be JSON encoded as the Body on new requests (see Request()). +// The bodyJSON argument should be a pointer to a JSON tagged struct. See +// https://golang.org/pkg/encoding/json/#MarshalIndent for details. +func (s *Sling) BodyJSON(bodyJSON interface{}) *Sling { + if bodyJSON != nil { + s.bodyJSON = bodyJSON + s.Set(contentType, jsonContentType) + } + return s +} + +// BodyForm sets the Sling's bodyForm. The value pointed to by the bodyForm +// will be url encoded as the Body on new requests (see Request()). +// The bodyStruct argument should be a pointer to a url tagged struct. See +// https://godoc.org/github.com/google/go-querystring/query for details. +func (s *Sling) BodyForm(bodyForm interface{}) *Sling { + if bodyForm != nil { + s.bodyForm = bodyForm + s.Set(contentType, formContentType) + } + return s +} + +// Body sets the Sling's body. The body value will be set as the Body on new +// requests (see Request()). +// If the provided body is also an io.Closer, the request Body will be closed +// by http.Client methods. +func (s *Sling) Body(body io.Reader) *Sling { + rc, ok := body.(io.ReadCloser) + if !ok && body != nil { + rc = ioutil.NopCloser(body) + } + if rc != nil { + s.body = rc + } + return s +} + +// Requests + +// Request returns a new http.Request created with the Sling properties. +// Returns any errors parsing the rawURL, encoding query structs, encoding +// the body, or creating the http.Request. +func (s *Sling) Request() (*http.Request, error) { + reqURL, err := url.Parse(s.rawURL) + if err != nil { + return nil, err + } + err = addQueryStructs(reqURL, s.queryStructs) + if err != nil { + return nil, err + } + body, err := s.getRequestBody() + if err != nil { + return nil, err + } + req, err := http.NewRequest(s.method, reqURL.String(), body) + if err != nil { + return nil, err + } + addHeaders(req, s.header) + return req, err +} + +// addQueryStructs parses url tagged query structs using go-querystring to +// encode them to url.Values and format them onto the url.RawQuery. Any +// query parsing or encoding errors are returned. +func addQueryStructs(reqURL *url.URL, queryStructs []interface{}) error { + urlValues, err := url.ParseQuery(reqURL.RawQuery) + if err != nil { + return err + } + // encodes query structs into a url.Values map and merges maps + for _, queryStruct := range queryStructs { + queryValues, err := goquery.Values(queryStruct) + if err != nil { + return err + } + for key, values := range queryValues { + for _, value := range values { + urlValues.Add(key, value) + } + } + } + // url.Values format to a sorted "url encoded" string, e.g. "key=val&foo=bar" + reqURL.RawQuery = urlValues.Encode() + return nil +} + +// getRequestBody returns the io.Reader which should be used as the body +// of new Requests. +func (s *Sling) getRequestBody() (body io.Reader, err error) { + if s.bodyJSON != nil && s.header.Get(contentType) == jsonContentType { + body, err = encodeBodyJSON(s.bodyJSON) + if err != nil { + return nil, err + } + } else if s.bodyForm != nil && s.header.Get(contentType) == formContentType { + body, err = encodeBodyForm(s.bodyForm) + if err != nil { + return nil, err + } + } else if s.body != nil { + body = s.body + } + return body, nil +} + +// encodeBodyJSON JSON encodes the value pointed to by bodyJSON into an +// io.Reader, typically for use as a Request Body. +func encodeBodyJSON(bodyJSON interface{}) (io.Reader, error) { + var buf = new(bytes.Buffer) + if bodyJSON != nil { + buf = &bytes.Buffer{} + err := json.NewEncoder(buf).Encode(bodyJSON) + if err != nil { + return nil, err + } + } + return buf, nil +} + +// encodeBodyForm url encodes the value pointed to by bodyForm into an +// io.Reader, typically for use as a Request Body. +func encodeBodyForm(bodyForm interface{}) (io.Reader, error) { + values, err := goquery.Values(bodyForm) + if err != nil { + return nil, err + } + return strings.NewReader(values.Encode()), nil +} + +// addHeaders adds the key, value pairs from the given http.Header to the +// request. Values for existing keys are appended to the keys values. +func addHeaders(req *http.Request, header http.Header) { + for key, values := range header { + for _, value := range values { + req.Header.Add(key, value) + } + } +} + +// Sending + +// ReceiveSuccess creates a new HTTP request and returns the response. Success +// responses (2XX) are JSON decoded into the value pointed to by successV. +// Any error creating the request, sending it, or decoding a 2XX response +// is returned. +func (s *Sling) ReceiveSuccess(successV interface{}) (*http.Response, error) { + return s.Receive(successV, nil) +} + +// Receive creates a new HTTP request and returns the response. Success +// responses (2XX) are JSON decoded into the value pointed to by successV and +// other responses are JSON decoded into the value pointed to by failureV. +// Any error creating the request, sending it, or decoding the response is +// returned. +// Receive is shorthand for calling Request and Do. +func (s *Sling) Receive(successV, failureV interface{}) (*http.Response, error) { + req, err := s.Request() + if err != nil { + return nil, err + } + return s.Do(req, successV, failureV) +} + +// Do sends an HTTP request and returns the response. Success responses (2XX) +// are JSON decoded into the value pointed to by successV and other responses +// are JSON decoded into the value pointed to by failureV. +// Any error sending the request or decoding the response is returned. +func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Response, error) { + resp, err := s.httpClient.Do(req) + if err != nil { + return resp, err + } + // when err is nil, resp contains a non-nil resp.Body which must be closed + defer resp.Body.Close() + if strings.Contains(resp.Header.Get(contentType), jsonContentType) { + err = decodeResponseJSON(resp, successV, failureV) + } + return resp, err +} + +// decodeResponse decodes response Body into the value pointed to by successV +// if the response is a success (2XX) or into the value pointed to by failureV +// otherwise. If the successV or failureV argument to decode into is nil, +// decoding is skipped. +// Caller is responsible for closing the resp.Body. +func decodeResponseJSON(resp *http.Response, successV, failureV interface{}) error { + if code := resp.StatusCode; 200 <= code && code <= 299 { + if successV != nil { + return decodeResponseBodyJSON(resp, successV) + } + } else { + if failureV != nil { + return decodeResponseBodyJSON(resp, failureV) + } + } + return nil +} + +// decodeResponseBodyJSON JSON decodes a Response Body into the value pointed +// to by v. +// Caller must provide a non-nil v and close the resp.Body. +func decodeResponseBodyJSON(resp *http.Response, v interface{}) error { + return json.NewDecoder(resp.Body).Decode(v) +} diff --git a/Godeps/_workspace/src/github.com/google/go-querystring/LICENSE b/Godeps/_workspace/src/github.com/google/go-querystring/LICENSE new file mode 100644 index 0000000..ae121a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-querystring/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 Google. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go new file mode 100644 index 0000000..19437b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go @@ -0,0 +1,320 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package query implements encoding of structs into URL query parameters. +// +// As a simple example: +// +// type Options struct { +// Query string `url:"q"` +// ShowAll bool `url:"all"` +// Page int `url:"page"` +// } +// +// opt := Options{ "foo", true, 2 } +// v, _ := query.Values(opt) +// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" +// +// The exact mapping between Go values and url.Values is described in the +// documentation for the Values() function. +package query + +import ( + "bytes" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var timeType = reflect.TypeOf(time.Time{}) + +var encoderType = reflect.TypeOf(new(Encoder)).Elem() + +// Encoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type Encoder interface { + EncodeValues(key string, v *url.Values) error +} + +// Values returns the url.Values encoding of v. +// +// Values expects to be passed a struct, and traverses it recursively using the +// following encoding rules. +// +// Each exported struct field is encoded as a URL parameter unless +// +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option +// +// The empty values are false, 0, any nil pointer or interface value, any array +// slice, map, or string of length zero, and any time.Time that returns true +// for IsZero(). +// +// The URL parameter name defaults to the struct field name but can be +// specified in the struct field's tag value. The "url" key in the struct +// field's tag value is the key name, followed by an optional comma and +// options. For example: +// +// // Field is ignored by this package. +// Field int `url:"-"` +// +// // Field appears as URL parameter "myName". +// Field int `url:"myName"` +// +// // Field appears as URL parameter "myName" and the field is omitted if +// // its value is empty +// Field int `url:"myName,omitempty"` +// +// // Field appears as URL parameter "Field" (the default), but the field +// // is skipped if empty. Note the leading comma. +// Field int `url:",omitempty"` +// +// For encoding individual field values, the following type-dependent rules +// apply: +// +// Boolean values default to encoding as the strings "true" or "false". +// Including the "int" option signals that the field should be encoded as the +// strings "1" or "0". +// +// time.Time values default to encoding as RFC3339 timestamps. Including the +// "unix" option signals that the field should be encoded as a Unix time (see +// time.Unix()) +// +// Slice and Array values default to encoding as multiple URL values of the +// same name. Including the "comma" option signals that the field should be +// encoded as a single comma-delimited value. Including the "space" option +// similarly encodes the value as a single space-delimited string. Including +// the "semicolon" option will encode the value as a semicolon-delimited string. +// Including the "brackets" option signals that the multiple URL values should +// have "[]" appended to the value name. "numbered" will append a number to +// the end of each incidence of the value name, example: +// name0=value0&name1=value1, etc. +// +// Anonymous struct fields are usually encoded as if their inner exported +// fields were fields in the outer struct, subject to the standard Go +// visibility rules. An anonymous struct field with a name given in its URL +// tag is treated as having that name, rather than being anonymous. +// +// Non-nil pointer values are encoded as the value pointed to. +// +// Nested structs are encoded including parent fields in value names for +// scoping. e.g: +// +// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" +// +// All other values are encoded using their default string representation. +// +// Multiple fields that encode to the same URL parameter name will be included +// as multiple URL values of the same name. +func Values(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + var embedded []reflect.Value + + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if name == "" { + if sf.Anonymous && sv.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, sv) + continue + } + + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(encoderType) { + if !reflect.Indirect(sv).IsValid() { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(Encoder) + if err := m.EncodeValues(name, &values); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + var del byte + if opts.Contains("comma") { + del = ',' + } else if opts.Contains("space") { + del = ' ' + } else if opts.Contains("semicolon") { + del = ';' + } else if opts.Contains("brackets") { + name = name + "[]" + } + + if del != 0 { + s := new(bytes.Buffer) + first := true + for i := 0; i < sv.Len(); i++ { + if first { + first = false + } else { + s.WriteByte(del) + } + s.WriteString(valueString(sv.Index(i), opts)) + } + values.Add(name, s.String()) + } else { + for i := 0; i < sv.Len(); i++ { + k := name + if opts.Contains("numbered") { + k = fmt.Sprintf("%s%d", name, i) + } + values.Add(k, valueString(sv.Index(i), opts)) + } + } + continue + } + + if sv.Type() == timeType { + values.Add(name, valueString(sv, opts)) + continue + } + + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Kind() == reflect.Struct { + reflectValue(values, sv, name) + continue + } + + values.Add(name, valueString(sv, opts)) + } + + for _, f := range embedded { + if err := reflectValue(values, f, scope); err != nil { + return err + } + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Kind() == reflect.Bool && opts.Contains("int") { + if v.Bool() { + return "1" + } + return "0" + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if opts.Contains("unix") { + return strconv.FormatInt(t.Unix(), 10) + } + return t.Format(time.RFC3339) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + if v.Type() == timeType { + return v.Interface().(time.Time).IsZero() + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/Godeps/_workspace/src/github.com/jgreat/powerdns/.gitignore b/Godeps/_workspace/src/github.com/jgreat/powerdns/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/Godeps/_workspace/src/github.com/jgreat/powerdns/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/Godeps/_workspace/src/github.com/jgreat/powerdns/LICENSE b/Godeps/_workspace/src/github.com/jgreat/powerdns/LICENSE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jgreat/powerdns/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Godeps/_workspace/src/github.com/jgreat/powerdns/README.md b/Godeps/_workspace/src/github.com/jgreat/powerdns/README.md new file mode 100644 index 0000000..bf7feec --- /dev/null +++ b/Godeps/_workspace/src/github.com/jgreat/powerdns/README.md @@ -0,0 +1,6 @@ +# powerdns +Go PowerDNS API Client + +Forked from github.com/waynz0r/powerdns + +Documentation to come. diff --git a/Godeps/_workspace/src/github.com/jgreat/powerdns/powerdns.go b/Godeps/_workspace/src/github.com/jgreat/powerdns/powerdns.go new file mode 100644 index 0000000..d8f90f7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jgreat/powerdns/powerdns.go @@ -0,0 +1,273 @@ +package powerdns + +//Based off of github.com/waynz0r/powerdns + +import ( + "fmt" + "log" + "net/url" + "strings" + + "github.com/dghubble/sling" +) + +// Error strct +type Error struct { + Message string `json:"error"` +} + +// Error Returns +func (e Error) Error() string { + return fmt.Sprintf("%v", e.Message) +} + +// CombinedRecord strct +type CombinedRecord struct { + Name string + Type string + TTL int + Records []string +} + +// Zone struct +type Zone struct { + ID string `json:"id"` + URL string `json:"url"` + Name string `json:"name"` + Type string `json:"type"` + DNSsec bool `json:"dnssec"` + Serial int `json:"serial"` + NotifiedSerial int `json:"notified_serial"` + LastCheck int `json:"last_check"` + Records []struct { + Name string `json:"name"` + Type string `json:"type"` + TTL int `json:"ttl"` + Priority int `json:"priority"` + Disabled bool `json:"disabled"` + Content string `json:"content"` + } `json:"records"` +} + +// Record struct +type Record struct { + Name string `json:"name"` + Type string `json:"type"` + TTL int `json:"ttl"` + Priority int `json:"priority"` + Disabled bool `json:"disabled"` + Content string `json:"content"` +} + +// RRset struct +type RRset struct { + Name string `json:"name"` + Type string `json:"type"` + ChangeType string `json:"changetype"` + Records []Record `json:"records"` +} + +// RRsets struct +type RRsets struct { + Sets []RRset `json:"rrsets"` +} + +// PowerDNS struct +type PowerDNS struct { + scheme string + hostname string + port string + vhost string + domain string + apikey string +} + +// New returns a new PowerDNS +func New(baseURL string, vhost string, domain string, apikey string) *PowerDNS { + if vhost == "" { + vhost = "localhost" + } + + u, err := url.Parse(baseURL) + if err != nil { + log.Fatalf("%s is not a valid url: %v", baseURL, err) + } + hp := strings.Split(u.Host, ":") + hostname := hp[0] + var port string + if len(hp) > 1 { + port = hp[1] + } else { + if u.Scheme == "https" { + port = "443" + } else { + port = "80" + } + } + + return &PowerDNS{ + scheme: u.Scheme, + hostname: hostname, + port: port, + vhost: vhost, + domain: domain, + apikey: apikey, + } +} + +// AddRecord ... +func (p *PowerDNS) AddRecord(name string, recordType string, ttl int, content []string) (*Zone, error) { + + zone, err := p.ChangeRecord(name, recordType, ttl, content, "UPSERT") + + return zone, err +} + +// DeleteRecord ... +func (p *PowerDNS) DeleteRecord(name string, recordType string, ttl int, content []string) (*Zone, error) { + + zone, err := p.ChangeRecord(name, recordType, ttl, content, "DELETE") + + return zone, err +} + +// ChangeRecord ... +func (p *PowerDNS) ChangeRecord(name string, recordType string, ttl int, content []string, action string) (*Zone, error) { + + Record := new(CombinedRecord) + Record.Name = name + Record.Type = recordType + Record.TTL = ttl + Record.Records = content + + zone, err := p.patchRRset(*Record, action) + + return zone, err +} + +func (p *PowerDNS) patchRRset(record CombinedRecord, action string) (*Zone, error) { + + if strings.HasSuffix(record.Name, ".") { + record.Name = strings.TrimSuffix(record.Name, ".") + } + + Set := RRset{Name: record.Name, Type: record.Type, ChangeType: "REPLACE"} + + if action == "DELETE" { + Set.ChangeType = "DELETE" + } + + var R Record + + for _, rec := range record.Records { + R = Record{Name: record.Name, Type: record.Type, TTL: record.TTL, Content: rec} + Set.Records = append(Set.Records, R) + } + + json := RRsets{} + json.Sets = append(json.Sets, Set) + + error := new(Error) + zone := new(Zone) + + resp, err := p.getSling().Patch(p.domain).BodyJSON(json).Receive(zone, error) + + if err == nil && resp.StatusCode >= 400 { + error.Message = strings.Join([]string{resp.Status, error.Message}, " ") + return nil, error + } + + return zone, err +} + +func (p *PowerDNS) getSling() *sling.Sling { + + u := new(url.URL) + u.Host = p.hostname + ":" + p.port + u.Scheme = p.scheme + u.Path = "/servers/" + p.vhost + "/zones/" + + Sling := sling.New().Base(u.String()) + + Sling.Set("X-API-Key", p.apikey) + + return Sling +} + +// GetRecords ... +func (p *PowerDNS) GetRecords() ([]Record, error) { + + var records []Record + + zone := new(Zone) + error := new(Error) + + u := new(url.URL) + u.Host = p.hostname + ":" + p.port + u.Scheme = p.scheme + u.Path = "/servers/" + p.vhost + "/zones/" + + resp, err := p.getSling().Path(p.domain).Set("X-API-Key", p.apikey).Receive(zone, error) + + if err != nil { + return records, fmt.Errorf("PowerDNS API call has failed: %v", err) + } + + if resp.StatusCode >= 400 { + error.Message = strings.Join([]string{resp.Status, error.Message}, " ") + return records, error + } + + for _, rec := range zone.Records { + record := Record{Name: rec.Name, Type: rec.Type, TTL: rec.TTL, Priority: rec.Priority, Disabled: rec.Disabled, Content: rec.Content} + records = append(records, record) + } + + return records, err +} + +// GetCombinedRecords ... +func (p *PowerDNS) GetCombinedRecords() ([]CombinedRecord, error) { + var records []CombinedRecord + var uniqueRecords []CombinedRecord + + //- Plain records from the zone + Records, err := p.GetRecords() + + if err != nil { + return records, err + } + + //- Iterate through records to combine them by name and type + for _, rec := range Records { + record := CombinedRecord{Name: rec.Name, Type: rec.Type, TTL: rec.TTL} + found := false + for _, uRec := range uniqueRecords { + if uRec.Name == rec.Name && uRec.Type == rec.Type { + found = true + continue + } + } + + //- append them only if missing + if found == false { + uniqueRecords = append(uniqueRecords, record) + } + } + + //- Get all values from the unique records + for _, uRec := range uniqueRecords { + for _, rec := range Records { + if uRec.Name == rec.Name && uRec.Type == rec.Type { + uRec.Records = append(uRec.Records, rec.Content) + } + } + records = append(records, uRec) + } + + return records, nil +} + +func init() { + +}