134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
package v2
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/exoscale/egoscale/v2/api"
|
|
papi "github.com/exoscale/egoscale/v2/internal/public-api"
|
|
)
|
|
|
|
const defaultTimeout = 60 * time.Second
|
|
|
|
// ClientOpt represents a function setting Exoscale API client option.
|
|
type ClientOpt func(*Client) error
|
|
|
|
// ClientOptWithAPIEndpoint returns a ClientOpt overriding the default Exoscale API endpoint.
|
|
func ClientOptWithAPIEndpoint(v string) ClientOpt {
|
|
return func(c *Client) error {
|
|
endpointURL, err := url.Parse(v)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse URL: %s", err)
|
|
}
|
|
|
|
endpointURL = endpointURL.ResolveReference(&url.URL{Path: api.Prefix})
|
|
c.apiEndpoint = endpointURL.String()
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// ClientOptWithTimeout returns a ClientOpt overriding the default client timeout.
|
|
func ClientOptWithTimeout(v time.Duration) ClientOpt {
|
|
return func(c *Client) error {
|
|
c.timeout = v
|
|
|
|
if v <= 0 {
|
|
return errors.New("timeout value must be greater than 0")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// ClientOptWithHTTPClient returns a ClientOpt overriding the default http.Client.
|
|
// Note: the Exoscale API client will chain additional middleware
|
|
// (http.RoundTripper) on the HTTP client internally, which can alter the HTTP
|
|
// requests and responses. If you don't want any other middleware than the ones
|
|
// currently set to your HTTP client, you should duplicate it and pass a copy
|
|
// instead.
|
|
func ClientOptWithHTTPClient(v *http.Client) ClientOpt {
|
|
return func(c *Client) error {
|
|
c.httpClient = v
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Client represents an Exoscale API client.
|
|
type Client struct {
|
|
apiKey string
|
|
apiSecret string
|
|
apiEndpoint string
|
|
timeout time.Duration
|
|
httpClient *http.Client
|
|
|
|
*papi.ClientWithResponses
|
|
}
|
|
|
|
// NewClient returns a new Exoscale API client, or an error if one couldn't be initialized.
|
|
func NewClient(apiKey, apiSecret string, opts ...ClientOpt) (*Client, error) {
|
|
client := Client{
|
|
apiKey: apiKey,
|
|
apiSecret: apiSecret,
|
|
apiEndpoint: api.EndpointURL,
|
|
httpClient: http.DefaultClient,
|
|
timeout: defaultTimeout,
|
|
}
|
|
|
|
if client.apiKey == "" || client.apiSecret == "" {
|
|
return nil, fmt.Errorf("%w: missing or incomplete API credentials", ErrClientConfig)
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
if err := opt(&client); err != nil {
|
|
return nil, fmt.Errorf("%w: %s", ErrClientConfig, err)
|
|
}
|
|
}
|
|
|
|
apiSecurityProvider, err := api.NewSecurityProvider(client.apiKey, client.apiSecret)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to initialize API security provider: %s", err)
|
|
}
|
|
|
|
apiURL, err := url.Parse(client.apiEndpoint)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to initialize API client: %s", err)
|
|
}
|
|
apiURL = apiURL.ResolveReference(&url.URL{Path: api.Prefix})
|
|
|
|
client.httpClient.Transport = api.NewAPIErrorHandlerMiddleware(client.httpClient.Transport)
|
|
|
|
papiOpts := []papi.ClientOption{
|
|
papi.WithHTTPClient(client.httpClient),
|
|
papi.WithRequestEditorFn(
|
|
papi.MultiRequestsEditor(
|
|
apiSecurityProvider.Intercept,
|
|
setEndpointFromContext,
|
|
),
|
|
),
|
|
}
|
|
|
|
if client.ClientWithResponses, err = papi.NewClientWithResponses(apiURL.String(), papiOpts...); err != nil {
|
|
return nil, fmt.Errorf("unable to initialize API client: %s", err)
|
|
}
|
|
|
|
return &client, nil
|
|
}
|
|
|
|
// setEndpointFromContext is an HTTP client request interceptor that overrides the "Host" header
|
|
// with information from a request endpoint optionally set in the context instance. If none is
|
|
// found, the request is left untouched.
|
|
func setEndpointFromContext(ctx context.Context, req *http.Request) error {
|
|
if v, ok := ctx.Value(api.ReqEndpoint{}).(api.ReqEndpoint); ok {
|
|
req.Host = v.Host()
|
|
req.URL.Host = v.Host()
|
|
}
|
|
|
|
return nil
|
|
}
|