open-nomad/vendor/github.com/nicolai86/scaleway-sdk/api.go
Seth Hoenig 435c0d9fc8 deps: Switch to Go modules for dependency management
This PR switches the Nomad repository from using govendor to Go modules
for managing dependencies. Aspects of the Nomad workflow remain pretty
much the same. The usual Makefile targets should continue to work as
they always did. The API submodule simply defers to the parent Nomad
version on the repository, keeping the semantics of API versioning that
currently exists.
2020-06-02 14:30:36 -05:00

303 lines
8 KiB
Go

// Copyright (C) 2015 . All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.
// Interact with API
// Package api contains client and functions to interact with API
package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"golang.org/x/sync/errgroup"
)
// https://cp-par1.scaleway.com/products/servers
// https://cp-ams1.scaleway.com/products/servers
// Default values
var (
AccountAPI = "https://account.scaleway.com/"
MetadataAPI = "http://169.254.42.42/"
MarketplaceAPI = "https://api-marketplace.scaleway.com"
ComputeAPIPar1 = "https://cp-par1.scaleway.com/"
ComputeAPIAms1 = "https://cp-ams1.scaleway.com/"
URLPublicDNS = ".pub.cloud.scaleway.com"
URLPrivateDNS = ".priv.cloud.scaleway.com"
)
func init() {
if url := os.Getenv("SCW_ACCOUNT_API"); url != "" {
AccountAPI = url
}
if url := os.Getenv("SCW_METADATA_API"); url != "" {
MetadataAPI = url
}
if url := os.Getenv("SCW_MARKETPLACE_API"); url != "" {
MarketplaceAPI = url
}
}
const (
perPage = 50
)
// HTTPClient wraps the net/http Client Do method
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
// API is the interface used to communicate with the API
type API struct {
Organization string // Organization is the identifier of the organization
Token string // Token is the authentication token for the organization
Client HTTPClient // Client is used for all HTTP interactions
password string // Password is the authentication password
userAgent string
computeAPI string
Region string
}
// APIError represents a API Error
type APIError struct {
// Message is a human-friendly error message
APIMessage string `json:"message,omitempty"`
// Type is a string code that defines the kind of error
Type string `json:"type,omitempty"`
// Fields contains detail about validation error
Fields map[string][]string `json:"fields,omitempty"`
// StatusCode is the HTTP status code received
StatusCode int `json:"-"`
// Message
Message string `json:"-"`
}
// Error returns a string representing the error
func (e APIError) Error() string {
var b bytes.Buffer
fmt.Fprintf(&b, "StatusCode: %v, ", e.StatusCode)
fmt.Fprintf(&b, "Type: %v, ", e.Type)
fmt.Fprintf(&b, "APIMessage: \x1b[31m%v\x1b[0m", e.APIMessage)
if len(e.Fields) > 0 {
fmt.Fprintf(&b, ", Details: %v", e.Fields)
}
return b.String()
}
// New creates a ready-to-use SDK client
func New(organization, token, region string, options ...func(*API)) (*API, error) {
s := &API{
// exposed
Organization: organization,
Token: token,
Client: &http.Client{},
// internal
password: "",
userAgent: "scaleway-sdk",
}
for _, option := range options {
option(s)
}
switch region {
case "par1", "":
s.computeAPI = ComputeAPIPar1
case "ams1":
s.computeAPI = ComputeAPIAms1
default:
return nil, fmt.Errorf("%s isn't a valid region", region)
}
s.Region = region
if url := os.Getenv("SCW_COMPUTE_API"); url != "" {
s.computeAPI = url
}
return s, nil
}
func (s *API) response(method, uri string, content io.Reader) (resp *http.Response, err error) {
var (
req *http.Request
)
req, err = http.NewRequest(method, uri, content)
if err != nil {
err = fmt.Errorf("response %s %s", method, uri)
return
}
req.Header.Set("X-Auth-Token", s.Token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", s.userAgent)
resp, err = s.Client.Do(req)
return
}
// GetResponsePaginate fetchs all resources and returns an http.Response object for the requested resource
func (s *API) GetResponsePaginate(apiURL, resource string, values url.Values) (*http.Response, error) {
resp, err := s.response("HEAD", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil)
if err != nil {
return nil, err
}
count := resp.Header.Get("X-Total-Count")
var maxElem int
if count == "" {
maxElem = 0
} else {
maxElem, err = strconv.Atoi(count)
if err != nil {
return nil, err
}
}
get := maxElem / perPage
if (float32(maxElem) / perPage) > float32(get) {
get++
}
if get <= 1 { // If there is 0 or 1 page of result, the response is not paginated
if len(values) == 0 {
return s.response("GET", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), nil)
}
return s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil)
}
fetchAll := !(values.Get("per_page") != "" || values.Get("page") != "")
if fetchAll {
var g errgroup.Group
ch := make(chan *http.Response, get)
for i := 1; i <= get; i++ {
i := i // closure tricks
g.Go(func() (err error) {
var resp *http.Response
val := url.Values{}
val.Set("per_page", fmt.Sprintf("%v", perPage))
val.Set("page", fmt.Sprintf("%v", i))
resp, err = s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, val.Encode()), nil)
ch <- resp
return
})
}
if err = g.Wait(); err != nil {
return nil, err
}
newBody := make(map[string][]json.RawMessage)
body := make(map[string][]json.RawMessage)
key := ""
for i := 0; i < get; i++ {
res := <-ch
if res.StatusCode != http.StatusOK {
return res, nil
}
content, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return nil, err
}
if err := json.Unmarshal(content, &body); err != nil {
return nil, err
}
if i == 0 {
resp = res
for k := range body {
key = k
break
}
}
newBody[key] = append(newBody[key], body[key]...)
}
payload := new(bytes.Buffer)
if err := json.NewEncoder(payload).Encode(newBody); err != nil {
return nil, err
}
resp.Body = ioutil.NopCloser(payload)
} else {
resp, err = s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil)
}
return resp, err
}
// PostResponse returns an http.Response object for the updated resource
func (s *API) PostResponse(apiURL, resource string, data interface{}) (*http.Response, error) {
payload := new(bytes.Buffer)
if err := json.NewEncoder(payload).Encode(data); err != nil {
return nil, err
}
return s.response("POST", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload)
}
// PatchResponse returns an http.Response object for the updated resource
func (s *API) PatchResponse(apiURL, resource string, data interface{}) (*http.Response, error) {
payload := new(bytes.Buffer)
if err := json.NewEncoder(payload).Encode(data); err != nil {
return nil, err
}
return s.response("PATCH", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload)
}
// PutResponse returns an http.Response object for the updated resource
func (s *API) PutResponse(apiURL, resource string, data interface{}) (*http.Response, error) {
payload := new(bytes.Buffer)
if err := json.NewEncoder(payload).Encode(data); err != nil {
return nil, err
}
return s.response("PUT", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload)
}
// DeleteResponse returns an http.Response object for the deleted resource
func (s *API) DeleteResponse(apiURL, resource string) (*http.Response, error) {
return s.response("DELETE", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), nil)
}
// handleHTTPError checks the statusCode and displays the error
func (s *API) handleHTTPError(goodStatusCode []int, resp *http.Response) ([]byte, error) {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= http.StatusInternalServerError {
return nil, errors.New(string(body))
}
for _, code := range goodStatusCode {
if code == resp.StatusCode {
return body, nil
}
}
var scwError APIError
scwError.StatusCode = resp.StatusCode
if len(body) > 0 {
if err := json.Unmarshal(body, &scwError); err != nil {
return nil, err
}
}
return nil, scwError
}
// SetPassword register the password
func (s *API) SetPassword(password string) {
s.password = password
}