oci 2.0.9 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +179 -10
  3. data/lib/oci.rb +1 -0
  4. data/lib/oci/api_client.rb +186 -64
  5. data/lib/oci/api_client_proxy_settings.rb +71 -0
  6. data/lib/oci/audit/audit_client.rb +6 -4
  7. data/lib/oci/auth/signers/instance_principals_security_token_signer.rb +1 -1
  8. data/lib/oci/core/blockstorage_client.rb +18 -4
  9. data/lib/oci/core/compute_client.rb +27 -4
  10. data/lib/oci/core/core.rb +5 -0
  11. data/lib/oci/core/models/connect_remote_peering_connections_details.rb +149 -0
  12. data/lib/oci/core/models/create_remote_peering_connection_details.rb +165 -0
  13. data/lib/oci/core/models/peer_region_for_remote_peering.rb +125 -0
  14. data/lib/oci/core/models/remote_peering_connection.rb +362 -0
  15. data/lib/oci/core/models/update_remote_peering_connection_details.rb +129 -0
  16. data/lib/oci/core/virtual_network_client.rb +334 -4
  17. data/lib/oci/database/database_client.rb +41 -4
  18. data/lib/oci/database/models/db_system.rb +1 -1
  19. data/lib/oci/database/models/db_system_shape_summary.rb +74 -2
  20. data/lib/oci/database/models/db_system_summary.rb +1 -1
  21. data/lib/oci/dns/dns_client.rb +32 -4
  22. data/lib/oci/email/email_client.rb +10 -4
  23. data/lib/oci/errors.rb +115 -19
  24. data/lib/oci/file_storage/file_storage_client.rb +18 -4
  25. data/lib/oci/global_context.rb +2 -2
  26. data/lib/oci/identity/identity_client.rb +68 -4
  27. data/lib/oci/identity/models/create_region_subscription_details.rb +1 -0
  28. data/lib/oci/identity/models/region.rb +2 -0
  29. data/lib/oci/identity/models/region_subscription.rb +2 -0
  30. data/lib/oci/identity/models/tenancy.rb +3 -1
  31. data/lib/oci/internal/util.rb +5 -0
  32. data/lib/oci/load_balancer/load_balancer_client.rb +59 -4
  33. data/lib/oci/object_storage/object_storage_client.rb +62 -4
  34. data/lib/oci/regions.rb +4 -2
  35. data/lib/oci/version.rb +1 -1
  36. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 40f4fc8cef8b30940cc5d4357f659cd7da8a077c
4
- data.tar.gz: 3442d01039aaa3c13d203e8f195a3e9372487c99
3
+ metadata.gz: 710c56b90571dcc168a349f896579265e90fe8d7
4
+ data.tar.gz: 4a77c01309de5a1b22e14b123c4038c5e714f0a1
5
5
  SHA512:
6
- metadata.gz: 09d6e76897744e3911bee15dd75f154996c8654e351f6a5683139beb7012dbd1b0263c99e4a9a7de7ae7623e72a2dd656e6f49d9ceb4893491efef5ce149425d
7
- data.tar.gz: ef317dec6a0135a5dde2e9e9ca82a2a9266d2cd50594fcc1ae896012b37711db27bf27816437ddb9cfda13e8ac0c8d93a3a313e4c1ce0f63954fef7b4dd1c51d
6
+ metadata.gz: 56cda480d05e618cfb517a1b1a13bc9137e13852e789dfdcbc78fab0eedfdabe4c9be1e2a28d342ac119003da1323c987edcee78d7ebc5b15b5b5a03a01769da
7
+ data.tar.gz: c47dc6cce662d968890c7b8f5c405db3eee70f70cc5a265cca84c43d35fe8a632cdf9d8757f089c26903d00697c9713c47cf667cc1efc61e4b5d40d43ce727ec
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Oracle Cloud Infrastructure Ruby SDK
2
- **Version 2.0.9**
2
+ **Version 2.1.0**
3
3
 
4
4
  This topic describes how to install, configure, and use the Oracle Cloud Infrastructure Ruby SDK.
5
5
 
@@ -39,7 +39,24 @@ The following table provides details about some of the attributes of the SDK.
39
39
  </tr>
40
40
  <tr>
41
41
  <td>Errors</td>
42
- <td>Unsuccessful requests will always raise an exception. {OCI::Errors::ServiceError ServiceError} is raised when the service returns an error, and {OCI::Errors::NetworkError NetworkError} is raised for network issues (such as failed host resolution).</td>
42
+ <td>
43
+ The main exception classes for the SDK are:
44
+ <ul>
45
+ <li>{OCI::Errors::ServiceError ServiceError} is thrown when an OCI service returns a non-2xx HTTP status code</li>
46
+ <li>
47
+ {OCI::Errors::NetworkError NetworkError} is thrown when the issue is likely to be network-related rather than an application issue. This is defined as:
48
+ <ul>
49
+ <li>
50
+ Requests that were sent from the SDK, but for which the outcome was not a response from an OCI service. Examples include a gateway timeout, read or connection timeouts from Net::HTTP, and any (Errno) exception that was generated by the request itself.
51
+ </li>
52
+ <li>Requests that result in a HTTP 408 (timeout)</li>
53
+ </ul>
54
+ </li>
55
+ <li>
56
+ {OCI::Errors::ResponseParsingError ResponseParsingError} is thrown when a response is received from an OCI service, but the SDK could not parse it into the appropriate model type for inserting into an {OCI::Response}
57
+ </li>
58
+ </ul>
59
+ </td>
43
60
  </tr>
44
61
  <tr>
45
62
  <td>Signing</td>
@@ -55,8 +72,7 @@ The following table provides details about some of the attributes of the SDK.
55
72
  </tr>
56
73
  <tr>
57
74
  <td>HTTP Client</td>
58
- <td>The Ruby SDK uses Net::HTTP for HTTP requests, if needed, options may be passed to each Net::HTTP by specifying them in {OCI::ApiClient#request_option_overrides ApiClient.request_option_overrides}.</br>
59
- Please check http://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start for the supported options.</td>
75
+ <td>The Ruby SDK uses Net::HTTP for HTTP requests. Information on how to configure the client, including configuring a proxy, can be found in the <b>Configuring the HTTP Client</b> section of this page</td>
60
76
  </tr>
61
77
  <tr>
62
78
  <td>Instance Principals Authentication</td>
@@ -69,7 +85,7 @@ The following table provides details about some of the attributes of the SDK.
69
85
  <td>
70
86
  The Object Storage service supports multipart uploads to make large object uploads easier by splitting the large object into parts. The Ruby SDK supports raw multipart upload operations for advanced use cases, as well as a higher-level upload class that uses the multipart upload APIs.
71
87
  <p>
72
- <a href="https://docs.us-phoenix-1.oraclecloud.com/Content/Object/Tasks/managingmultipartuploads.htm">Managing Multipart Uploads</a> provides links to the APIs used for raw multipart upload operations. Higher-level uploads can be performed using the {OCI::ObjectStorage::Transfer::UploadManager}.
88
+ <a href="https://docs.us-phoenix-1.oraclecloud.com/Content/Object/Tasks/managingmultipartuploads.htm">Managing Multipart Uploads</a> provides links to the APIs used for raw multipart upload operations. Higher-level uploads can be performed using the {OCI::ObjectStorage::Transfer::UploadManager}.
73
89
  </p>
74
90
  <p>
75
91
  The UploadManager simplifies interaction with the Object Storage service by abstracting away the method used to upload objects and can handle uploading an entire object at once, or in multiple parts if it is of sufficient size (which is configurable via a {OCI::ObjectStorage::Transfer::UploadManagerConfig} object). In the latter case, the UploadManager will split a large object into parts for you, upload the parts in parallel, and then recombine and commit the parts as a single object in Object Storage.
@@ -154,9 +170,165 @@ Or you can create a {OCI::Config Config} programmatically. Note that the global
154
170
 
155
171
  The default config file location is `~/.oci/config` (on Windows `C:\Users\{user}\.oci\config`).
156
172
 
157
- ## Service Errors
173
+ ## Configuring the HTTP Client
174
+
175
+ The underlying HTTP client used by the SDK can be configured by setting the {OCI::ApiClient#request_option_overrides ApiClient.request_option_overrides} on the client. An example on how to do this, showing all supported options, is:
176
+
177
+ require 'oci'
178
+
179
+ identity = OCI::Identity::IdentityService.new
180
+
181
+ identity.api_client.request_option_overrides = {
182
+ # The path to the PEM-formatted CA certificate file.
183
+ # The file can contain several CA certificates
184
+ ca_file: '/path/to/pem/file',
185
+
186
+ # The path to a directory of PEM-formatted CA certificate files
187
+ ca_path_cert: '/path/to/cert/directory',
188
+
189
+ # An OpenSSL::X509::Certificate object as client certificate
190
+ cert: OpenSSL::X509::Certificate.new(...),
191
+
192
+ # The X509::Store to verify peer certificate.
193
+ cert_store: OpenSSL::X509::Store.new(...),
194
+
195
+ # One of the available ciphers from OpenSSL::SSL::SSLContext#ciphers
196
+ ciphers: ...,
197
+
198
+ # Close the connection immediately if the server sent no response body
199
+ close_on_empty_response: true,
200
+
201
+ # An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object
202
+ key: ...,
203
+
204
+ # The SSL timeout in seconds
205
+ ssl_timeout: 60,
206
+
207
+ # The SSL version to use. See OpenSSL::SSL::SSLContext#ssl_version
208
+ ssl_version: ...,
209
+
210
+ # The verify callback for the server certification verification
211
+ verify_callback: ...,
212
+
213
+ # The maximum depth for the certificate chain verification
214
+ verify_depth: 3,
215
+
216
+ # Either OpenSSL::SSL::VERIFY_PEER or OpenSSL::SSL::VERIFY_NONE
217
+ # Using OpenSSL::SSL::VERIFY_NONE (i.e. not validating the SSL certificate) is not
218
+ # recommended
219
+ verify_mode: OpenSSL::SSL::VERIFY_PEER
220
+ }
221
+
222
+ Note that these correspond to a subset of the keys that can be provided as `opts` to the `Net::HTTP` [start](https://ruby-doc.org/stdlib-2.4.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start) method.
223
+
224
+ ### Setting a proxy
225
+ If the SDK is running in an environment which requires the use of a proxy server for outgoing HTTP requests, this can be configured by providing via:
226
+
227
+ * Providing an environment variable; or
228
+ * Providing an {OCI::ApiClientProxySettings} object. Here is an example of the different ways to create this object:
229
+
230
+ #### Proxy via environment variable
231
+ As the SDK uses `Net::HTTP`, the `http_proxy` environment variable can be used to specify a proxy:
232
+
233
+ export HTTP_PROXY="http://10.10.1.10:3128"
234
+
235
+ When using the environment variable approach, no code changes need to be made.
236
+
237
+ Note that this does not support providing a username and password to the proxy, *except* when using Ruby 2.5+ on Linux, FreeBSD or macOS (Darwin). Otherwise, if a username and password is required then the {OCI::ApiClientProxySettings} object method can be used.
238
+
239
+ #### Proxy via the ApiClientProxySettings object
240
+ An {OCI::ApiClientProxySettings} object can be used to capture proxy settings. Here are the different ways an object can be created:
241
+
242
+
243
+ # This creates an ApiClientProxySettings with a nil proxy address and will make the API client
244
+ # bypass all proxies
245
+ proxy_settings = OCI::ApiClientProxySettings.new(nil)
246
+
247
+ # This creates an ApiClientProxySettings that will use the proxy at 10.10.1.10:3128 and will
248
+ # not provide a username and password to the proxy
249
+ proxy_settings = OCI::ApiClientProxySettings.new("10.10.1.10", 3128)
250
+
251
+ # This creates an ApiClientProxySettings with a username and password. In production, you should use an appropriate password/secret management mechanism to vend the username and password
252
+ # rather than having the credentials in code
253
+ proxy_settings = OCI::ApiClientProxySettings.new("10.10.1.10", 3128, "username", "pass")
254
+
255
+ Once an {OCI::ApiClientProxySettings} object has been created, it can be used when creating or modifying a client:
158
256
 
159
- Any operation resulting in a service error will cause an exception of type OCI::Errors::ServiceError to be thrown by the SDK. For information about common service errors returned by OCI, see [API Errors](https://docs.us-phoenix-1.oraclecloud.com/Content/API/References/apierrors.htm).
257
+ # At client creation time
258
+ identity = OCI::Identity::IdentityService.new(proxy_settings: proxy_settings)
259
+
260
+ # When updating a client
261
+ identity.api_client.proxy_settings = proxy_settings
262
+
263
+ # The proxy_settings can also be nil'ed out. This will make the ApiClient fall back to
264
+ # using the http_proxy environment variable (if present)
265
+ identity.api_client.proxy_settings = nil
266
+
267
+ ## Exceptions
268
+
269
+ ### Service Errors
270
+ Any operation that receives a response with a non-2xx HTTP status code from an OCI service will cause an exception of type {OCI::Errors::ServiceError ServiceError} to be thrown by the SDK.
271
+
272
+ For information about common service errors returned by OCI, see [API Errors](https://docs.us-phoenix-1.oraclecloud.com/Content/API/References/apierrors.htm).
273
+
274
+ The key attributes to inspect when dealing with a {OCI::Errors::ServiceError} are:
275
+
276
+ * {OCI::Errors::ServiceError#request_id request_id} if you need to contact Oracle about a particular request, please provide this request ID
277
+ * {OCI::Errors::ServiceError#status_code status_code} contains the HTTP status code of the response
278
+ * {OCI::Errors::ServiceError#service_code service_code} should contain a value from the *Error Code* field as described in the *API Errors* page
279
+ * {OCI::Errors::ServiceError#request_made request_made} contains the actual `Net::HTTPRequest` which was sent by the SDK
280
+
281
+ You can also call {OCI::Errors::ServiceError#to_s to_s} on the error to get a summary of the key information about the error.
282
+
283
+ #### HTTP 3xx Responses
284
+ The SDK will throw exceptions of type {OCI::Errors::ServiceError ServiceError} on HTTP 3xx responses. This impacts operations that support conditional GETs. This includes {OCI::ObjectStorage::ObjectStorageClient#get_object} and {OCI::ObjectStorage::ObjectStorageClient#head_object} methods as these can return responses with a HTTP status code of 304 if passed an `if_none_match`, which corresponds to the current etag of the object or bucket.
285
+
286
+ In order to account for this, you should catch {OCI::Errors::ServiceError ServiceError} and check its `status_code` attribute for the HTTP status code. For example:
287
+
288
+ require 'oci'
289
+
290
+ object_storage = OCI::ObjectStorage::ObjectStorageClient.new
291
+
292
+ begin
293
+ get_object_response = object_storage.get_object('my_namespace', 'my_bucket', 'my_object', if_none_match: 'some_etag_value')
294
+ rescue OCI::Errors::ServiceError => err
295
+ # Do nothing if the object exists but has not been modified (based on the etag value), otherwise raise the exception
296
+ raise if err.status_code != 304
297
+ end
298
+
299
+
300
+ ### Network Errors
301
+ {OCI::Errors::NetworkError NetworkError} is thrown when the issue is likely to be network related rather than an application issue. This is defined as:
302
+
303
+ * Requests which were sent from the SDK but the outcome was not a response from an OCI service. Further examples of include:
304
+ * Gateway timeouts
305
+ * Read or connection timeouts
306
+ * Any {Errno} exception which was generated by making the request
307
+ * Requests which resulted in a HTTP 408 (timeout)
308
+
309
+ The key attributes to inspect when dealing with a {OCI::Errors::NetworkError} are:
310
+
311
+ * {OCI::Errors::NetworkError#request_made request_made} and {OCI::Errors::NetworkError#response_received response_received} contain the `Net::HTTPRequest` and `Net::HTTPResponse`, respectively
312
+ * {OCI::Errors::NetworkError#code code} contains the HTTP status code of the response
313
+
314
+ You can also call {OCI::Errors::NetworkError#to_s to_s} on the error to get a summary of the key information about the error. In addition, the {OCI::Errors::NetworkError#cause NetworkError.cause} of the {OCI::Errors::NetworkError NetworkError} can be inspected to see the original error that caused the {OCI::Errors::NetworkError NetworkError} to be thrown.
315
+
316
+ ### Response Parsing Errors
317
+ {OCI::Errors::ResponseParsingError ResponseParsingError} is thrown when a response was received from an OCI service but the SDK could not parse it into the appropriate model type to put into an {OCI::Response}.
318
+
319
+ The key attributes to inspect when dealing with a {OCI::Errors::ResponseParsingError} are:
320
+
321
+ * {OCI::Errors::ResponseParsingError#request_made request_made} and {OCI::Errors::ResponseParsingError#response_received response_received} contain the `Net::HTTPRequest` and `Net::HTTPResponse`, respectively
322
+ * {OCI::Errors::ResponseParsingError#response_body response_body} contains the response data which failed to parse
323
+
324
+ You can also call {OCI::Errors::ResponseParsingError#to_s to_s} on the error to get a summary of the key information about the error. In addition, the {OCI::Errors::ResponseParsingError#cause ResponseParsingError.cause} of the {OCI::Errors::ResponseParsingError ResponseParsingError} can be inspected to see the original error that caused the {OCI::Errors::ResponseParsingError ResponseParsingError} to be thrown.
325
+
326
+ ### Other Errors
327
+ The other errors you may encounter while using the SDK are:
328
+
329
+ * {OCI::Errors::MaximumWaitTimeExceededError MaximumWaitTimeExceededError} when using {OCI::Response#wait_until} or when using {OCI::LoadBalancer::Util#wait_on_work_request}
330
+ * {OCI::Errors::WorkRequestFailedError WorkRequestFailedError} when using {OCI::LoadBalancer::Util#wait_on_work_request}. The {OCI::Errors::WorkRequestFailedError#work_request work_request} attribute can be inspected to get the actual work request details
331
+ * {OCI::Errors::MultipartUploadError MultipartUploadError} when using {OCI::ObjectStorage::Transfer::UploadManager UploadManager}. The {OCI::Errors::MultipartUploadError#errors errors} attribute can be inspected to see what errors occurred during the upload, and the {OCI::Errors::MultipartUploadError#upload_id upload_id} can be used if you wish to try and resume the upload via the UploadManager's {OCI::ObjectStorage::Transfer::UploadManager#resume resume} method
160
332
 
161
333
  ## Examples
162
334
  The example code in this section shows how various parts of the Ruby SDK work. More examples can be found in the SDK download.
@@ -349,6 +521,3 @@ Ways to get in touch:
349
521
  * [Stack Overflow](https://stackoverflow.com/): Please use the [oracle-cloud-infrastructure](https://stackoverflow.com/questions/tagged/oracle-cloud-infrastructure) and [oci-ruby-sdk](https://stackoverflow.com/questions/tagged/oci-ruby-sdk) tags in your post
350
522
  * [Developer Tools section](https://community.oracle.com/community/cloud_computing/bare-metal/content?filterID=contentstatus%5Bpublished%5D~category%5Bdeveloper-tools%5D&filterID=contentstatus%5Bpublished%5D~objecttype~objecttype%5Bthread%5D) of the Oracle Cloud forums
351
523
  * [My Oracle Support](https://support.oracle.com)
352
-
353
-
354
-
data/lib/oci.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
2
2
 
3
3
  require 'oci/api_client'
4
+ require 'oci/api_client_proxy_settings'
4
5
  require 'oci/config'
5
6
  require 'oci/config_file_loader'
6
7
  require 'oci/errors'
@@ -45,7 +45,12 @@ module OCI
45
45
  # @return [Hash]
46
46
  attr_accessor :request_option_overrides
47
47
 
48
- def initialize(config, signer)
48
+ # The proxy settings which this ApiClient will use
49
+ #
50
+ # @return [OCI::ApiClientProxySettings]
51
+ attr_accessor :proxy_settings
52
+
53
+ def initialize(config, signer, proxy_settings: nil)
49
54
  raise "Missing the required parameter 'config' when initializing ApiClient." if config.nil?
50
55
  raise "Missing the required parameter 'signer' when initializing ApiClient." if signer.nil?
51
56
 
@@ -53,6 +58,7 @@ module OCI
53
58
  @signer = signer
54
59
  @default_headers = {}
55
60
  @request_option_overrides = {}
61
+ @proxy_settings = proxy_settings
56
62
  end
57
63
 
58
64
  # Call an API with given options.
@@ -101,8 +107,8 @@ module OCI
101
107
  # to support IO-like object as well like StringIO
102
108
  return model if model.nil? || model.is_a?(String) || (model.respond_to?(:read) && model.respond_to?(:write))
103
109
 
104
- # Supports IO-wrapping objects we can convert to an IO. An example is Rails'
105
- # ActionDispatch::Http::UploadedFile, which wraps an IO (a Tempfile) but
110
+ # Supports IO-wrapping objects we can convert to an IO. An example is Rails'
111
+ # ActionDispatch::Http::UploadedFile, which wraps an IO (a Tempfile) but
106
112
  # doesn't expose all the IO operations directly (e.g. you can't write to it, it's not seekable)
107
113
  #
108
114
  # This should be safe to use with IO and its subclasses as well as to_io is a method on IO:
@@ -177,7 +183,7 @@ module OCI
177
183
  elsif http_method == :patch
178
184
  request = Net::HTTP::Patch.new(uri)
179
185
  else
180
- fail "new http method (#{http_method}) needs to be supported!"
186
+ raise "new http method (#{http_method}) needs to be supported!"
181
187
  end
182
188
 
183
189
  if body.respond_to?(:read) && body.respond_to?(:write)
@@ -204,7 +210,7 @@ module OCI
204
210
  request['opc-request-id'] ||= build_request_id
205
211
  request['User-Agent'] = build_user_agent
206
212
 
207
- http = Net::HTTP.new(uri.hostname, uri.port)
213
+ http = get_http_object(uri.hostname, uri.port)
208
214
 
209
215
  unless @request_option_overrides.empty?
210
216
  http.methods.grep(/\A(\w+)=\z/) do |meth|
@@ -219,74 +225,166 @@ module OCI
219
225
  http.open_timeout = @config.connection_timeout
220
226
  http.read_timeout = @config.timeout.zero? ? 31_536_000 : @config.timeout # 31536000 means 365 days
221
227
 
228
+ response_ref = nil
222
229
  begin
223
230
  http.start do
224
- http.request request do |response|
225
-
226
- # If the response is timeout already, does not make sense to parse the http body because partial
227
- # response may be returned, we should skip JSON parser and raise exception immediately
228
- if response.is_a? Net::HTTPRequestTimeOut
229
- raise Errors::NetworkError.new(response.message, response.code.to_i)
230
- end
231
+ http.request(request) do |response|
232
+ response_ref = response
231
233
 
232
234
  # process headers for opc-meta- key
233
- metadata = {}
234
- prefix_size = 'opc-meta-'.length.freeze
235
- response.each_header do |key, value|
236
- if key.start_with? 'opc-meta-'
237
- metadata[key[prefix_size..-1]] = value
238
- end
239
- end
240
-
241
- metadata.each do |key, value|
242
- response.delete "opc-meta-#{key}"
243
- response[key] = value
244
- end
245
-
246
- if block.nil? || !success?(response)
247
- if @config.logger
248
- @config.logger.debug "HTTP response body ~BEGIN~\n#{response.body}\n~END~\n"
249
- end
250
-
251
- # In the case of an error, deserialize to a plain hash by indicating 'Object'.
252
- return_type = success?(response) ? opts[:return_type] : 'Object'
253
- data = return_type ? deserialize(response, return_type) : nil
254
-
255
- unless success? response
256
- if data
257
- raise Errors::ServiceError.new(response.code.to_i,
258
- data[:code],
259
- response.header['opc-request-id'],
260
- data[:message])
261
- else
262
- raise Errors::NetworkError.new(response.message, response.code.to_i)
263
- end
264
- end
265
-
266
- if @config.logger
267
- @config.logger.debug "API Response Received:\nData: #{data.inspect}\nStatus code: #{response.code}\nHeaders: #{response.header}"
268
- end
269
-
270
- return Response.new(response.code.to_i, response.header, data)
271
- else
272
- response.read_body do |chunk|
273
- yield chunk, response
274
- end
275
- return Response.new(response.code.to_i, response.header, nil)
276
- end
235
+ replace_keys_in_response_headers_with_non_prefixed_equivalents(response, 'opc-meta-')
236
+
237
+ # Either stream the body through a block (if we have one) and return, or read the body so that it can be accessed
238
+ # again later and jump out of the http.start block
239
+ #
240
+ # The idea is to terminate the HTTP connection as early as possible. There are two main reasons for
241
+ # this:
242
+ #
243
+ # 1. Net::HTTP can throw a lot of exceptions, both from itself and what bubbles up from the underlying OS
244
+ # via Errno:: so we have a very broad rescue block. This isn't great because the more work we do inside the
245
+ # http.start block the more gaps we leave for non-Net::HTTP related errors. This makes it hard to throw
246
+ # a meaningful error back to the customer (i.e. we leave ourselves gaps where Errors::NetworkError does not apply
247
+ # and could throw something disingenuous if we mask everything as a NetworkError)
248
+ #
249
+ # 2. Release resources sooner/don't hold connections open longer than necessary (e.g. we don't need the
250
+ # connection open while we're deserialising)
251
+ return process_response_block(response, &block) if success?(response) && !block.nil?
252
+ response.body
277
253
  end
278
254
  end
279
- rescue Errors::NetworkError, Errors::ServiceError, JSON::ParserError
280
- raise
281
- rescue => ex
282
- raise Errors::NetworkError.new(ex.message, 0)
255
+ rescue StandardError => err
256
+ # Unfortunately, catching StandardError is the surest way to capture all the errors originating from Net::HTTP
257
+ code_from_response = if !response_ref.nil?
258
+ response_ref.code.to_i
259
+ else
260
+ 0
261
+ end
262
+
263
+ raise Errors::NetworkError.new(
264
+ err.message,
265
+ code_from_response,
266
+ request_made: request,
267
+ response_received: response_ref
268
+ )
283
269
  end
270
+
271
+ # If the response is a timeout (HTTP 408), it does not make sense to parse the http body because a partial
272
+ # response may be returned. We should skip JSON parsing and raise an error immediately
273
+ handle_timeout_response(request, response_ref)
274
+
275
+ @config.logger.debug("HTTP response body ~BEGIN~\n#{response_ref.body}\n~END~\n") if @config.logger
276
+
277
+ return handle_success_response(request, response_ref, opts[:return_type]) if success?(response_ref)
278
+ handle_non_success_response(request, response_ref)
279
+ end
280
+
281
+ def get_http_object(hostname, port)
282
+ return Net::HTTP.new(hostname, port) if @proxy_settings.nil?
283
+ return Net::HTTP.new(hostname, port, @proxy_settings.proxy_address) if @proxy_settings.proxy_address.nil?
284
+
285
+ Net::HTTP.new(
286
+ hostname,
287
+ port,
288
+ @proxy_settings.proxy_address,
289
+ @proxy_settings.proxy_port,
290
+ @proxy_settings.proxy_user,
291
+ @proxy_settings.proxy_password
292
+ )
284
293
  end
285
294
 
286
295
  def success?(response)
287
296
  response.is_a?(Net::HTTPSuccess)
288
297
  end
289
298
 
299
+ # Determines whether a response "looks like" one returned from an OCI service (regardless of whether the response itself
300
+ # represents success or failure).
301
+ #
302
+ # This is determined by whether the response includes an opc-request-id header or some header which starts with
303
+ # opc-
304
+ def response_from_oci_service?(response)
305
+ response.key?('opc-request-id') || response.to_hash.keys.any? { |k| k.downcase.start_with?('opc-') }
306
+ end
307
+
308
+ def replace_keys_in_response_headers_with_non_prefixed_equivalents(response, prefix)
309
+ raise 'Response cannot be nil' if response.nil?
310
+ raise 'A non-blank prefix must be provided' if prefix.nil? || prefix.strip.empty?
311
+
312
+ prefix_size = prefix.length.freeze
313
+ processed_keys = {}
314
+
315
+ response.each_header do |key, value|
316
+ if key.start_with?(prefix)
317
+ processed_keys[key[prefix_size..-1]] = value
318
+ end
319
+ end
320
+
321
+ processed_keys.each do |key, value|
322
+ response.delete("#{prefix}#{key}")
323
+ response[key] = value
324
+ end
325
+
326
+ processed_keys
327
+ end
328
+
329
+ def process_response_block(response)
330
+ raise 'A block must be provided' unless block_given?
331
+ response.read_body do |chunk|
332
+ yield chunk, response
333
+ end
334
+
335
+ Response.new(response.code.to_i, response.header, nil)
336
+ end
337
+
338
+ def handle_timeout_response(request, response)
339
+ return unless response.is_a?(Net::HTTPRequestTimeOut)
340
+ raise Errors::NetworkError.new(
341
+ response.message,
342
+ response.code.to_i,
343
+ request_made: request,
344
+ response_received: response
345
+ )
346
+ end
347
+
348
+ def handle_success_response(request, response, return_type)
349
+ data = return_type ? deserialize(request, response, return_type) : nil
350
+
351
+ @config.logger.debug("API Response Received:\nData: #{data.inspect}\nStatus code: #{response.code}\nHeaders: #{response.header}") \
352
+ if @config.logger
353
+
354
+ Response.new(response.code.to_i, response.header, data)
355
+ end
356
+
357
+ def handle_non_success_response(request, response)
358
+ # For errors, we expect a plain JSON object so pass 'Object' here in order to serialize it into a hash
359
+ data = deserialize(request, response, 'Object')
360
+ if data
361
+ raise Errors::ServiceError.new(
362
+ response.code.to_i,
363
+ data[:code],
364
+ response.header['opc-request-id'],
365
+ data[:message],
366
+ request_made: request
367
+ )
368
+ elsif response_from_oci_service?(response)
369
+ # TODO: Once we have a standard for how service errors are transmitted when there is no body (e.g. in the header),
370
+ # change how the ServiceError gets constructed to get data from those fields
371
+ raise Errors::ServiceError.new(
372
+ response.code.to_i,
373
+ nil,
374
+ response.header['opc-request-id'],
375
+ response.message,
376
+ request_made: request
377
+ )
378
+ else
379
+ raise Errors::NetworkError.new(
380
+ response.message,
381
+ response.code.to_i,
382
+ request_made: request,
383
+ response_received: response
384
+ )
385
+ end
386
+ end
387
+
290
388
  def self.append_query_params(url, query_params)
291
389
  if query_params.empty?
292
390
  url
@@ -374,7 +472,7 @@ module OCI
374
472
  #
375
473
  # @param [Response] response HTTP response
376
474
  # @param [String] return_type some examples: "User", "Array[User]", "Hash[String,Integer]"
377
- def deserialize(response, return_type)
475
+ def deserialize(request, response, return_type)
378
476
  body = response.body
379
477
  return if body.nil? || body.empty?
380
478
 
@@ -386,15 +484,39 @@ module OCI
386
484
  # There are some cases, the error is not returned by services but by like gateway, for example in bug
387
485
  # https://jira.aka.lgl.grungy.us/browse/DEX-564, gateway timeouts and 504 is returned and content is generated
388
486
  # by gateway, so there is no guarantee that the content-type will be application/json.
389
- raise Errors::NetworkError.new(response.message, response.code.to_i) unless json_mime?(content_type)
487
+ if !success?(response) && !response_from_oci_service?(response)
488
+ raise Errors::NetworkError.new(
489
+ response.message,
490
+ response.code.to_i,
491
+ request_made: request,
492
+ response_received: response
493
+ )
494
+ end
390
495
 
391
496
  begin
392
497
  data = JSON.parse("[#{body}]", :symbolize_names => true)[0]
393
- rescue JSON::ParserError => e
498
+ rescue JSON::ParserError
394
499
  if %w(String Date DateTime).include?(return_type)
395
500
  data = body
396
501
  else
397
- raise e
502
+ # If we received an error here then we received a response from the OCI service that
503
+ # we could not parse. Instead of throwing a parsing error, throw out a service error
504
+ # (so the customer has all the information) and put the response body in the message
505
+ # so that they may also have a sense of what the error from the service was
506
+ unless success?(response)
507
+ raise OCI::Errors::ServiceError.new(
508
+ response.code.to_i,
509
+ nil,
510
+ response['opc-request-id'],
511
+ response.body,
512
+ request_made: request
513
+ )
514
+ end
515
+
516
+ raise OCI::Errors::ResponseParsingError.new(
517
+ request_made: request,
518
+ response_received: response
519
+ )
398
520
  end
399
521
  end
400
522