219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package packngo
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
libraryVersion = "0.1.0"
|
|
baseURL = "https://api.packet.net/"
|
|
userAgent = "packngo/" + libraryVersion
|
|
mediaType = "application/json"
|
|
|
|
headerRateLimit = "X-RateLimit-Limit"
|
|
headerRateRemaining = "X-RateLimit-Remaining"
|
|
headerRateReset = "X-RateLimit-Reset"
|
|
)
|
|
|
|
// ListOptions specifies optional global API parameters
|
|
type ListOptions struct {
|
|
// for paginated result sets, page of results to retrieve
|
|
Page int `url:"page,omitempty"`
|
|
|
|
// for paginated result sets, the number of results to return per page
|
|
PerPage int `url:"per_page,omitempty"`
|
|
|
|
// specify which resources you want to return as collections instead of references
|
|
Includes string
|
|
}
|
|
|
|
// Response is the http response from api calls
|
|
type Response struct {
|
|
*http.Response
|
|
Rate
|
|
}
|
|
|
|
func (r *Response) populateRate() {
|
|
// parse the rate limit headers and populate Response.Rate
|
|
if limit := r.Header.Get(headerRateLimit); limit != "" {
|
|
r.Rate.RequestLimit, _ = strconv.Atoi(limit)
|
|
}
|
|
if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
|
|
r.Rate.RequestsRemaining, _ = strconv.Atoi(remaining)
|
|
}
|
|
if reset := r.Header.Get(headerRateReset); reset != "" {
|
|
if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
|
|
r.Rate.Reset = Timestamp{time.Unix(v, 0)}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ErrorResponse is the http response used on errrors
|
|
type ErrorResponse struct {
|
|
Response *http.Response
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
func (r *ErrorResponse) Error() string {
|
|
return fmt.Sprintf("%v %v: %d %v",
|
|
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, strings.Join(r.Errors, ", "))
|
|
}
|
|
|
|
// Client is the base API Client
|
|
type Client struct {
|
|
client *http.Client
|
|
|
|
BaseURL *url.URL
|
|
|
|
UserAgent string
|
|
ConsumerToken string
|
|
APIKey string
|
|
|
|
RateLimit Rate
|
|
|
|
// Packet Api Objects
|
|
Plans PlanService
|
|
Users UserService
|
|
Emails EmailService
|
|
SSHKeys SSHKeyService
|
|
Devices DeviceService
|
|
Projects ProjectService
|
|
Facilities FacilityService
|
|
OperatingSystems OSService
|
|
Ips IPService
|
|
IpReservations IPReservationService
|
|
Volumes VolumeService
|
|
}
|
|
|
|
// NewRequest inits a new http request with the proper headers
|
|
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
|
|
// relative path to append to the endpoint url, no leading slash please
|
|
rel, err := url.Parse(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u := c.BaseURL.ResolveReference(rel)
|
|
|
|
// json encode the request body, if any
|
|
buf := new(bytes.Buffer)
|
|
if body != nil {
|
|
err := json.NewEncoder(buf).Encode(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
req, err := http.NewRequest(method, u.String(), buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Close = true
|
|
|
|
req.Header.Add("X-Auth-Token", c.APIKey)
|
|
req.Header.Add("X-Consumer-Token", c.ConsumerToken)
|
|
|
|
req.Header.Add("Content-Type", mediaType)
|
|
req.Header.Add("Accept", mediaType)
|
|
req.Header.Add("User-Agent", userAgent)
|
|
return req, nil
|
|
}
|
|
|
|
// Do executes the http request
|
|
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
response := Response{Response: resp}
|
|
response.populateRate()
|
|
c.RateLimit = response.Rate
|
|
|
|
err = checkResponse(resp)
|
|
// if the response is an error, return the ErrorResponse
|
|
if err != nil {
|
|
return &response, err
|
|
}
|
|
|
|
if v != nil {
|
|
// if v implements the io.Writer interface, return the raw response
|
|
if w, ok := v.(io.Writer); ok {
|
|
io.Copy(w, resp.Body)
|
|
} else {
|
|
err = json.NewDecoder(resp.Body).Decode(v)
|
|
if err != nil {
|
|
return &response, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return &response, err
|
|
}
|
|
|
|
// NewClient initializes and returns a Client, use this to get an API Client to operate on
|
|
// N.B.: Packet's API certificate requires Go 1.5+ to successfully parse. If you are using
|
|
// an older version of Go, pass in a custom http.Client with a custom TLS configuration
|
|
// that sets "InsecureSkipVerify" to "true"
|
|
func NewClient(consumerToken string, apiKey string, httpClient *http.Client) *Client {
|
|
client, _ := NewClientWithBaseURL(consumerToken, apiKey, httpClient, baseURL)
|
|
return client
|
|
}
|
|
func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http.Client, apiBaseURL string) (*Client, error) {
|
|
if httpClient == nil {
|
|
// Don't fall back on http.DefaultClient as it's not nice to adjust state
|
|
// implicitly. If the client wants to use http.DefaultClient, they can
|
|
// pass it in explicitly.
|
|
httpClient = &http.Client{}
|
|
}
|
|
|
|
u, err := url.Parse(apiBaseURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c := &Client{client: httpClient, BaseURL: u, UserAgent: userAgent, ConsumerToken: consumerToken, APIKey: apiKey}
|
|
c.Plans = &PlanServiceOp{client: c}
|
|
c.Users = &UserServiceOp{client: c}
|
|
c.Emails = &EmailServiceOp{client: c}
|
|
c.SSHKeys = &SSHKeyServiceOp{client: c}
|
|
c.Devices = &DeviceServiceOp{client: c}
|
|
c.Projects = &ProjectServiceOp{client: c}
|
|
c.Facilities = &FacilityServiceOp{client: c}
|
|
c.OperatingSystems = &OSServiceOp{client: c}
|
|
c.Ips = &IPServiceOp{client: c}
|
|
c.IpReservations = &IPReservationServiceOp{client: c}
|
|
c.Volumes = &VolumeServiceOp{client: c}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func checkResponse(r *http.Response) error {
|
|
// return if http status code is within 200 range
|
|
if c := r.StatusCode; c >= 200 && c <= 299 {
|
|
// response is good, return
|
|
return nil
|
|
}
|
|
|
|
errorResponse := &ErrorResponse{Response: r}
|
|
data, err := ioutil.ReadAll(r.Body)
|
|
// if the response has a body, populate the message in errorResponse
|
|
if err == nil && len(data) > 0 {
|
|
json.Unmarshal(data, errorResponse)
|
|
}
|
|
|
|
return errorResponse
|
|
}
|