client.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. // Copyright (c) 2016, 2018, 2020, 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. // Package common provides supporting functions and structs used by service packages
  4. package common
  5. import (
  6. "context"
  7. "fmt"
  8. "math/rand"
  9. "net/http"
  10. "net/http/httputil"
  11. "net/url"
  12. "os"
  13. "os/user"
  14. "path"
  15. "runtime"
  16. "strings"
  17. "sync/atomic"
  18. "time"
  19. )
  20. const (
  21. // DefaultHostURLTemplate The default url template for service hosts
  22. DefaultHostURLTemplate = "%s.%s.oraclecloud.com"
  23. // requestHeaderAccept The key for passing a header to indicate Accept
  24. requestHeaderAccept = "Accept"
  25. // requestHeaderAuthorization The key for passing a header to indicate Authorization
  26. requestHeaderAuthorization = "Authorization"
  27. // requestHeaderContentLength The key for passing a header to indicate Content Length
  28. requestHeaderContentLength = "Content-Length"
  29. // requestHeaderContentType The key for passing a header to indicate Content Type
  30. requestHeaderContentType = "Content-Type"
  31. // requestHeaderDate The key for passing a header to indicate Date
  32. requestHeaderDate = "Date"
  33. // requestHeaderIfMatch The key for passing a header to indicate If Match
  34. requestHeaderIfMatch = "if-match"
  35. // requestHeaderOpcClientInfo The key for passing a header to indicate OPC Client Info
  36. requestHeaderOpcClientInfo = "opc-client-info"
  37. // requestHeaderOpcRetryToken The key for passing a header to indicate OPC Retry Token
  38. requestHeaderOpcRetryToken = "opc-retry-token"
  39. // requestHeaderOpcRequestID The key for unique Oracle-assigned identifier for the request.
  40. requestHeaderOpcRequestID = "opc-request-id"
  41. // requestHeaderOpcClientRequestID The key for unique Oracle-assigned identifier for the request.
  42. requestHeaderOpcClientRequestID = "opc-client-request-id"
  43. // requestHeaderUserAgent The key for passing a header to indicate User Agent
  44. requestHeaderUserAgent = "User-Agent"
  45. // requestHeaderXContentSHA256 The key for passing a header to indicate SHA256 hash
  46. requestHeaderXContentSHA256 = "X-Content-SHA256"
  47. // requestHeaderOpcOboToken The key for passing a header to use obo token
  48. requestHeaderOpcOboToken = "opc-obo-token"
  49. // private constants
  50. defaultScheme = "https"
  51. defaultSDKMarker = "Oracle-GoSDK"
  52. defaultUserAgentTemplate = "%s/%s (%s/%s; go/%s)" //SDK/SDKVersion (OS/OSVersion; Lang/LangVersion)
  53. defaultTimeout = 60 * time.Second
  54. defaultConfigFileName = "config"
  55. defaultConfigDirName = ".oci"
  56. configFilePathEnvVarName = "OCI_CONFIG_FILE"
  57. secondaryConfigDirName = ".oraclebmc"
  58. maxBodyLenForDebug = 1024 * 1000
  59. )
  60. // RequestInterceptor function used to customize the request before calling the underlying service
  61. type RequestInterceptor func(*http.Request) error
  62. // HTTPRequestDispatcher wraps the execution of a http request, it is generally implemented by
  63. // http.Client.Do, but can be customized for testing
  64. type HTTPRequestDispatcher interface {
  65. Do(req *http.Request) (*http.Response, error)
  66. }
  67. // BaseClient struct implements all basic operations to call oci web services.
  68. type BaseClient struct {
  69. //HTTPClient performs the http network operations
  70. HTTPClient HTTPRequestDispatcher
  71. //Signer performs auth operation
  72. Signer HTTPRequestSigner
  73. //A request interceptor can be used to customize the request before signing and dispatching
  74. Interceptor RequestInterceptor
  75. //The host of the service
  76. Host string
  77. //The user agent
  78. UserAgent string
  79. //Base path for all operations of this client
  80. BasePath string
  81. }
  82. func defaultUserAgent() string {
  83. userAgent := fmt.Sprintf(defaultUserAgentTemplate, defaultSDKMarker, Version(), runtime.GOOS, runtime.GOARCH, runtime.Version())
  84. return userAgent
  85. }
  86. var clientCounter int64
  87. func getNextSeed() int64 {
  88. newCounterValue := atomic.AddInt64(&clientCounter, 1)
  89. return newCounterValue + time.Now().UnixNano()
  90. }
  91. func newBaseClient(signer HTTPRequestSigner, dispatcher HTTPRequestDispatcher) BaseClient {
  92. rand.Seed(getNextSeed())
  93. return BaseClient{
  94. UserAgent: defaultUserAgent(),
  95. Interceptor: nil,
  96. Signer: signer,
  97. HTTPClient: dispatcher,
  98. }
  99. }
  100. func defaultHTTPDispatcher() http.Client {
  101. httpClient := http.Client{
  102. Timeout: defaultTimeout,
  103. }
  104. return httpClient
  105. }
  106. func defaultBaseClient(provider KeyProvider) BaseClient {
  107. dispatcher := defaultHTTPDispatcher()
  108. signer := DefaultRequestSigner(provider)
  109. return newBaseClient(signer, &dispatcher)
  110. }
  111. //DefaultBaseClientWithSigner creates a default base client with a given signer
  112. func DefaultBaseClientWithSigner(signer HTTPRequestSigner) BaseClient {
  113. dispatcher := defaultHTTPDispatcher()
  114. return newBaseClient(signer, &dispatcher)
  115. }
  116. // NewClientWithConfig Create a new client with a configuration provider, the configuration provider
  117. // will be used for the default signer as well as reading the region
  118. // This function does not check for valid regions to implement forward compatibility
  119. func NewClientWithConfig(configProvider ConfigurationProvider) (client BaseClient, err error) {
  120. var ok bool
  121. if ok, err = IsConfigurationProviderValid(configProvider); !ok {
  122. err = fmt.Errorf("can not create client, bad configuration: %s", err.Error())
  123. return
  124. }
  125. client = defaultBaseClient(configProvider)
  126. return
  127. }
  128. // NewClientWithOboToken Create a new client that will use oboToken for auth
  129. func NewClientWithOboToken(configProvider ConfigurationProvider, oboToken string) (client BaseClient, err error) {
  130. client, err = NewClientWithConfig(configProvider)
  131. if err != nil {
  132. return
  133. }
  134. // Interceptor to add obo token header
  135. client.Interceptor = func(request *http.Request) error {
  136. request.Header.Add(requestHeaderOpcOboToken, oboToken)
  137. return nil
  138. }
  139. // Obo token will also be signed
  140. defaultHeaders := append(DefaultGenericHeaders(), requestHeaderOpcOboToken)
  141. client.Signer = RequestSigner(configProvider, defaultHeaders, DefaultBodyHeaders())
  142. return
  143. }
  144. func getHomeFolder() string {
  145. current, e := user.Current()
  146. if e != nil {
  147. //Give up and try to return something sensible
  148. home := os.Getenv("HOME")
  149. if home == "" {
  150. home = os.Getenv("USERPROFILE")
  151. }
  152. return home
  153. }
  154. return current.HomeDir
  155. }
  156. // DefaultConfigProvider returns the default config provider. The default config provider
  157. // will look for configurations in 3 places: file in $HOME/.oci/config, HOME/.obmcs/config and
  158. // variables names starting with the string TF_VAR. If the same configuration is found in multiple
  159. // places the provider will prefer the first one.
  160. // If the config file is not placed in the default location, the environment variable
  161. // OCI_CONFIG_FILE can provide the config file location.
  162. func DefaultConfigProvider() ConfigurationProvider {
  163. defaultConfigFile := getDefaultConfigFilePath()
  164. homeFolder := getHomeFolder()
  165. secondaryConfigFile := path.Join(homeFolder, secondaryConfigDirName, defaultConfigFileName)
  166. defaultFileProvider, _ := ConfigurationProviderFromFile(defaultConfigFile, "")
  167. secondaryFileProvider, _ := ConfigurationProviderFromFile(secondaryConfigFile, "")
  168. environmentProvider := environmentConfigurationProvider{EnvironmentVariablePrefix: "TF_VAR"}
  169. provider, _ := ComposingConfigurationProvider([]ConfigurationProvider{defaultFileProvider, secondaryFileProvider, environmentProvider})
  170. Debugf("Configuration provided by: %s", provider)
  171. return provider
  172. }
  173. func getDefaultConfigFilePath() string {
  174. homeFolder := getHomeFolder()
  175. defaultConfigFile := path.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
  176. if _, err := os.Stat(defaultConfigFile); err == nil {
  177. return defaultConfigFile
  178. }
  179. Debugf("The %s does not exist, will check env var %s for file path.", defaultConfigFile, configFilePathEnvVarName)
  180. // Read configuration file path from OCI_CONFIG_FILE env var
  181. fallbackConfigFile, existed := os.LookupEnv(configFilePathEnvVarName)
  182. if !existed {
  183. Debugf("The env var %s does not exist...", configFilePathEnvVarName)
  184. return defaultConfigFile
  185. }
  186. if _, err := os.Stat(fallbackConfigFile); os.IsNotExist(err) {
  187. Debugf("The specified cfg file path in the env var %s does not exist: %s", configFilePathEnvVarName, fallbackConfigFile)
  188. return defaultConfigFile
  189. }
  190. return fallbackConfigFile
  191. }
  192. // CustomProfileConfigProvider returns the config provider of given profile. The custom profile config provider
  193. // will look for configurations in 2 places: file in $HOME/.oci/config, and variables names starting with the
  194. // string TF_VAR. If the same configuration is found in multiple places the provider will prefer the first one.
  195. func CustomProfileConfigProvider(customConfigPath string, profile string) ConfigurationProvider {
  196. homeFolder := getHomeFolder()
  197. if customConfigPath == "" {
  198. customConfigPath = path.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
  199. }
  200. customFileProvider, _ := ConfigurationProviderFromFileWithProfile(customConfigPath, profile, "")
  201. defaultFileProvider, _ := ConfigurationProviderFromFileWithProfile(customConfigPath, "DEFAULT", "")
  202. environmentProvider := environmentConfigurationProvider{EnvironmentVariablePrefix: "TF_VAR"}
  203. provider, _ := ComposingConfigurationProvider([]ConfigurationProvider{customFileProvider, defaultFileProvider, environmentProvider})
  204. Debugf("Configuration provided by: %s", provider)
  205. return provider
  206. }
  207. func (client *BaseClient) prepareRequest(request *http.Request) (err error) {
  208. if client.UserAgent == "" {
  209. return fmt.Errorf("user agent can not be blank")
  210. }
  211. if request.Header == nil {
  212. request.Header = http.Header{}
  213. }
  214. request.Header.Set(requestHeaderUserAgent, client.UserAgent)
  215. request.Header.Set(requestHeaderDate, time.Now().UTC().Format(http.TimeFormat))
  216. if !strings.Contains(client.Host, "http") &&
  217. !strings.Contains(client.Host, "https") {
  218. client.Host = fmt.Sprintf("%s://%s", defaultScheme, client.Host)
  219. }
  220. clientURL, err := url.Parse(client.Host)
  221. if err != nil {
  222. return fmt.Errorf("host is invalid. %s", err.Error())
  223. }
  224. request.URL.Host = clientURL.Host
  225. request.URL.Scheme = clientURL.Scheme
  226. currentPath := request.URL.Path
  227. if !strings.Contains(currentPath, fmt.Sprintf("/%s", client.BasePath)) {
  228. request.URL.Path = path.Clean(fmt.Sprintf("/%s/%s", client.BasePath, currentPath))
  229. }
  230. return
  231. }
  232. func (client BaseClient) intercept(request *http.Request) (err error) {
  233. if client.Interceptor != nil {
  234. err = client.Interceptor(request)
  235. }
  236. return
  237. }
  238. func checkForSuccessfulResponse(res *http.Response) error {
  239. familyStatusCode := res.StatusCode / 100
  240. if familyStatusCode == 4 || familyStatusCode == 5 {
  241. return newServiceFailureFromResponse(res)
  242. }
  243. return nil
  244. }
  245. // OCIRequest is any request made to an OCI service.
  246. type OCIRequest interface {
  247. // HTTPRequest assembles an HTTP request.
  248. HTTPRequest(method, path string) (http.Request, error)
  249. }
  250. // RequestMetadata is metadata about an OCIRequest. This structure represents the behavior exhibited by the SDK when
  251. // issuing (or reissuing) a request.
  252. type RequestMetadata struct {
  253. // RetryPolicy is the policy for reissuing the request. If no retry policy is set on the request,
  254. // then the request will be issued exactly once.
  255. RetryPolicy *RetryPolicy
  256. }
  257. // OCIResponse is the response from issuing a request to an OCI service.
  258. type OCIResponse interface {
  259. // HTTPResponse returns the raw HTTP response.
  260. HTTPResponse() *http.Response
  261. }
  262. // OCIOperation is the generalization of a request-response cycle undergone by an OCI service.
  263. type OCIOperation func(context.Context, OCIRequest) (OCIResponse, error)
  264. //ClientCallDetails a set of settings used by the a single Call operation of the http Client
  265. type ClientCallDetails struct {
  266. Signer HTTPRequestSigner
  267. }
  268. // Call executes the http request with the given context
  269. func (client BaseClient) Call(ctx context.Context, request *http.Request) (response *http.Response, err error) {
  270. return client.CallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
  271. }
  272. // CallWithDetails executes the http request, the given context using details specified in the paremeters, this function
  273. // provides a way to override some settings present in the client
  274. func (client BaseClient) CallWithDetails(ctx context.Context, request *http.Request, details ClientCallDetails) (response *http.Response, err error) {
  275. Debugln("Atempting to call downstream service")
  276. request = request.WithContext(ctx)
  277. err = client.prepareRequest(request)
  278. if err != nil {
  279. return
  280. }
  281. //Intercept
  282. err = client.intercept(request)
  283. if err != nil {
  284. return
  285. }
  286. //Sign the request
  287. err = details.Signer.Sign(request)
  288. if err != nil {
  289. return
  290. }
  291. IfDebug(func() {
  292. dumpBody := true
  293. if request.ContentLength > maxBodyLenForDebug {
  294. Debugf("not dumping body too big\n")
  295. dumpBody = false
  296. }
  297. dumpBody = dumpBody && defaultLogger.LogLevel() == verboseLogging
  298. if dump, e := httputil.DumpRequestOut(request, dumpBody); e == nil {
  299. Debugf("Dump Request %s", string(dump))
  300. } else {
  301. Debugf("%v\n", e)
  302. }
  303. })
  304. //Execute the http request
  305. response, err = client.HTTPClient.Do(request)
  306. IfDebug(func() {
  307. if err != nil {
  308. Debugf("%v\n", err)
  309. return
  310. }
  311. dumpBody := true
  312. if response.ContentLength > maxBodyLenForDebug {
  313. Debugf("not dumping body too big\n")
  314. dumpBody = false
  315. }
  316. dumpBody = dumpBody && defaultLogger.LogLevel() == verboseLogging
  317. if dump, e := httputil.DumpResponse(response, dumpBody); e == nil {
  318. Debugf("Dump Response %s", string(dump))
  319. } else {
  320. Debugf("%v\n", e)
  321. }
  322. })
  323. if err != nil {
  324. return
  325. }
  326. err = checkForSuccessfulResponse(response)
  327. return
  328. }
  329. //CloseBodyIfValid closes the body of an http response if the response and the body are valid
  330. func CloseBodyIfValid(httpResponse *http.Response) {
  331. if httpResponse != nil && httpResponse.Body != nil {
  332. httpResponse.Body.Close()
  333. }
  334. }