client.go raw

   1  // Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates.  All rights reserved.
   2  // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
   3  
   4  // Package common provides supporting functions and structs used by service packages
   5  package common
   6  
   7  import (
   8  	"bytes"
   9  	"context"
  10  	"fmt"
  11  	"io"
  12  	"io/ioutil"
  13  	"math/rand"
  14  	"net/http"
  15  	"net/http/httputil"
  16  	"net/url"
  17  	"os"
  18  	"os/user"
  19  	"path"
  20  	"path/filepath"
  21  	"reflect"
  22  	"regexp"
  23  	"runtime"
  24  	"strconv"
  25  	"strings"
  26  	"sync"
  27  	"sync/atomic"
  28  	"time"
  29  )
  30  
  31  const (
  32  	// DefaultHostURLTemplate The default url template for service hosts
  33  	DefaultHostURLTemplate = "%s.%s.oraclecloud.com"
  34  
  35  	// requestHeaderAccept The key for passing a header to indicate Accept
  36  	requestHeaderAccept = "Accept"
  37  
  38  	// requestHeaderAuthorization The key for passing a header to indicate Authorization
  39  	requestHeaderAuthorization = "Authorization"
  40  
  41  	// requestHeaderContentLength The key for passing a header to indicate Content Length
  42  	requestHeaderContentLength = "Content-Length"
  43  
  44  	// requestHeaderContentType The key for passing a header to indicate Content Type
  45  	requestHeaderContentType = "Content-Type"
  46  
  47  	// requestHeaderExpect The key for passing a header to indicate Expect/100-Continue
  48  	requestHeaderExpect = "Expect"
  49  
  50  	// requestHeaderDate The key for passing a header to indicate Date
  51  	requestHeaderDate = "Date"
  52  
  53  	// requestHeaderIfMatch The key for passing a header to indicate If Match
  54  	requestHeaderIfMatch = "if-match"
  55  
  56  	// requestHeaderOpcClientInfo The key for passing a header to indicate OPC Client Info
  57  	requestHeaderOpcClientInfo = "opc-client-info"
  58  
  59  	// requestHeaderOpcRetryToken The key for passing a header to indicate OPC Retry Token
  60  	requestHeaderOpcRetryToken = "opc-retry-token"
  61  
  62  	// requestHeaderOpcRequestID The key for unique Oracle-assigned identifier for the request.
  63  	requestHeaderOpcRequestID = "opc-request-id"
  64  
  65  	// requestHeaderOpcClientRequestID The key for unique Oracle-assigned identifier for the request.
  66  	requestHeaderOpcClientRequestID = "opc-client-request-id"
  67  
  68  	// requestHeaderUserAgent The key for passing a header to indicate User Agent
  69  	requestHeaderUserAgent = "User-Agent"
  70  
  71  	// requestHeaderXContentSHA256 The key for passing a header to indicate SHA256 hash
  72  	requestHeaderXContentSHA256 = "X-Content-SHA256"
  73  
  74  	// requestHeaderOpcOboToken The key for passing a header to use obo token
  75  	requestHeaderOpcOboToken = "opc-obo-token"
  76  
  77  	// private constants
  78  	defaultScheme            = "https"
  79  	defaultSDKMarker         = "Oracle-GoSDK"
  80  	defaultUserAgentTemplate = "%s/%s (%s/%s; go/%s)" //SDK/SDKVersion (OS/OSVersion; Lang/LangVersion)
  81  	// http.Client.Timeout includes Dial, TLSHandshake, Request, Response header and body
  82  	defaultTimeout           = 60 * time.Second
  83  	defaultConfigFileName    = "config"
  84  	defaultConfigDirName     = ".oci"
  85  	configFilePathEnvVarName = "OCI_CONFIG_FILE"
  86  
  87  	secondaryConfigDirName = ".oraclebmc"
  88  	maxBodyLenForDebug     = 1024 * 1000
  89  
  90  	// appendUserAgentEnv The key for retrieving append user agent value from env var
  91  	appendUserAgentEnv = "OCI_SDK_APPEND_USER_AGENT"
  92  
  93  	// requestHeaderOpcClientRetries The key for passing a header to set client retries info
  94  	requestHeaderOpcClientRetries = "opc-client-retries"
  95  
  96  	// isDefaultRetryEnabled The key for set default retry disabled from env var
  97  	isDefaultRetryEnabled = "OCI_SDK_DEFAULT_RETRY_ENABLED"
  98  
  99  	// isDefaultCircuitBreakerEnabled is the key for set default circuit breaker disabled from env var
 100  	isDefaultCircuitBreakerEnabled = "OCI_SDK_DEFAULT_CIRCUITBREAKER_ENABLED"
 101  
 102  	//circuitBreakerNumberOfHistoryResponseEnv is the number of recorded history responses
 103  	circuitBreakerNumberOfHistoryResponseEnv = "OCI_SDK_CIRCUITBREAKER_NUM_HISTORY_RESPONSE"
 104  
 105  	// ociDefaultRefreshIntervalForCustomCerts is the env var for overriding the defaultRefreshIntervalForCustomCerts.
 106  	// The value represents the refresh interval in minutes and has a higher precedence than defaultRefreshIntervalForCustomCerts
 107  	// but has a lower precedence then the refresh interval configured via OciGlobalRefreshIntervalForCustomCerts
 108  	// If the value is negative, then it is assumed that this property is not configured
 109  	// if the value is Zero, then the refresh of custom certs will be disabled
 110  	ociDefaultRefreshIntervalForCustomCerts = "OCI_DEFAULT_REFRESH_INTERVAL_FOR_CUSTOM_CERTS"
 111  
 112  	// ociDefaultCertsPath is the env var for the path to the SSL cert file
 113  	ociDefaultCertsPath = "OCI_DEFAULT_CERTS_PATH"
 114  
 115  	// ociDefaultClientCertsPath is the env var for the path to the custom client cert
 116  	ociDefaultClientCertsPath = "OCI_DEFAULT_CLIENT_CERTS_PATH"
 117  
 118  	// ociDefaultClientCertsPrivateKeyPath is the env var for the path to the custom client cert private key
 119  	ociDefaultClientCertsPrivateKeyPath = "OCI_DEFAULT_CLIENT_CERTS_PRIVATE_KEY_PATH"
 120  
 121  	//maxAttemptsForRefreshableRetry is the number of retry when 401 happened on a refreshable auth type
 122  	maxAttemptsForRefreshableRetry = 3
 123  
 124  	//defaultRefreshIntervalForCustomCerts is the default refresh interval in minutes
 125  	defaultRefreshIntervalForCustomCerts = 30
 126  
 127  	// CustomClientTimeoutEnvVar allows the user to set the timeout in seconds to be used by each service client.
 128  	CustomClientTimeoutEnvVar = "OCI_CUSTOM_CLIENT_TIMEOUT"
 129  
 130  	// Environment variable to check whether dual stack endpoints should be enabled
 131  	ociDualStackEndpointEnabledEnvVar = "OCI_DUAL_STACK_ENDPOINT_ENABLED"
 132  
 133  	// String representing a single "phrase" of an endpoint template option
 134  	endpointTemplateOptionPhrase = "((\\w|\\.|\\-)+)"
 135  
 136  	// Checks for template for endpoint options
 137  	patternForEndpointTemplateOptions = "\\{" + endpointTemplateOptionPhrase + "\\?((" + endpointTemplateOptionPhrase + ":" + endpointTemplateOptionPhrase + ")" +
 138  		"|(" + endpointTemplateOptionPhrase + ":\\s*)|(\\s*:" + endpointTemplateOptionPhrase + "))}"
 139  
 140  	dualStackOption = "{dualStack"
 141  )
 142  
 143  // OciGlobalRefreshIntervalForCustomCerts is the global policy for overriding the refresh interval in minutes.
 144  // This variable has a higher precedence than the env variable OCI_DEFAULT_REFRESH_INTERVAL_FOR_CUSTOM_CERTS
 145  // and the defaultRefreshIntervalForCustomCerts values.
 146  // If the value is negative, then it is assumed that this property is not configured
 147  // if the value is Zero, then the refresh of custom certs will be disabled
 148  var OciGlobalRefreshIntervalForCustomCerts int = -1
 149  
 150  // RequestInterceptor function used to customize the request before calling the underlying service
 151  type RequestInterceptor func(*http.Request) error
 152  
 153  // HTTPRequestDispatcher wraps the execution of a http request, it is generally implemented by
 154  // http.Client.Do, but can be customized for testing
 155  type HTTPRequestDispatcher interface {
 156  	Do(req *http.Request) (*http.Response, error)
 157  }
 158  
 159  // CustomClientConfiguration contains configurations set at client level
 160  type CustomClientConfiguration struct {
 161  
 162  	// Retry policy used on calls made by the client
 163  	RetryPolicy *RetryPolicy
 164  
 165  	// The Circuit Breaker used to regulate calls made by the client
 166  	CircuitBreaker *OciCircuitBreaker
 167  
 168  	// Allows user to decide if they want to use realm specific endpoints
 169  	RealmSpecificServiceEndpointTemplateEnabled *bool
 170  
 171  	// Allows user to decide if they want to use dual stack endpoints
 172  	EnableDualStackEndpoints *bool
 173  
 174  	// Set on creation of the client, based on the below flag from the service spec
 175  	// x-obmcs-endpoint-template-options: dualStack: true/false
 176  	ServiceUsesDualStackByDefault *bool
 177  }
 178  
 179  // BaseClient struct implements all basic operations to call oci web services.
 180  type BaseClient struct {
 181  	//HTTPClient performs the http network operations
 182  	HTTPClient HTTPRequestDispatcher
 183  
 184  	//Signer performs auth operation
 185  	Signer HTTPRequestSigner
 186  
 187  	//A request interceptor can be used to customize the request before signing and dispatching
 188  	Interceptor RequestInterceptor
 189  
 190  	//The host of the service
 191  	Host string
 192  
 193  	//The user agent
 194  	UserAgent string
 195  
 196  	//Base path for all operations of this client
 197  	BasePath string
 198  
 199  	Configuration CustomClientConfiguration
 200  }
 201  
 202  // SetCustomClientConfiguration sets client with retry and other custom configurations
 203  func (client *BaseClient) SetCustomClientConfiguration(config CustomClientConfiguration) {
 204  	client.Configuration = config
 205  }
 206  
 207  // RetryPolicy returns the retryPolicy configured for client
 208  func (client *BaseClient) RetryPolicy() *RetryPolicy {
 209  	return client.Configuration.RetryPolicy
 210  }
 211  
 212  // Endpoint returns the endpoint configured for client
 213  func (client *BaseClient) Endpoint() string {
 214  	host := client.Host
 215  	if !strings.Contains(host, "http") &&
 216  		!strings.Contains(host, "https") {
 217  		host = fmt.Sprintf("%s://%s", defaultScheme, host)
 218  	}
 219  	return host
 220  }
 221  
 222  func UpdateEndpointTemplateForOptions(client *BaseClient) {
 223  	templateRegex := regexp.MustCompile(patternForEndpointTemplateOptions)
 224  	templates := templateRegex.FindAllString(client.Host, -1)
 225  	for _, option := range templates {
 226  		optionParam := ""
 227  		optionEnabledParam := option[strings.Index(option, "?")+1 : strings.Index(option, ":")]
 228  		optionDisabledParam := option[strings.Index(option, ":")+1 : strings.Index(option, "}")]
 229  
 230  		// Option case: Dual Stack Endpoints
 231  		if strings.Contains(option, dualStackOption) {
 232  			dualStackEnvVarValue := os.Getenv(ociDualStackEndpointEnabledEnvVar)
 233  			if client.IsServiceDualStackEnabledByDefault() {
 234  				if !client.IsDualStackEndpointEnabled() || (dualStackEnvVarValue != "" && strings.ToLower(dualStackEnvVarValue) == "false") {
 235  					optionParam = optionDisabledParam
 236  				} else {
 237  					optionParam = optionEnabledParam
 238  				}
 239  			} else {
 240  				if client.IsDualStackEndpointEnabled() || (dualStackEnvVarValue != "" && strings.ToLower(dualStackEnvVarValue) == "true") {
 241  					optionParam = optionEnabledParam
 242  				} else {
 243  					optionParam = optionDisabledParam
 244  				}
 245  			}
 246  		}
 247  		client.Host = strings.Replace(client.Host, option, optionParam, -1)
 248  	}
 249  }
 250  
 251  // UseDualStackEndpointsByDefault sets whether dual stack endpoints are used by default
 252  func (client *BaseClient) UseDualStackEndpointsByDefault(useByDefault bool) {
 253  	client.Configuration.EnableDualStackEndpoints = &useByDefault
 254  	client.Configuration.ServiceUsesDualStackByDefault = &useByDefault
 255  }
 256  
 257  // EnableDualStackEndpoints sets whether dual stack endpoints should be used for this client
 258  func (client *BaseClient) EnableDualStackEndpoints(EnableDualStack bool) {
 259  	client.Configuration.EnableDualStackEndpoints = &EnableDualStack
 260  }
 261  
 262  // IsDualStackEndpointEnabled is used to check if Dual Stack Endpoints are Enabled
 263  func (client *BaseClient) IsDualStackEndpointEnabled() bool {
 264  	return client.Configuration.EnableDualStackEndpoints != nil && *client.Configuration.EnableDualStackEndpoints
 265  }
 266  
 267  // IsServiceDualStackEnabledByDefault is used to check if Dual Stack Endpoints enabled by default for the service of the client
 268  func (client *BaseClient) IsServiceDualStackEnabledByDefault() bool {
 269  	return client.Configuration.ServiceUsesDualStackByDefault != nil && *client.Configuration.ServiceUsesDualStackByDefault
 270  }
 271  
 272  func defaultUserAgent() string {
 273  	userAgent := fmt.Sprintf(defaultUserAgentTemplate, defaultSDKMarker, Version(), runtime.GOOS, runtime.GOARCH, runtime.Version())
 274  	appendUA := os.Getenv(appendUserAgentEnv)
 275  	if appendUA != "" {
 276  		userAgent = fmt.Sprintf("%s %s", userAgent, appendUA)
 277  	}
 278  	return userAgent
 279  }
 280  
 281  var clientCounter int64
 282  
 283  func getNextSeed() int64 {
 284  	newCounterValue := atomic.AddInt64(&clientCounter, 1)
 285  	return newCounterValue + time.Now().UnixNano()
 286  }
 287  
 288  func newBaseClient(signer HTTPRequestSigner, dispatcher HTTPRequestDispatcher) BaseClient {
 289  	rand.Seed(getNextSeed())
 290  
 291  	baseClient := BaseClient{
 292  		UserAgent:   defaultUserAgent(),
 293  		Interceptor: nil,
 294  		Signer:      signer,
 295  		HTTPClient:  dispatcher,
 296  	}
 297  
 298  	// check the default retry environment variable setting
 299  	if IsEnvVarTrue(isDefaultRetryEnabled) {
 300  		defaultRetry := DefaultRetryPolicy()
 301  		baseClient.Configuration.RetryPolicy = &defaultRetry
 302  	} else if IsEnvVarFalse(isDefaultRetryEnabled) {
 303  		policy := NoRetryPolicy()
 304  		baseClient.Configuration.RetryPolicy = &policy
 305  	}
 306  	// check if user defined global retry is configured
 307  	if GlobalRetry != nil {
 308  		baseClient.Configuration.RetryPolicy = GlobalRetry
 309  	}
 310  
 311  	baseClient.UseDualStackEndpointsByDefault(false)
 312  
 313  	return baseClient
 314  }
 315  
 316  func defaultHTTPDispatcher() http.Client {
 317  	var httpClient http.Client
 318  	refreshInterval := getCustomCertRefreshInterval()
 319  	if refreshInterval <= 0 {
 320  		Debug("Custom cert refresh has been disabled")
 321  	}
 322  	var tp = &OciHTTPTransportWrapper{
 323  		RefreshRate:       time.Duration(refreshInterval) * time.Minute,
 324  		TLSConfigProvider: GetTLSConfigTemplateForTransport(),
 325  	}
 326  
 327  	// Set client timeout to default or value set in environment variable
 328  	clientTimeout := defaultTimeout
 329  	if customTimeout := os.Getenv(CustomClientTimeoutEnvVar); customTimeout != "" {
 330  		if timeInSeconds, err := strconv.Atoi(customTimeout); err != nil || timeInSeconds < 0 {
 331  			Logf("WARNING: %s set but could not be converted to a postive integer", CustomClientTimeoutEnvVar)
 332  		} else {
 333  			Debugf("Using custom client timeout of %s seconds", customTimeout)
 334  			clientTimeout = time.Duration(timeInSeconds) * time.Second
 335  		}
 336  	}
 337  
 338  	// Create the underlying HTTP client
 339  	httpClient = http.Client{
 340  		Timeout:   clientTimeout,
 341  		Transport: tp,
 342  	}
 343  	return httpClient
 344  }
 345  
 346  func defaultBaseClient(provider KeyProvider) BaseClient {
 347  	dispatcher := defaultHTTPDispatcher()
 348  	signer := DefaultRequestSigner(provider)
 349  	return newBaseClient(signer, &dispatcher)
 350  }
 351  
 352  // DefaultBaseClientWithSigner creates a default base client with a given signer
 353  func DefaultBaseClientWithSigner(signer HTTPRequestSigner) BaseClient {
 354  	dispatcher := defaultHTTPDispatcher()
 355  	return newBaseClient(signer, &dispatcher)
 356  }
 357  
 358  // NewClientWithConfig Create a new client with a configuration provider, the configuration provider
 359  // will be used for the default signer as well as reading the region
 360  // This function does not check for valid regions to implement forward compatibility
 361  func NewClientWithConfig(configProvider ConfigurationProvider) (client BaseClient, err error) {
 362  	var ok bool
 363  	if ok, err = IsConfigurationProviderValid(configProvider); !ok {
 364  		err = fmt.Errorf("can not create client, bad configuration: %s", err.Error())
 365  		return
 366  	}
 367  
 368  	client = defaultBaseClient(configProvider)
 369  
 370  	if authConfig, e := configProvider.AuthType(); e == nil && authConfig.OboToken != nil {
 371  		Debugf("authConfig's authType is %s, and token content is %s", authConfig.AuthType, *authConfig.OboToken)
 372  		signOboToken(&client, *authConfig.OboToken, configProvider)
 373  	}
 374  
 375  	return
 376  }
 377  
 378  // NewClientWithOboToken Create a new client that will use oboToken for auth
 379  func NewClientWithOboToken(configProvider ConfigurationProvider, oboToken string) (client BaseClient, err error) {
 380  	client, err = NewClientWithConfig(configProvider)
 381  	if err != nil {
 382  		return
 383  	}
 384  
 385  	signOboToken(&client, oboToken, configProvider)
 386  
 387  	return
 388  }
 389  
 390  // Add obo token header to Interceptor and sign to client
 391  func signOboToken(client *BaseClient, oboToken string, configProvider ConfigurationProvider) {
 392  	// Interceptor to add obo token header
 393  	client.Interceptor = func(request *http.Request) error {
 394  		request.Header.Add(requestHeaderOpcOboToken, oboToken)
 395  		return nil
 396  	}
 397  	// Obo token will also be signed
 398  	defaultHeaders := append(DefaultGenericHeaders(), requestHeaderOpcOboToken)
 399  	client.Signer = RequestSigner(configProvider, defaultHeaders, DefaultBodyHeaders())
 400  }
 401  
 402  func getHomeFolder() string {
 403  	current, e := user.Current()
 404  	if e != nil {
 405  		//Give up and try to return something sensible
 406  		home := os.Getenv("HOME")
 407  		if home == "" {
 408  			home = os.Getenv("USERPROFILE")
 409  		}
 410  		return home
 411  	}
 412  	return current.HomeDir
 413  }
 414  
 415  // DefaultConfigProvider returns the default config provider. The default config provider
 416  // will look for configurations in 3 places: file in $HOME/.oci/config, HOME/.obmcs/config and
 417  // variables names starting with the string TF_VAR. If the same configuration is found in multiple
 418  // places the provider will prefer the first one.
 419  // If the config file is not placed in the default location, the environment variable
 420  // OCI_CONFIG_FILE can provide the config file location.
 421  func DefaultConfigProvider() ConfigurationProvider {
 422  	defaultConfigFile := getDefaultConfigFilePath()
 423  	homeFolder := getHomeFolder()
 424  	secondaryConfigFile := filepath.Join(homeFolder, secondaryConfigDirName, defaultConfigFileName)
 425  
 426  	defaultFileProvider, _ := ConfigurationProviderFromFile(defaultConfigFile, "")
 427  	secondaryFileProvider, _ := ConfigurationProviderFromFile(secondaryConfigFile, "")
 428  	environmentProvider := environmentConfigurationProvider{EnvironmentVariablePrefix: "TF_VAR"}
 429  
 430  	provider, _ := ComposingConfigurationProvider([]ConfigurationProvider{defaultFileProvider, secondaryFileProvider, environmentProvider})
 431  	Debugf("Configuration provided by: %s", provider)
 432  	return provider
 433  }
 434  
 435  // CustomProfileSessionTokenConfigProvider returns the session token config provider of the given profile.
 436  // This will look for the configuration in the given config file path.
 437  func CustomProfileSessionTokenConfigProvider(customConfigPath string, profile string) ConfigurationProvider {
 438  	if customConfigPath == "" {
 439  		customConfigPath = getDefaultConfigFilePath()
 440  	}
 441  
 442  	sessionTokenConfigurationProvider, _ := ConfigurationProviderForSessionTokenWithProfile(customConfigPath, profile, "")
 443  	Debugf("Configuration provided by: %s", sessionTokenConfigurationProvider)
 444  	return sessionTokenConfigurationProvider
 445  }
 446  
 447  func getDefaultConfigFilePath() string {
 448  	homeFolder := getHomeFolder()
 449  	defaultConfigFile := filepath.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
 450  	if _, err := os.Stat(defaultConfigFile); err == nil {
 451  		return defaultConfigFile
 452  	}
 453  	Debugf("The %s does not exist, will check env var %s for file path.", defaultConfigFile, configFilePathEnvVarName)
 454  	// Read configuration file path from OCI_CONFIG_FILE env var
 455  	fallbackConfigFile, existed := os.LookupEnv(configFilePathEnvVarName)
 456  	if !existed {
 457  		Debugf("The env var %s does not exist...", configFilePathEnvVarName)
 458  		return defaultConfigFile
 459  	}
 460  	if _, err := os.Stat(fallbackConfigFile); os.IsNotExist(err) {
 461  		Debugf("The specified cfg file path in the env var %s does not exist: %s", configFilePathEnvVarName, fallbackConfigFile)
 462  		return defaultConfigFile
 463  	}
 464  	return fallbackConfigFile
 465  }
 466  
 467  // setRawPath sets the Path and RawPath fields of the URL based on the provided
 468  // escaped path p. It maintains the invariant that RawPath is only specified
 469  // when it differs from the default encoding of the path.
 470  // For example:
 471  // - setPath("/foo/bar")   will set Path="/foo/bar" and RawPath=""
 472  // - setPath("/foo%2fbar") will set Path="/foo/bar" and RawPath="/foo%2fbar"
 473  func setRawPath(u *url.URL) error {
 474  	oldPath := u.Path
 475  	path, err := url.PathUnescape(u.Path)
 476  	if err != nil {
 477  		return err
 478  	}
 479  	u.Path = path
 480  	if escp := u.EscapedPath(); oldPath == escp {
 481  		// Default encoding is fine.
 482  		u.RawPath = ""
 483  	} else {
 484  		u.RawPath = oldPath
 485  	}
 486  	return nil
 487  }
 488  
 489  // CustomProfileConfigProvider returns the config provider of given profile. The custom profile config provider
 490  // will look for configurations in 2 places: file in $HOME/.oci/config,  and variables names starting with the
 491  // string TF_VAR. If the same configuration is found in multiple places the provider will prefer the first one.
 492  func CustomProfileConfigProvider(customConfigPath string, profile string) ConfigurationProvider {
 493  	homeFolder := getHomeFolder()
 494  	if customConfigPath == "" {
 495  		customConfigPath = filepath.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
 496  	}
 497  	customFileProvider, _ := ConfigurationProviderFromFileWithProfile(customConfigPath, profile, "")
 498  	defaultFileProvider, _ := ConfigurationProviderFromFileWithProfile(customConfigPath, "DEFAULT", "")
 499  	environmentProvider := environmentConfigurationProvider{EnvironmentVariablePrefix: "TF_VAR"}
 500  	provider, _ := ComposingConfigurationProvider([]ConfigurationProvider{customFileProvider, defaultFileProvider, environmentProvider})
 501  	Debugf("Configuration provided by: %s", provider)
 502  	return provider
 503  }
 504  
 505  func (client *BaseClient) prepareRequest(request *http.Request) (err error) {
 506  	if client.UserAgent == "" {
 507  		return fmt.Errorf("user agent can not be blank")
 508  	}
 509  
 510  	if request.Header == nil {
 511  		request.Header = http.Header{}
 512  	}
 513  	request.Header.Set(requestHeaderUserAgent, client.UserAgent)
 514  	request.Header.Set(requestHeaderDate, time.Now().UTC().Format(http.TimeFormat))
 515  
 516  	if !strings.Contains(client.Host, "http") &&
 517  		!strings.Contains(client.Host, "https") {
 518  		client.Host = fmt.Sprintf("%s://%s", defaultScheme, client.Host)
 519  	}
 520  
 521  	clientURL, err := url.Parse(client.Host)
 522  	if err != nil {
 523  		return fmt.Errorf("host is invalid. %s", err.Error())
 524  	}
 525  	request.URL.Host = clientURL.Host
 526  	request.URL.Scheme = clientURL.Scheme
 527  	currentPath := request.URL.Path
 528  	if !strings.Contains(currentPath, fmt.Sprintf("/%s", client.BasePath)) {
 529  		request.URL.Path = path.Clean(fmt.Sprintf("/%s/%s", client.BasePath, currentPath))
 530  		err := setRawPath(request.URL)
 531  		if err != nil {
 532  			return err
 533  		}
 534  	}
 535  	return
 536  }
 537  
 538  func (client BaseClient) intercept(request *http.Request) (err error) {
 539  	if client.Interceptor != nil {
 540  		err = client.Interceptor(request)
 541  	}
 542  	return
 543  }
 544  
 545  // checkForSuccessfulResponse checks if the response is successful
 546  // If Error Code is 4XX/5XX and debug level is set to info, will log the request and response
 547  func checkForSuccessfulResponse(res *http.Response, requestBody *io.ReadCloser) error {
 548  	familyStatusCode := res.StatusCode / 100
 549  	if familyStatusCode == 4 || familyStatusCode == 5 {
 550  		IfInfo(func() {
 551  			// If debug level is set to verbose, the request and request body will be dumped and logged under debug level, this is to avoid duplicate logging
 552  			if defaultLogger.LogLevel() < verboseLogging {
 553  				logRequest(res.Request, Logf, noLogging)
 554  				if requestBody != nil && *requestBody != http.NoBody {
 555  					bodyContent, _ := ioutil.ReadAll(*requestBody)
 556  					Logf("Dump Request Body: \n%s", string(bodyContent))
 557  				}
 558  			}
 559  			logResponse(res, Logf, infoLogging)
 560  		})
 561  		return newServiceFailureFromResponse(res)
 562  	}
 563  	IfDebug(func() {
 564  		logResponse(res, Debugf, verboseLogging)
 565  	})
 566  	return nil
 567  }
 568  
 569  func logRequest(request *http.Request, fn func(format string, v ...interface{}), bodyLoggingLevel int) {
 570  	if request == nil {
 571  		return
 572  	}
 573  	dumpBody := true
 574  	if checkBodyLengthExceedLimit(request.ContentLength) {
 575  		fn("not dumping body too big\n")
 576  		dumpBody = false
 577  	}
 578  
 579  	dumpBody = dumpBody && defaultLogger.LogLevel() >= bodyLoggingLevel && bodyLoggingLevel != noLogging
 580  	if dump, e := httputil.DumpRequestOut(request, dumpBody); e == nil {
 581  		fn("Dump Request %s", string(dump))
 582  	} else {
 583  		fn("%v\n", e)
 584  	}
 585  }
 586  
 587  func logResponse(response *http.Response, fn func(format string, v ...interface{}), bodyLoggingLevel int) {
 588  	if response == nil {
 589  		return
 590  	}
 591  	dumpBody := true
 592  	if checkBodyLengthExceedLimit(response.ContentLength) {
 593  		fn("not dumping body too big\n")
 594  		dumpBody = false
 595  	}
 596  	dumpBody = dumpBody && defaultLogger.LogLevel() >= bodyLoggingLevel && bodyLoggingLevel != noLogging
 597  	if dump, e := httputil.DumpResponse(response, dumpBody); e == nil {
 598  		fn("Dump Response %s", string(dump))
 599  	} else {
 600  		fn("%v\n", e)
 601  	}
 602  }
 603  
 604  func checkBodyLengthExceedLimit(contentLength int64) bool {
 605  	return contentLength > maxBodyLenForDebug
 606  }
 607  
 608  // OCIRequest is any request made to an OCI service.
 609  type OCIRequest interface {
 610  	// HTTPRequest assembles an HTTP request.
 611  	HTTPRequest(method, path string, binaryRequestBody *OCIReadSeekCloser, extraHeaders map[string]string) (http.Request, error)
 612  }
 613  
 614  // RequestMetadata is metadata about an OCIRequest. This structure represents the behavior exhibited by the SDK when
 615  // issuing (or reissuing) a request.
 616  type RequestMetadata struct {
 617  	// RetryPolicy is the policy for reissuing the request. If no retry policy is set on the request,
 618  	// then the request will be issued exactly once.
 619  	RetryPolicy *RetryPolicy
 620  }
 621  
 622  // OCIReadSeekCloser is a thread-safe io.ReadSeekCloser to prevent racing with retrying binary requests
 623  type OCIReadSeekCloser struct {
 624  	rc       io.ReadCloser
 625  	lock     sync.Mutex
 626  	isClosed bool
 627  }
 628  
 629  // NewOCIReadSeekCloser constructs OCIReadSeekCloser, the only input is binary request body
 630  func NewOCIReadSeekCloser(rc io.ReadCloser) *OCIReadSeekCloser {
 631  	rsc := OCIReadSeekCloser{}
 632  	rsc.rc = rc
 633  	return &rsc
 634  }
 635  
 636  // Seek is a thread-safe operation, it implements io.seek() interface, if the original request body implements io.seek()
 637  // interface, or implements "well-known" data type like os.File, io.SectionReader, or wrapped by ioutil.NopCloser can be supported
 638  func (rsc *OCIReadSeekCloser) Seek(offset int64, whence int) (int64, error) {
 639  	rsc.lock.Lock()
 640  	defer rsc.lock.Unlock()
 641  
 642  	if _, ok := rsc.rc.(io.Seeker); ok {
 643  		return rsc.rc.(io.Seeker).Seek(offset, whence)
 644  	}
 645  	// once the binary request body is wrapped with ioutil.NopCloser:
 646  	if isNopCloser(rsc.rc) {
 647  		unwrappedInterface := reflect.ValueOf(rsc.rc).Field(0).Interface()
 648  		if _, ok := unwrappedInterface.(io.Seeker); ok {
 649  			return unwrappedInterface.(io.Seeker).Seek(offset, whence)
 650  		}
 651  	}
 652  	return 0, fmt.Errorf("current binary request body type is not seekable, if want to use retry feature, please make sure the request body implements seek() method")
 653  }
 654  
 655  // Close is a thread-safe operation, it closes the instance of the OCIReadSeekCloser's access to the underlying io.ReadCloser.
 656  func (rsc *OCIReadSeekCloser) Close() error {
 657  	rsc.lock.Lock()
 658  	defer rsc.lock.Unlock()
 659  	rsc.isClosed = true
 660  	return nil
 661  }
 662  
 663  // Read is a thread-safe operation, it implements io.Read() interface
 664  func (rsc *OCIReadSeekCloser) Read(p []byte) (n int, err error) {
 665  	rsc.lock.Lock()
 666  	defer rsc.lock.Unlock()
 667  
 668  	if rsc.isClosed {
 669  		return 0, io.EOF
 670  	}
 671  
 672  	return rsc.rc.Read(p)
 673  }
 674  
 675  // Seekable is used for check if the binary request body can be seek or no
 676  func (rsc *OCIReadSeekCloser) Seekable() bool {
 677  	if rsc == nil {
 678  		return false
 679  	}
 680  	if _, ok := rsc.rc.(io.Seeker); ok {
 681  		return true
 682  	}
 683  	// once the binary request body is wrapped with ioutil.NopCloser:
 684  	if isNopCloser(rsc.rc) {
 685  		if _, ok := reflect.ValueOf(rsc.rc).Field(0).Interface().(io.Seeker); ok {
 686  			return true
 687  		}
 688  	}
 689  	return false
 690  }
 691  
 692  // OCIResponse is the response from issuing a request to an OCI service.
 693  type OCIResponse interface {
 694  	// HTTPResponse returns the raw HTTP response.
 695  	HTTPResponse() *http.Response
 696  }
 697  
 698  // OCIOperation is the generalization of a request-response cycle undergone by an OCI service.
 699  type OCIOperation func(context.Context, OCIRequest, *OCIReadSeekCloser, map[string]string) (OCIResponse, error)
 700  
 701  // ClientCallDetails a set of settings used by the a single Call operation of the http Client
 702  type ClientCallDetails struct {
 703  	Signer HTTPRequestSigner
 704  }
 705  
 706  // Call executes the http request with the given context
 707  func (client BaseClient) Call(ctx context.Context, request *http.Request) (response *http.Response, err error) {
 708  	if client.IsRefreshableAuthType() {
 709  		return client.RefreshableTokenWrappedCallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
 710  	}
 711  	return client.CallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
 712  }
 713  
 714  // RefreshableTokenWrappedCallWithDetails wraps the CallWithDetails with retry on 401 for Refreshable Toekn (Instance Principal, Resource Principal etc.)
 715  // This is to intimitate the race condition on refresh
 716  func (client BaseClient) RefreshableTokenWrappedCallWithDetails(ctx context.Context, request *http.Request, details ClientCallDetails) (response *http.Response, err error) {
 717  	for i := 0; i < maxAttemptsForRefreshableRetry; i++ {
 718  		response, err = client.CallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
 719  		if response != nil && response.StatusCode != 401 {
 720  			return response, err
 721  		}
 722  		time.Sleep(1 * time.Second)
 723  	}
 724  	return
 725  }
 726  
 727  // CallWithDetails executes the http request, the given context using details specified in the parameters, this function
 728  // provides a way to override some settings present in the client
 729  func (client BaseClient) CallWithDetails(ctx context.Context, request *http.Request, details ClientCallDetails) (response *http.Response, err error) {
 730  	Debugln("Attempting to call downstream service")
 731  	request = request.WithContext(ctx)
 732  	err = client.prepareRequest(request)
 733  	if err != nil {
 734  		return
 735  	}
 736  	//Intercept
 737  	err = client.intercept(request)
 738  	if err != nil {
 739  		return
 740  	}
 741  	//Sign the request
 742  	err = details.Signer.Sign(request)
 743  	if err != nil {
 744  		return
 745  	}
 746  
 747  	//Execute the http request
 748  	if ociGoBreaker := client.Configuration.CircuitBreaker; ociGoBreaker != nil {
 749  		resp, cbErr := ociGoBreaker.Cb.Execute(func() (interface{}, error) {
 750  			return client.httpDo(request)
 751  		})
 752  		if httpResp, ok := resp.(*http.Response); ok {
 753  			if httpResp != nil && httpResp.StatusCode != 200 {
 754  				if failure, ok := IsServiceError(cbErr); ok {
 755  					ociGoBreaker.AddToHistory(resp.(*http.Response), failure)
 756  				}
 757  			}
 758  		}
 759  		if cbErr != nil && IsCircuitBreakerError(cbErr) {
 760  			cbErr = getCircuitBreakerError(request, cbErr, ociGoBreaker)
 761  		}
 762  		if _, ok := resp.(*http.Response); !ok {
 763  			return nil, cbErr
 764  		}
 765  		return resp.(*http.Response), cbErr
 766  	}
 767  	return client.httpDo(request)
 768  }
 769  
 770  // IsRefreshableAuthType validates if a signer is from a refreshable config provider
 771  func (client BaseClient) IsRefreshableAuthType() bool {
 772  	if signer, ok := client.Signer.(ociRequestSigner); ok {
 773  		if provider, ok := signer.KeyProvider.(RefreshableConfigurationProvider); ok {
 774  			return provider.Refreshable()
 775  		}
 776  	}
 777  	return false
 778  }
 779  
 780  func (client BaseClient) httpDo(request *http.Request) (response *http.Response, err error) {
 781  
 782  	//Copy request body and save for logging
 783  	dumpRequestBody := ioutil.NopCloser(bytes.NewBuffer(nil))
 784  	if request.Body != nil && !checkBodyLengthExceedLimit(request.ContentLength) {
 785  		if dumpRequestBody, request.Body, err = drainBody(request.Body); err != nil {
 786  			dumpRequestBody = ioutil.NopCloser(bytes.NewBuffer(nil))
 787  		}
 788  	}
 789  	IfDebug(func() {
 790  		logRequest(request, Debugf, verboseLogging)
 791  	})
 792  
 793  	//Execute the http request
 794  	response, err = client.HTTPClient.Do(request)
 795  
 796  	if err != nil {
 797  		IfInfo(func() {
 798  			Logf("%v\n", err)
 799  		})
 800  		return response, err
 801  	}
 802  
 803  	err = checkForSuccessfulResponse(response, &dumpRequestBody)
 804  	return response, err
 805  }
 806  
 807  // CloseBodyIfValid closes the body of an http response if the response and the body are valid
 808  func CloseBodyIfValid(httpResponse *http.Response) {
 809  	if httpResponse != nil && httpResponse.Body != nil {
 810  		if httpResponse.Header != nil && strings.ToLower(httpResponse.Header.Get("content-type")) == "text/event-stream" {
 811  			return
 812  		}
 813  		httpResponse.Body.Close()
 814  	}
 815  }
 816  
 817  // IsOciRealmSpecificServiceEndpointTemplateEnabled returns true if the client is configured to use realm specific service endpoint template
 818  // it will first check the client configuration, if not set, it will check the environment variable
 819  func (client BaseClient) IsOciRealmSpecificServiceEndpointTemplateEnabled() bool {
 820  	if client.Configuration.RealmSpecificServiceEndpointTemplateEnabled != nil {
 821  		return *client.Configuration.RealmSpecificServiceEndpointTemplateEnabled
 822  	}
 823  	return IsEnvVarTrue(OciRealmSpecificServiceEndpointTemplateEnabledEnvVar)
 824  }
 825  
 826  func getCustomCertRefreshInterval() int {
 827  	if OciGlobalRefreshIntervalForCustomCerts >= 0 {
 828  		Debugf("Setting refresh interval as %d for custom certs via OciGlobalRefreshIntervalForCustomCerts", OciGlobalRefreshIntervalForCustomCerts)
 829  		return OciGlobalRefreshIntervalForCustomCerts
 830  	}
 831  	if refreshIntervalValue, ok := os.LookupEnv(ociDefaultRefreshIntervalForCustomCerts); ok {
 832  		refreshInterval, err := strconv.Atoi(refreshIntervalValue)
 833  		if err != nil || refreshInterval < 0 {
 834  			Debugf("The environment variable %s is not a valid int or is a negative value, skipping this configuration", ociDefaultRefreshIntervalForCustomCerts)
 835  		} else {
 836  			Debugf("Setting refresh interval as %d for custom certs via the env variable %s", refreshInterval, ociDefaultRefreshIntervalForCustomCerts)
 837  			return refreshInterval
 838  		}
 839  	}
 840  	Debugf("Setting the default refresh interval %d for custom certs", defaultRefreshIntervalForCustomCerts)
 841  	return defaultRefreshIntervalForCustomCerts
 842  }
 843