mirror of
https://github.com/vxcontrol/external-dns.git
synced 2026-07-20 21:58:23 -04:00
Add deps for powerdns
This commit is contained in:
Generated
+29
-1
@@ -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"
|
||||
|
||||
+13
@@ -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 ./...
|
||||
+52
@@ -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.
|
||||
|
||||
|
||||
+21
@@ -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.
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
|
||||
# Sling [](https://travis-ci.org/dghubble/sling) [](https://gocover.io/github.com/dghubble/sling) [](https://godoc.org/github.com/dghubble/sling)
|
||||
<img align="right" src="https://s3.amazonaws.com/dghubble/small-gopher-with-sling.png">
|
||||
|
||||
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)
|
||||
+179
@@ -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
|
||||
+390
@@ -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)
|
||||
}
|
||||
+27
@@ -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.
|
||||
+320
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
+202
@@ -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.
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# powerdns
|
||||
Go PowerDNS API Client
|
||||
|
||||
Forked from github.com/waynz0r/powerdns
|
||||
|
||||
Documentation to come.
|
||||
+273
@@ -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() {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user