Files
nomad/vendor/github.com/softlayer/softlayer-go/session/session.go
Seth Hoenig 15fb4c990a 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

360 lines
9.9 KiB
Go

/**
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package session
import (
"fmt"
"log"
"math/rand"
"net"
"net/http"
"os"
"os/user"
"runtime"
"strings"
"time"
"github.com/softlayer/softlayer-go/config"
"github.com/softlayer/softlayer-go/sl"
)
// Logger is the logger used by the SoftLayer session package. Can be overridden by the user.
var Logger *log.Logger
func init() {
// initialize the logger used by the session package.
Logger = log.New(os.Stderr, "", log.LstdFlags)
}
// DefaultEndpoint is the default endpoint for API calls, when no override
// is provided.
const DefaultEndpoint = "https://api.softlayer.com/rest/v3"
var retryableErrorCodes = []string{"SoftLayer_Exception_WebService_RateLimitExceeded"}
// TransportHandler interface for the protocol-specific handling of API requests.
type TransportHandler interface {
// DoRequest is the protocol-specific handler for making API requests.
//
// sess is a reference to the current session object, where authentication and
// endpoint information can be found.
//
// service and method are the SoftLayer service name and method name, exactly as they
// are documented at http://sldn.softlayer.com/reference/softlayerapi (i.e., with the
// 'SoftLayer_' prefix and properly cased.
//
// args is a slice of arguments required for the service method being invoked. The
// types of each argument varies. See the method definition in the services package
// for the expected type of each argument.
//
// options is an sl.Options struct, containing any mask, filter, or result limit values
// to be applied.
//
// pResult is a pointer to a variable to be populated with the result of the API call.
// DoRequest should ensure that the native API response (i.e., XML or JSON) is correctly
// unmarshaled into the result structure.
//
// A sl.Error is returned, and can be (with a type assertion) inspected for details of
// the error (http code, API error message, etc.), or simply handled as a generic error,
// (in which case no type assertion would be necessary)
DoRequest(
sess *Session,
service string,
method string,
args []interface{},
options *sl.Options,
pResult interface{}) error
}
const (
DefaultTimeout = time.Second * 120
DefaultRetryWait = time.Second * 3
)
// Session stores the information required for communication with the SoftLayer
// API
type Session struct {
// UserName is the name of the SoftLayer API user
UserName string
// ApiKey is the secret for making API calls
APIKey string
// Endpoint is the SoftLayer API endpoint to communicate with
Endpoint string
// UserId is the user id for token-based authentication
UserId int
// AuthToken is the token secret for token-based authentication
AuthToken string
// Debug controls logging of request details (URI, parameters, etc.)
Debug bool
// The handler whose DoRequest() function will be called for each API request.
// Handles the request and any response parsing specific to the desired protocol
// (e.g., REST). Set automatically for a new Session, based on the
// provided Endpoint.
TransportHandler TransportHandler
// HTTPClient This allows a custom user configured HTTP Client.
HTTPClient *http.Client
// Custom Headers to be used on each request (Currently only for rest)
Headers map[string]string
// Timeout specifies a time limit for http requests made by this
// session. Requests that take longer that the specified timeout
// will result in an error.
Timeout time.Duration
// Retries is the number of times to retry a connection that failed due to a timeout.
Retries int
// RetryWait minimum wait time to retry a request
RetryWait time.Duration
// userAgent is the user agent to send with each API request
// User shouldn't be able to change or set the base user agent
userAgent string
}
func init() {
rand.Seed(time.Now().UnixNano())
}
// New creates and returns a pointer to a new session object. It takes up to
// four parameters, all of which are optional. If specified, they will be
// interpreted in the following sequence:
//
// 1. UserName
// 2. Api Key
// 3. Endpoint
// 4. Timeout
//
// If one or more are omitted, New() will attempt to retrieve these values from
// the environment, and the ~/.softlayer config file, in that order.
func New(args ...interface{}) *Session {
keys := map[string]int{"username": 0, "api_key": 1, "endpoint_url": 2, "timeout": 3}
values := []string{"", "", "", ""}
for i := 0; i < len(args); i++ {
values[i] = args[i].(string)
}
// Default to the environment variables
// Prioritize SL_USERNAME
envFallback("SL_USERNAME", &values[keys["username"]])
envFallback("SOFTLAYER_USERNAME", &values[keys["username"]])
// Prioritize SL_API_KEY
envFallback("SL_API_KEY", &values[keys["api_key"]])
envFallback("SOFTLAYER_API_KEY", &values[keys["api_key"]])
// Prioritize SL_ENDPOINT_URL
envFallback("SL_ENDPOINT_URL", &values[keys["endpoint_url"]])
envFallback("SOFTLAYER_ENDPOINT_URL", &values[keys["endpoint_url"]])
envFallback("SL_TIMEOUT", &values[keys["timeout"]])
envFallback("SOFTLAYER_TIMEOUT", &values[keys["timeout"]])
// Read ~/.softlayer for configuration
var homeDir string
u, err := user.Current()
if err != nil {
for _, name := range []string{"HOME", "USERPROFILE"} { // *nix, windows
if dir := os.Getenv(name); dir != "" {
homeDir = dir
break
}
}
} else {
homeDir = u.HomeDir
}
if homeDir != "" {
configPath := fmt.Sprintf("%s/.softlayer", homeDir)
if _, err = os.Stat(configPath); !os.IsNotExist(err) {
// config file exists
file, err := config.LoadFile(configPath)
if err != nil {
log.Println(fmt.Sprintf("[WARN] session: Could not parse %s : %s", configPath, err))
} else {
for k, v := range keys {
value, ok := file.Get("softlayer", k)
if ok && values[v] == "" {
values[v] = value
}
}
}
}
} else {
log.Println("[WARN] session: home dir could not be determined. Skipping read of ~/.softlayer.")
}
endpointURL := values[keys["endpoint_url"]]
if endpointURL == "" {
endpointURL = DefaultEndpoint
}
sess := &Session{
UserName: values[keys["username"]],
APIKey: values[keys["api_key"]],
Endpoint: endpointURL,
userAgent: getDefaultUserAgent(),
}
timeout := values[keys["timeout"]]
if timeout != "" {
timeoutDuration, err := time.ParseDuration(fmt.Sprintf("%ss", timeout))
if err == nil {
sess.Timeout = timeoutDuration
}
}
sess.RetryWait = DefaultRetryWait
return sess
}
// DoRequest hands off the processing to the assigned transport handler. It is
// normally called internally by the service objects, but is exported so that it can
// be invoked directly by client code in exceptional cases where direct control is
// needed over one of the parameters.
//
// For a description of parameters, see TransportHandler.DoRequest in this package
func (r *Session) DoRequest(service string, method string, args []interface{}, options *sl.Options, pResult interface{}) error {
if r.TransportHandler == nil {
r.TransportHandler = getDefaultTransport(r.Endpoint)
}
return r.TransportHandler.DoRequest(r, service, method, args, options, pResult)
}
// SetTimeout creates a copy of the session and sets the passed timeout into it
// before returning it.
func (r *Session) SetTimeout(timeout time.Duration) *Session {
var s Session
s = *r
s.Timeout = timeout
return &s
}
// SetRetries creates a copy of the session and sets the passed retries into it
// before returning it.
func (r *Session) SetRetries(retries int) *Session {
var s Session
s = *r
s.Retries = retries
return &s
}
// SetRetryWait creates a copy of the session and sets the passed retryWait into it
// before returning it.
func (r *Session) SetRetryWait(retryWait time.Duration) *Session {
var s Session
s = *r
s.RetryWait = retryWait
return &s
}
// AppendUserAgent allows higher level application to identify themselves by
// appending to the useragent string
func (r *Session) AppendUserAgent(agent string) {
if r.userAgent == "" {
r.userAgent = getDefaultUserAgent()
}
if agent != "" {
r.userAgent += " " + agent
}
}
// ResetUserAgent resets the current user agent to the default value
func (r *Session) ResetUserAgent() {
r.userAgent = getDefaultUserAgent()
}
func envFallback(keyName string, value *string) {
if *value == "" {
*value = os.Getenv(keyName)
}
}
func getDefaultTransport(endpointURL string) TransportHandler {
var transportHandler TransportHandler
if strings.Contains(endpointURL, "/xmlrpc/") {
transportHandler = &XmlRpcTransport{}
} else {
transportHandler = &RestTransport{}
}
return transportHandler
}
func isTimeout(err error) bool {
if slErr, ok := err.(sl.Error); ok {
switch slErr.StatusCode {
case 408, 504, 599:
return true
}
}
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
return true
}
if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
return true
}
if netErr, ok := err.(net.UnknownNetworkError); ok && netErr.Timeout() {
return true
}
return false
}
func hasRetryableCode(err error) bool {
for _, code := range retryableErrorCodes {
if slErr, ok := err.(sl.Error); ok {
if slErr.Exception == code {
return true
}
}
}
return false
}
func isRetryable(err error) bool {
return isTimeout(err) || hasRetryableCode(err)
}
func getDefaultUserAgent() string {
return fmt.Sprintf("softlayer-go/%s (%s;%s;%s)", sl.Version.String(),
runtime.Version(),
runtime.GOARCH,
runtime.GOOS,
)
}