2018-01-16 18:41:10 +01:00
|
|
|
//
|
|
|
|
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
//
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/json"
|
2018-01-03 21:12:46 +01:00
|
|
|
"fmt"
|
2017-10-09 13:46:54 +02:00
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
"github.com/joyent/triton-go"
|
2017-10-09 13:46:54 +02:00
|
|
|
"github.com/joyent/triton-go/authentication"
|
2018-01-16 18:41:10 +01:00
|
|
|
"github.com/joyent/triton-go/errors"
|
|
|
|
pkgerrors "github.com/pkg/errors"
|
2017-10-09 13:46:54 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const nilContext = "nil context"
|
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
var (
|
2018-01-16 18:41:10 +01:00
|
|
|
ErrDefaultAuth = pkgerrors.New("default SSH agent authentication requires SDC_KEY_ID / TRITON_KEY_ID and SSH_AUTH_SOCK")
|
|
|
|
ErrAccountName = pkgerrors.New("missing account name")
|
|
|
|
ErrMissingURL = pkgerrors.New("missing API URL")
|
2018-01-03 21:12:46 +01:00
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
InvalidTritonURL = "invalid format of Triton URL"
|
|
|
|
InvalidMantaURL = "invalid format of Manta URL"
|
2018-01-03 21:12:46 +01:00
|
|
|
)
|
2017-10-09 13:46:54 +02:00
|
|
|
|
|
|
|
// Client represents a connection to the Triton Compute or Object Storage APIs.
|
|
|
|
type Client struct {
|
2018-03-13 11:10:41 +01:00
|
|
|
HTTPClient *http.Client
|
|
|
|
RequestHeader *http.Header
|
|
|
|
Authorizers []authentication.Signer
|
|
|
|
TritonURL url.URL
|
|
|
|
MantaURL url.URL
|
|
|
|
AccountName string
|
|
|
|
Username string
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// New is used to construct a Client in order to make API
|
|
|
|
// requests to the Triton API.
|
|
|
|
//
|
|
|
|
// At least one signer must be provided - example signers include
|
|
|
|
// authentication.PrivateKeySigner and authentication.SSHAgentSigner.
|
|
|
|
func New(tritonURL string, mantaURL string, accountName string, signers ...authentication.Signer) (*Client, error) {
|
2018-01-03 21:12:46 +01:00
|
|
|
if accountName == "" {
|
|
|
|
return nil, ErrAccountName
|
|
|
|
}
|
|
|
|
|
|
|
|
if tritonURL == "" && mantaURL == "" {
|
|
|
|
return nil, ErrMissingURL
|
|
|
|
}
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
cloudURL, err := url.Parse(tritonURL)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, InvalidTritonURL)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
storageURL, err := url.Parse(mantaURL)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, InvalidMantaURL)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
authorizers := make([]authentication.Signer, 0)
|
|
|
|
for _, key := range signers {
|
|
|
|
if key != nil {
|
|
|
|
authorizers = append(authorizers, key)
|
|
|
|
}
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
newClient := &Client{
|
2018-01-03 21:12:46 +01:00
|
|
|
HTTPClient: &http.Client{
|
|
|
|
Transport: httpTransport(false),
|
|
|
|
CheckRedirect: doNotFollowRedirects,
|
|
|
|
},
|
|
|
|
Authorizers: authorizers,
|
2017-10-09 13:46:54 +02:00
|
|
|
TritonURL: *cloudURL,
|
|
|
|
MantaURL: *storageURL,
|
|
|
|
AccountName: accountName,
|
|
|
|
}
|
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
// Default to constructing an SSHAgentSigner if there are no other signers
|
|
|
|
// passed into NewClient and there's an TRITON_KEY_ID and SSH_AUTH_SOCK
|
|
|
|
// available in the user's environ(7).
|
|
|
|
if len(newClient.Authorizers) == 0 {
|
|
|
|
if err := newClient.DefaultAuth(); err != nil {
|
|
|
|
return nil, err
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
return newClient, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// initDefaultAuth provides a default key signer for a client. This should only
|
|
|
|
// be used internally if the client has no other key signer for authenticating
|
|
|
|
// with Triton. We first look for both `SDC_KEY_ID` and `SSH_AUTH_SOCK` in the
|
|
|
|
// user's environ(7). If so we default to the SSH agent key signer.
|
|
|
|
func (c *Client) DefaultAuth() error {
|
2018-03-13 11:10:41 +01:00
|
|
|
tritonKeyId := triton.GetEnv("KEY_ID")
|
2018-01-03 21:12:46 +01:00
|
|
|
if tritonKeyId != "" {
|
|
|
|
input := authentication.SSHAgentSignerInput{
|
|
|
|
KeyID: tritonKeyId,
|
|
|
|
AccountName: c.AccountName,
|
|
|
|
Username: c.Username,
|
|
|
|
}
|
|
|
|
defaultSigner, err := authentication.NewSSHAgentSigner(input)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return pkgerrors.Wrapf(err, "unable to initialize NewSSHAgentSigner")
|
2018-01-03 21:12:46 +01:00
|
|
|
}
|
|
|
|
c.Authorizers = append(c.Authorizers, defaultSigner)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ErrDefaultAuth
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// InsecureSkipTLSVerify turns off TLS verification for the client connection. This
|
|
|
|
// allows connection to an endpoint with a certificate which was signed by a non-
|
|
|
|
// trusted CA, such as self-signed certificates. This can be useful when connecting
|
|
|
|
// to temporary Triton installations such as Triton Cloud-On-A-Laptop.
|
|
|
|
func (c *Client) InsecureSkipTLSVerify() {
|
|
|
|
if c.HTTPClient == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.HTTPClient.Transport = httpTransport(true)
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
// httpTransport is responsible for setting up our HTTP client's transport
|
|
|
|
// settings
|
2017-10-09 13:46:54 +02:00
|
|
|
func httpTransport(insecureSkipTLSVerify bool) *http.Transport {
|
|
|
|
return &http.Transport{
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
Dial: (&net.Dialer{
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
KeepAlive: 30 * time.Second,
|
|
|
|
}).Dial,
|
|
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
2018-01-03 21:12:46 +01:00
|
|
|
MaxIdleConns: 10,
|
|
|
|
IdleConnTimeout: 15 * time.Second,
|
2017-10-09 13:46:54 +02:00
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
InsecureSkipVerify: insecureSkipTLSVerify,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func doNotFollowRedirects(*http.Request, []*http.Request) error {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
// DecodeError decodes a backend Triton error into a more usable Go error type
|
2018-01-16 18:41:10 +01:00
|
|
|
func (c *Client) DecodeError(resp *http.Response, requestMethod string) error {
|
|
|
|
err := &errors.APIError{
|
|
|
|
StatusCode: resp.StatusCode,
|
|
|
|
}
|
2017-10-09 13:46:54 +02:00
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
if requestMethod != http.MethodHead && resp.Body != nil {
|
|
|
|
errorDecoder := json.NewDecoder(resp.Body)
|
|
|
|
if err := errorDecoder.Decode(err); err != nil {
|
|
|
|
return pkgerrors.Wrapf(err, "unable to decode error response")
|
|
|
|
}
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
if err.Message == "" {
|
|
|
|
err.Message = fmt.Sprintf("HTTP response returned status code %d", err.StatusCode)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
// overrideHeader overrides the header of the passed in HTTP request
|
|
|
|
func (c *Client) overrideHeader(req *http.Request) {
|
|
|
|
if c.RequestHeader != nil {
|
|
|
|
for k, vs := range *c.RequestHeader {
|
|
|
|
for _, v := range vs {
|
|
|
|
req.Header.Add(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// resetHeader will reset the struct field that stores custom header
|
|
|
|
// information
|
|
|
|
func (c *Client) resetHeader() {
|
|
|
|
c.RequestHeader = nil
|
|
|
|
}
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
type RequestInput struct {
|
|
|
|
Method string
|
|
|
|
Path string
|
|
|
|
Query *url.Values
|
|
|
|
Headers *http.Header
|
|
|
|
Body interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) ExecuteRequestURIParams(ctx context.Context, inputs RequestInput) (io.ReadCloser, error) {
|
2018-03-13 11:10:41 +01:00
|
|
|
defer c.resetHeader()
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
method := inputs.Method
|
|
|
|
path := inputs.Path
|
|
|
|
body := inputs.Body
|
|
|
|
query := inputs.Query
|
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
var requestBody io.Reader
|
2017-10-09 13:46:54 +02:00
|
|
|
if body != nil {
|
|
|
|
marshaled, err := json.MarshalIndent(body, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
requestBody = bytes.NewReader(marshaled)
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint := c.TritonURL
|
|
|
|
endpoint.Path = path
|
|
|
|
if query != nil {
|
|
|
|
endpoint.RawQuery = query.Encode()
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, endpoint.String(), requestBody)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
|
|
|
req.Header.Set("date", dateHeader)
|
|
|
|
|
|
|
|
// NewClient ensures there's always an authorizer (unless this is called
|
|
|
|
// outside that constructor).
|
2018-03-13 11:10:41 +01:00
|
|
|
authHeader, err := c.Authorizers[0].Sign(dateHeader, false)
|
2017-10-09 13:46:54 +02:00
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
req.Header.Set("Accept", "application/json")
|
2018-01-16 18:41:10 +01:00
|
|
|
req.Header.Set("Accept-Version", triton.CloudAPIMajorVersion)
|
|
|
|
req.Header.Set("User-Agent", triton.UserAgent())
|
2017-10-09 13:46:54 +02:00
|
|
|
|
|
|
|
if body != nil {
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
c.overrideHeader(req)
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
|
|
|
// StatusMultipleChoices is StatusCode 300
|
2017-10-09 13:46:54 +02:00
|
|
|
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
|
|
|
return resp.Body, nil
|
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, c.DecodeError(resp, req.Method)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) ExecuteRequest(ctx context.Context, inputs RequestInput) (io.ReadCloser, error) {
|
|
|
|
return c.ExecuteRequestURIParams(ctx, inputs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) ExecuteRequestRaw(ctx context.Context, inputs RequestInput) (*http.Response, error) {
|
2018-03-13 11:10:41 +01:00
|
|
|
defer c.resetHeader()
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
method := inputs.Method
|
|
|
|
path := inputs.Path
|
|
|
|
body := inputs.Body
|
2018-03-13 11:10:41 +01:00
|
|
|
query := inputs.Query
|
2017-10-09 13:46:54 +02:00
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
var requestBody io.Reader
|
2017-10-09 13:46:54 +02:00
|
|
|
if body != nil {
|
|
|
|
marshaled, err := json.MarshalIndent(body, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
requestBody = bytes.NewReader(marshaled)
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint := c.TritonURL
|
|
|
|
endpoint.Path = path
|
2018-03-13 11:10:41 +01:00
|
|
|
if query != nil {
|
|
|
|
endpoint.RawQuery = query.Encode()
|
|
|
|
}
|
2017-10-09 13:46:54 +02:00
|
|
|
|
|
|
|
req, err := http.NewRequest(method, endpoint.String(), requestBody)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
|
|
|
req.Header.Set("date", dateHeader)
|
|
|
|
|
|
|
|
// NewClient ensures there's always an authorizer (unless this is called
|
|
|
|
// outside that constructor).
|
2018-03-13 11:10:41 +01:00
|
|
|
authHeader, err := c.Authorizers[0].Sign(dateHeader, false)
|
2017-10-09 13:46:54 +02:00
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
req.Header.Set("Accept", "application/json")
|
2018-01-16 18:41:10 +01:00
|
|
|
req.Header.Set("Accept-Version", triton.CloudAPIMajorVersion)
|
|
|
|
req.Header.Set("User-Agent", triton.UserAgent())
|
2017-10-09 13:46:54 +02:00
|
|
|
|
|
|
|
if body != nil {
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
c.overrideHeader(req)
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
|
|
|
// StatusMultipleChoices is StatusCode 300
|
|
|
|
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
|
|
|
return resp, nil
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, c.DecodeError(resp, req.Method)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) ExecuteRequestStorage(ctx context.Context, inputs RequestInput) (io.ReadCloser, http.Header, error) {
|
2018-03-13 11:10:41 +01:00
|
|
|
defer c.resetHeader()
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
method := inputs.Method
|
|
|
|
path := inputs.Path
|
|
|
|
query := inputs.Query
|
|
|
|
headers := inputs.Headers
|
|
|
|
body := inputs.Body
|
|
|
|
|
|
|
|
endpoint := c.MantaURL
|
|
|
|
endpoint.Path = path
|
|
|
|
|
2018-01-03 21:12:46 +01:00
|
|
|
var requestBody io.Reader
|
2017-10-09 13:46:54 +02:00
|
|
|
if body != nil {
|
|
|
|
marshaled, err := json.MarshalIndent(body, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
requestBody = bytes.NewReader(marshaled)
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, endpoint.String(), requestBody)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if body != nil && (headers == nil || headers.Get("Content-Type") == "") {
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
}
|
|
|
|
if headers != nil {
|
|
|
|
for key, values := range *headers {
|
|
|
|
for _, value := range values {
|
|
|
|
req.Header.Set(key, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
|
|
|
req.Header.Set("date", dateHeader)
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
authHeader, err := c.Authorizers[0].Sign(dateHeader, true)
|
2017-10-09 13:46:54 +02:00
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
req.Header.Set("Accept", "*/*")
|
2018-01-16 18:41:10 +01:00
|
|
|
req.Header.Set("User-Agent", triton.UserAgent())
|
2017-10-09 13:46:54 +02:00
|
|
|
|
|
|
|
if query != nil {
|
|
|
|
req.URL.RawQuery = query.Encode()
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
c.overrideHeader(req)
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
|
|
|
// StatusMultipleChoices is StatusCode 300
|
2017-10-09 13:46:54 +02:00
|
|
|
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
|
|
|
return resp.Body, resp.Header, nil
|
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, c.DecodeError(resp, req.Method)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type RequestNoEncodeInput struct {
|
|
|
|
Method string
|
|
|
|
Path string
|
|
|
|
Query *url.Values
|
|
|
|
Headers *http.Header
|
2018-01-03 21:12:46 +01:00
|
|
|
Body io.Reader
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) ExecuteRequestNoEncode(ctx context.Context, inputs RequestNoEncodeInput) (io.ReadCloser, http.Header, error) {
|
2018-03-13 11:10:41 +01:00
|
|
|
defer c.resetHeader()
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
method := inputs.Method
|
|
|
|
path := inputs.Path
|
|
|
|
query := inputs.Query
|
|
|
|
headers := inputs.Headers
|
|
|
|
body := inputs.Body
|
|
|
|
|
|
|
|
endpoint := c.MantaURL
|
|
|
|
endpoint.Path = path
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, endpoint.String(), body)
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if headers != nil {
|
|
|
|
for key, values := range *headers {
|
|
|
|
for _, value := range values {
|
|
|
|
req.Header.Set(key, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
|
|
|
req.Header.Set("date", dateHeader)
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
authHeader, err := c.Authorizers[0].Sign(dateHeader, true)
|
2017-10-09 13:46:54 +02:00
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
req.Header.Set("Accept", "*/*")
|
2018-01-16 18:41:10 +01:00
|
|
|
req.Header.Set("Accept-Version", triton.CloudAPIMajorVersion)
|
|
|
|
req.Header.Set("User-Agent", triton.UserAgent())
|
2017-10-09 13:46:54 +02:00
|
|
|
|
|
|
|
if query != nil {
|
|
|
|
req.URL.RawQuery = query.Encode()
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:10:41 +01:00
|
|
|
c.overrideHeader(req)
|
|
|
|
|
2017-10-09 13:46:54 +02:00
|
|
|
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
|
|
|
if err != nil {
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
|
|
|
// StatusMultipleChoices is StatusCode 300
|
2017-10-09 13:46:54 +02:00
|
|
|
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
|
|
|
return resp.Body, resp.Header, nil
|
|
|
|
}
|
|
|
|
|
2018-01-16 18:41:10 +01:00
|
|
|
return nil, nil, c.DecodeError(resp, req.Method)
|
2017-10-09 13:46:54 +02:00
|
|
|
}
|