123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- 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
- }
- // 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 {
- 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{}
- }
- BaseURL, _ := url.Parse(baseURL)
- c := &Client{client: httpClient, BaseURL: BaseURL, 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}
- return c
- }
- 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
- }
|