oci-logging-analytics-kubernetes-discovery 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +40 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +36 -0
  6. data/README.md +83 -0
  7. data/Rakefile +15 -0
  8. data/bin/console +17 -0
  9. data/bin/oci-loganalytics-kubernetes-discovery +184 -0
  10. data/bin/setup +12 -0
  11. data/lib/config/oci_client_retry_config.rb +34 -0
  12. data/lib/discover/infrastructure.rb +122 -0
  13. data/lib/discover/object.rb +347 -0
  14. data/lib/dto/infra/cluster_entity_payload.rb +22 -0
  15. data/lib/dto/infra/load_balancers_entity_payload.rb +22 -0
  16. data/lib/dto/infra/node_pool_entity_payload.rb +24 -0
  17. data/lib/dto/infra/subnet_entity_payload.rb +22 -0
  18. data/lib/dto/infra/vcn_entity_payload.rb +22 -0
  19. data/lib/dto/infra_objects_payload.rb +40 -0
  20. data/lib/dto/kubernetes_objects_payload.rb +58 -0
  21. data/lib/dto/payload/log_events.rb +26 -0
  22. data/lib/dto/payload/log_events_json.rb +22 -0
  23. data/lib/dto/state.rb +19 -0
  24. data/lib/enum/auth_type_enum.rb +9 -0
  25. data/lib/enum/infrastructure_resource_discovery.rb +9 -0
  26. data/lib/enum/kubernetes_objects_enum.rb +22 -0
  27. data/lib/enum/object_client_mapping_enum.rb +21 -0
  28. data/lib/infra_resources.rb +91 -0
  29. data/lib/objects_resources.rb +174 -0
  30. data/lib/oci_loganalytics_resources_discovery.rb +293 -0
  31. data/lib/util/kube_client.rb +141 -0
  32. data/lib/util/kubectl_ops.rb +229 -0
  33. data/lib/util/log_analytics.rb +154 -0
  34. data/lib/util/logging.rb +96 -0
  35. data/lib/util/oci_clients.rb +228 -0
  36. data/lib/util/state_manager.rb +61 -0
  37. data/lib/util/string_utils.rb +16 -0
  38. data/lib/version.rb +6 -0
  39. data/oci-logging-analytics-kubernetes-discovery.gemspec +45 -0
  40. metadata +324 -0
@@ -0,0 +1,141 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require 'resolv'
5
+ require_relative '../util/logging'
6
+
7
+ module Util
8
+ module KubeClient
9
+ extend Util::Logging
10
+
11
+ module_function
12
+
13
+ attr_accessor :current_kube_context, :core_client, :apps_client, :batch_client,
14
+ :discover_v1_client, :kube_clients, :kube_config_hash,
15
+ :kubernetes_url, :api_endpoint, :api_version,
16
+ :client_cert, :client_key, :ca_file, :verify_ssl, :bearer_token_file, :secret_dir
17
+
18
+ def create_clients(kube_config_hash)
19
+ begin
20
+ initialize_config(kube_config_hash)
21
+ @core_client = create_client_by_object_type('', @api_version)
22
+ @apps_client = create_client_by_object_type('/apis/apps', @api_version)
23
+ @batch_client = create_client_by_object_type('/apis/batch', @api_version)
24
+ @discover_v1_client = create_client_by_object_type('/apis/discovery.k8s.io', @api_version)
25
+ rescue StandardError => e
26
+ logger.error("Error while creating kube clients. Error - #{e}")
27
+ raise e
28
+ end
29
+
30
+ set_clients(@core_client, @apps_client, @batch_client, @discover_v1_client)
31
+ end
32
+
33
+ def initialize_config(kube_config_hash)
34
+ @kube_config_hash = kube_config_hash
35
+
36
+ unless @kube_config_hash[:kube_config_location].nil?
37
+ begin
38
+ config = Kubeclient::Config.read @kube_config_hash[:kube_config_location]
39
+ @current_kube_context = config.context
40
+ rescue StandardError => e
41
+ logger.error("Error while initializing config params for kube client - #{e}")
42
+ raise e
43
+ end
44
+ end
45
+
46
+ @kubernetes_url = @kube_config_hash[:kubernetes_url]
47
+ @api_endpoint = @kube_config_hash[:api_endpoint]
48
+ @api_version = @kube_config_hash[:api_version]
49
+ @client_cert = @kube_config_hash[:client_cert]
50
+ @client_key = @kube_config_hash[:client_key]
51
+ @ca_file = @kube_config_hash[:ca_file]
52
+ @verify_ssl = @kube_config_hash[:verify_ssl]
53
+ @bearer_token_file = @kube_config_hash[:bearer_token_file]
54
+ @secret_dir = @kube_config_hash[:secret_dir]
55
+ end
56
+
57
+ def create_client_by_object_type(api_endpoint, api_version)
58
+ if !@current_kube_context.nil?
59
+ create_kube_client_config(api_endpoint, api_version)
60
+ else
61
+ create_kube_client_default(api_endpoint, api_version)
62
+ end
63
+ end
64
+
65
+ def create_kube_client_config(api_endpoint, api_version)
66
+ logger.debug("Endpoint: #{@current_kube_context.api_endpoint + api_endpoint} and api_version: #{api_version}")
67
+ begin
68
+ # TODO: Test with SSL options.
69
+ client = Kubeclient::Client.new(
70
+ @current_kube_context.api_endpoint + api_endpoint,
71
+ api_version,
72
+ ssl_options: @current_kube_context.ssl_options,
73
+ auth_options: @current_kube_context.auth_options
74
+ )
75
+ rescue StandardError => e
76
+ logger.error("Error while creating kube client based on config - #{e}")
77
+ raise e
78
+ end
79
+ client
80
+ end
81
+
82
+ def create_kube_client_default(api_endpoint, api_version)
83
+ # Reference: https://github.com/splunk/fluent-plugin-kubernetes-objects/blob/develop/lib/fluent/plugin/in_kubernetes_objects.rb#L139
84
+ kubernetes_url = nil
85
+ if @kubernetes_url.nil?
86
+ # Use Kubernetes default service account if we're in a pod.
87
+ env_host = ENV['KUBERNETES_SERVICE_HOST']
88
+ env_host = "[#{env_host}]" if env_host =~ Resolv::IPv6::Regex
89
+ env_port = ENV['KUBERNETES_SERVICE_PORT']
90
+ if env_host && env_port
91
+ # kubernetes_url = "https://#{env_host}:#{env_port}/#{@api_endpoint.delete_prefix('/')}#{api_endpoint}"
92
+ kubernetes_url = "https://#{env_host}:#{env_port}#{api_endpoint}"
93
+ end
94
+ else
95
+ kubernetes_url = @kubernetes_url + api_endpoint
96
+ end
97
+
98
+ # Use SSL certificate and bearer token from Kubernetes service account.
99
+ if Dir.exist?(@secret_dir)
100
+ secret_ca_file = File.join(@secret_dir, 'ca.crt')
101
+ secret_token_file = File.join(@secret_dir, 'token')
102
+
103
+ @ca_file = secret_ca_file if @ca_file.nil? && File.exist?(secret_ca_file)
104
+
105
+ @bearer_token_file = secret_token_file if @bearer_token_file.nil? && File.exist?(secret_token_file)
106
+ end
107
+
108
+ ssl_options = {
109
+ client_cert: @client_cert && OpenSSL::X509::Certificate.new(File.read(@client_cert)),
110
+ client_key: @client_key && OpenSSL::PKey::RSA.new(File.read(@client_key)),
111
+ ca_file: @ca_file,
112
+ verify_ssl: @verify_ssl ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
113
+ }
114
+
115
+ auth_options = {}
116
+ auth_options[:bearer_token] = File.read(@bearer_token_file) if @bearer_token_file
117
+ logger.debug("kubernetes_url : #{kubernetes_url} and api_version: #{api_version}")
118
+ Kubeclient::Client.new(
119
+ kubernetes_url, @api_version,
120
+ ssl_options: ssl_options,
121
+ auth_options: auth_options
122
+ )
123
+ rescue StandardError => e
124
+ logger.error("Error while creating default kube client - #{e}")
125
+ raise e
126
+ end
127
+
128
+ def set_clients(core_client, apps_client, batch_client, discover_v1_client)
129
+ @kube_clients = {
130
+ core_client: core_client,
131
+ apps_client: apps_client,
132
+ batch_client: batch_client,
133
+ discover_v1_client: discover_v1_client
134
+ }
135
+ end
136
+
137
+ def get_clients
138
+ @kube_clients
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,229 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require_relative '../util/logging'
5
+
6
+ # Enum
7
+ require_relative '../enum/kubernetes_objects_enum'
8
+
9
+ module Util
10
+ module KubectlOps
11
+ extend Util::Logging
12
+
13
+ module_function
14
+
15
+ attr_accessor :chunk_limit
16
+
17
+ CONFIG_MAP_RESOURCE_NAME = 'config_map'.freeze
18
+
19
+ # Status codes for which retry is attempted (To represent 5xx, use 5)
20
+ RETRYABLE_STATUS_CODE = [429, 5].freeze
21
+ MAX_ATTEMPTS = 5
22
+ INITIAL_DELAY = 1 # seconds
23
+ EXPONENT_CONSTANT = 2 # 'e' in exponential function (e^x) where x is number of retries
24
+
25
+ def set_chunk_limit(num)
26
+ @chunk_limit = num
27
+ end
28
+
29
+ def should_retry?(error, retry_count, object_type)
30
+ return false if error.error_code.nil? # for handling timeouts
31
+
32
+ if (RETRYABLE_STATUS_CODE.include? error.error_code) || RETRYABLE_STATUS_CODE.include?(error.error_code / 100)
33
+ if retry_count < MAX_ATTEMPTS
34
+ # If e = 2, and initial delay is 10, intervals will be (1, 2, 4, 8, 16)
35
+ sleep_interval = EXPONENT_CONSTANT.pow(retry_count) * INITIAL_DELAY
36
+ logger.warn("Error while making API Server call - '#{object_type}'. Retrying in #{sleep_interval} seconds. HTTP status code: #{error.error_code}")
37
+ sleep(sleep_interval)
38
+ return true
39
+ end
40
+ end
41
+ false
42
+ end
43
+
44
+ def kube_apiserver_request(method:, object_type:, client: nil, object_name: nil, namespace: nil, object: nil, patch: nil, opts: {})
45
+ response = nil
46
+ retry_count = 0
47
+ begin
48
+ case method
49
+ when :get_objects
50
+ if client.nil?
51
+ client = Util::KubeClient.get_clients[Enum::ObjectClientMapingEnum.const_get(object_type.upcase.to_s)]
52
+ end
53
+ logger.debug("Fetching '#{object_type}' details from all namespaces")
54
+ method_verb = 'GET'
55
+ opts.merge!({ as: :parsed_symbolized })
56
+ response = {}
57
+ loop do
58
+ entities = client.public_send("get_#{object_type}", opts)
59
+ continue = entities[:metadata][:continue]
60
+ if response.empty?
61
+ # when all objects are fetched via single API request
62
+ if continue.nil?
63
+ response = entities
64
+ break
65
+ end
66
+ response = entities.clone
67
+ else
68
+ response[:items] += entities[:items]
69
+ end
70
+ break if continue.nil?
71
+
72
+ logger.debug("remainingItemCount - #{entities[:metadata][:remainingItemCount]}")
73
+ opts.merge!({ continue: continue })
74
+ end
75
+ if response.nil? || response.empty?
76
+ logger.debug("Empty or null response recieved from Kubernetes API endpoint '#{client.rest_client.url}'.")
77
+ return { parsed_symbolized: [], raw: '' }
78
+ else
79
+ return { parsed_symbolized: response, raw: JSON.dump(response) }
80
+ end
81
+ when :get_object
82
+ method_verb = 'GET'
83
+ logger.debug("Fetching '#{object_type}/#{object_name}' details")
84
+ opts.merge!(as: :parsed_symbolized)
85
+ response = client.public_send("get_#{object_type}", object_name, namespace, opts)
86
+ when :create_object
87
+ method_verb = 'CREATE'
88
+ logger.debug("Creating '#{object_type}' - #{object}")
89
+ response = client.public_send("create_#{object_type}", object)
90
+ when :patch_object
91
+ method_verb = 'PATCH'
92
+ logger.debug("Patching '#{object_type}/#{object_name}' - #{patch}")
93
+ response = client.public_send("patch_#{object_type}", object_name, patch, namespace)
94
+ else
95
+ raise SyntaxError, "Unsupported method - #{method} passed."
96
+ end
97
+ rescue KubeException => e
98
+ if should_retry?(e, retry_count, object_type)
99
+ retry_count += 1
100
+ retry
101
+ end
102
+
103
+ if e.error_code.nil?
104
+ logger.error("Unexpected error occurred while calling #{method_verb} '#{object_type}'. Error details: #{e}")
105
+ raise e
106
+ elsif e.error_code == 302
107
+ logger.error("Max re-direct attempts while calling #{method_verb} '#{object_type}'. Error details: #{e}")
108
+ raise StandardError, "Max re-direct attempts while calling #{method_verb} '#{object_type}'."
109
+ elsif e.error_code == 401
110
+ # In such cases, client should be re-created in order to refresh token
111
+ logger.error("Not authorized to #{method_verb} '#{object_type}'. Error details: #{e}")
112
+ raise StandardError, "Not authorized to #{method_verb} '#{object_type}'."
113
+ elsif e.error_code == 403
114
+ logger.error("Forbidden to #{method_verb} '#{object_type}'. Error details: #{e}")
115
+ raise StandardError, "Forbidden to #{method_verb} '#{object_type}'."
116
+ elsif e.error_code == 404
117
+ logger.debug("Resource '#{object_type}' not found. Error details: #{e}")
118
+ raise e
119
+ elsif e.error_code == 410
120
+ # ref - https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks
121
+ logger.error("Too many '#{object_type}' objects. Consider increasing CHUNK limit.")
122
+ raise StandardError, "Too many '#{object_type}' objects. Consider increasing CHUNK limit."
123
+ elsif e.error_code == 429
124
+ logger.error("Encountered throttling while calling #{method_verb} '#{object_type}'. Error details: #{e}")
125
+ raise StandardError, "Encountered throttling while #{method_verb} '#{object_type}'."
126
+ elsif (e.error_code / 100) == 5
127
+ logger.error("Unexpected error occurred while #{method_verb} '#{object_type}'. Error details: #{e}")
128
+ raise StandardError, "Unexpected error occurred while #{method_verb} '#{object_type}'."
129
+ else
130
+ logger.error("Error '#{e}' encountered while #{method_verb} '#{object_type}' from Kubernetes API endpoint '#{client.rest_client.url}'")
131
+ raise StandardError, "Error '#{e}' encountered while #{method_verb} '#{object_type}'."
132
+ end
133
+ rescue NoMethodError => e
134
+ logger.error('Unexpected Error - Method not found.')
135
+ raise e
136
+ rescue StandardError => e
137
+ logger.error("Error while #{method_verb} '#{object_type}'. Error: #{e}")
138
+
139
+ if should_retry?(e, retry_count, object_type)
140
+ retry_count += 1
141
+ retry
142
+ end
143
+
144
+ error_msg = "Maximum retry attempts ('#{MAX_ATTEMPTS}') exceeded. Aborting fetch for '#{object_type}'."
145
+ logger.error(error_msg)
146
+ raise error error_msg
147
+ end
148
+ response
149
+ end
150
+
151
+ def get_objects(object_type, client = nil, options = {})
152
+ options.merge!({ limit: @chunk_limit, continue: nil })
153
+ kube_apiserver_request(method: :get_objects,
154
+ object_type: object_type,
155
+ client: client,
156
+ opts: options)
157
+ end
158
+
159
+ def get_object(object_type, object_name, namespace, client, options = {})
160
+ kube_apiserver_request(method: :get_object,
161
+ object_type: object_type,
162
+ client: client,
163
+ object_name: object_name,
164
+ namespace: namespace,
165
+ opts: options)
166
+ end
167
+
168
+ def create_object(object_type, kubeclient_resource_object, client)
169
+ kube_apiserver_request(method: :create_object,
170
+ object_type: object_type,
171
+ client: client,
172
+ object: kubeclient_resource_object)
173
+ end
174
+
175
+ def patch_object(object_type, object_name, patch_hash, namespace, client)
176
+ kube_apiserver_request(method: :patch_object,
177
+ object_type: object_type,
178
+ client: client,
179
+ object_name: object_name,
180
+ patch: patch_hash,
181
+ namespace: namespace)
182
+ end
183
+
184
+ def get_configmap(cm_name, namespace)
185
+ object_type = CONFIG_MAP_RESOURCE_NAME
186
+ client = Util::KubeClient.get_clients[:core_client]
187
+ begin
188
+ response = get_object(object_type, cm_name, namespace, client)
189
+ rescue StandardError => e
190
+ return nil if e.error_code == 404
191
+
192
+ raise e
193
+ end
194
+ response
195
+ end
196
+
197
+ def create_configmap(cm_name, namespace, data_hash)
198
+ # Create resource object for config map
199
+ cm = Kubeclient::Resource.new
200
+ cm.metadata = {}
201
+ cm.metadata.name = cm_name
202
+ cm.metadata.namespace = namespace
203
+ cm.data = {}
204
+ cm.data = data_hash
205
+
206
+ object_type = CONFIG_MAP_RESOURCE_NAME
207
+ client = Util::KubeClient.get_clients[:core_client]
208
+
209
+ begin
210
+ create_object(object_type, cm, client)
211
+ rescue StandardError => e
212
+ logger.error("Unable to create '#{object_type}/#{cm_name}'")
213
+ raise e
214
+ end
215
+ end
216
+
217
+ def patch_configmap(cm_name, namespace, data_hash)
218
+ patch_hash = { data: data_hash }
219
+ object_type = CONFIG_MAP_RESOURCE_NAME
220
+ client = Util::KubeClient.get_clients[:core_client]
221
+ begin
222
+ patch_object(object_type, cm_name, patch_hash, namespace, client)
223
+ rescue StandardError => e
224
+ logger.error("Unable to patch '#{object_type}/#{cm_name}'")
225
+ raise e
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,154 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require_relative '../util/logging'
5
+ require_relative '../util/oci_clients'
6
+
7
+ # 'httplog' required *after* HTTP gem of choice
8
+ # require 'httplog'
9
+
10
+ module Util
11
+ module LogAnalytics
12
+ extend Util::Logging
13
+
14
+ # Client information for raw request signing
15
+ CLIENT_INFO = 'Oracle-ORD/Raw Request'.freeze
16
+ CLIENT_AGENT = 'Oracle-ORD/Raw Request (net/http)'.freeze
17
+
18
+ LOG_ANALYTICS_ENDPOINT_TEMPLATE = 'https://loganalytics.{region}.oci.{secondLevelDomain}'.freeze
19
+
20
+ # Status codes for which retry is attempted (To represent 5xx, use 5)
21
+ RETRYABLE_STATUS_CODE = [408, 429, 5].freeze
22
+ MAX_ATTEMPTS = 5
23
+ INITIAL_DELAY = 1 # seconds
24
+ EXPONENT_CONSTANT = 2 # 'e' in exponential function (e^x) where x is number of retries
25
+
26
+ module_function
27
+
28
+ def upload_discovery_data(namespace, payload, opts)
29
+ logger.info("Uploading payload to 'Log Analytics' with Discovery API")
30
+ begin
31
+ client = Util::OCIClients.get_clients[:la_client]
32
+ response = client.upload_discovery_data(namespace_name = namespace,
33
+ upload_discovery_data_details = payload,
34
+ opts)
35
+ rescue StandardError => e
36
+ logger.error("Error while uploading payload to 'Log Analytics': #{e}")
37
+ raise StandardError, "Unable to upload data to 'Log Analytics' client."
38
+ end
39
+ response
40
+ end
41
+
42
+ def upload_discovery_data_raw_request(namespace, payload, opts, auth_config)
43
+ logger.info('Uploading payload to \'Log Analytics\' with Discovery API (raw request)')
44
+
45
+ # Prepare config and signer
46
+ signer = nil
47
+ operation_signing_strategy = :exclude_body
48
+
49
+ case auth_config[:auth_type]
50
+ when Enum::AuthTypeEnum::CONFIG
51
+ config = auth_config[:oci_config]
52
+ signer = OCI::Signer.config_file_auth_builder(config)
53
+ region = config.region
54
+ when Enum::AuthTypeEnum::INSTANCE_PRINCIPAL
55
+ signer = auth_config[:instance_principals_signer]
56
+ region = signer.region
57
+ else
58
+ logger.warn("Unknown auth_type provided for Discovery API (raw request): #{auth_object[:auth_type]}")
59
+ raise StandardError 'Unknown auth_type for Discovery API (raw request)'
60
+ end
61
+
62
+ # Construct URL protocol, subdomain, domain and port
63
+
64
+ endpoint = if !auth_config[:endpoint].nil?
65
+ auth_config[:endpoint] + '/20200601'
66
+ else
67
+ OCI::Regions.get_service_endpoint_for_template(region, LOG_ANALYTICS_ENDPOINT_TEMPLATE) + '/20200601'
68
+ end
69
+
70
+ # Construct URL path
71
+ path = '/namespaces/{namespaceName}/actions/uploadDiscoveryData'.sub('{namespaceName}', namespace.to_s)
72
+
73
+ # Construct Query Params
74
+ query_params = '?discoveryDataType={discoveryDataType}&payloadType={payloadType}'
75
+ .sub('{discoveryDataType}', opts[:discovery_data_type])
76
+ .sub('{payloadType}', opts[:payload_type])
77
+
78
+ # Prepare URI
79
+ url = endpoint + path + query_params
80
+ uri = URI(url)
81
+
82
+ # Set user agent and OPC parameters for request
83
+ http_method = :post
84
+
85
+ request = Net::HTTP::Post.new(uri)
86
+ request['opc-client-info'] = CLIENT_INFO
87
+ request['opc-request-id'] = SecureRandom.uuid.delete!('-').upcase
88
+ request['User-Agent'] = CLIENT_AGENT
89
+
90
+ # Header Params
91
+ header_params = {}
92
+ header_params = opts[:additional_headers].clone if opts[:additional_headers].is_a?(Hash)
93
+ header_params[:accept] = 'application/json'
94
+ header_params[:expect] = '100-continue'
95
+ header_params[:'content-type'] = 'application/octet-stream'
96
+ header_params[:'opc-meta-properties'] = opts[:opc_meta_properties] if opts[:opc_meta_properties]
97
+ header_params[:'opc-retry-token'] = OCI::Retry.generate_opc_retry_token
98
+
99
+ # Set the payload
100
+ if payload.respond_to?(:read) && payload.respond_to?(:write)
101
+ request.body_stream = payload
102
+ else
103
+ request.body = payload
104
+ end
105
+
106
+ # Sign the request
107
+ signer.sign(http_method, url, header_params, payload, operation_signing_strategy)
108
+
109
+ # Add header parameters
110
+ header_params.each do |key, value|
111
+ request[key.to_s] = value
112
+ end
113
+
114
+ # Set HTTP parameters
115
+ http = Net::HTTP.new(uri.hostname, uri.port)
116
+ http.use_ssl = (uri.scheme == 'https')
117
+ http.open_timeout = 10
118
+ http.read_timeout = 180 # 180 seconds = 3 minutes
119
+ http.continue_timeout = 3 # expect 100 continue timeout
120
+
121
+ # Fetch response
122
+ retry_count = 0
123
+ response = nil
124
+
125
+ begin
126
+ http.start do
127
+ http.request(request) do |resp|
128
+ response = resp
129
+ end
130
+ end
131
+ rescue StandardError => e
132
+ code_from_response = !response.nil? ? response.code.to_i : 0
133
+
134
+ if (RETRYABLE_STATUS_CODE.include? code_from_response) || RETRYABLE_STATUS_CODE.include?(code_from_response / 100) || e.instance_of?(Net::ReadTimeout)
135
+ if retry_count < MAX_ATTEMPTS
136
+ # If e = 2, and initial delay is 10, intervals will be (1, 2, 4, 8, 16)
137
+ sleep_interval = EXPONENT_CONSTANT.pow(retry_count) * INITIAL_DELAY
138
+ logger.error("Error while uploading data to discovery API (raw request). Retrying in #{sleep_interval} seconds. HTTP status code: #{code_from_response}")
139
+ sleep(sleep_interval)
140
+ retry_count += 1
141
+ retry
142
+ end
143
+ end
144
+ raise OCI::Errors::NetworkError.new(e.message, code_from_response, request_made: request, response_received: response)
145
+ end
146
+
147
+ if response.is_a?(Net::HTTPSuccess)
148
+ return OCI::Response.new(response.code.to_i, response.header, nil)
149
+ end # Success safe check
150
+
151
+ raise "Response from Discovery API (raw request) suggests failure. Response code: + #{response.code}"
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,96 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require 'logger'
5
+ require 'yajl'
6
+
7
+ module Util
8
+ module Logging
9
+ module_function
10
+
11
+ TRACE = Logger::DEBUG - 1
12
+ SEV_LABEL = {
13
+ -1 => 'TRACE',
14
+ 0 => 'DEBUG',
15
+ 1 => 'INFO',
16
+ 2 => 'WARN',
17
+ 3 => 'ERROR',
18
+ 4 => 'FATAL'
19
+ }.freeze
20
+
21
+ def logger
22
+ log_level = LogConfigs.log_level
23
+ log_format = LogConfigs.log_format
24
+ datetime_format = '%Y-%m-%d %H:%M:%S %z'
25
+
26
+ case log_format
27
+ when 'text'
28
+ @logger = OCILogger.new(STDOUT,
29
+ level: log_level,
30
+ datetime_format: datetime_format,
31
+ formatter: proc do |type, time, _level, msg|
32
+ r = "#{time} [#{type.downcase}] #{self} "
33
+ r << "#{msg}\n"
34
+ r
35
+ end)
36
+ when 'json'
37
+ @logger = OCILogger.new(STDOUT,
38
+ level: log_level,
39
+ datetime_format: datetime_format,
40
+ formatter: proc do |type, time, _level, msg|
41
+ r = {
42
+ 'time' => time,
43
+ 'level' => type.downcase,
44
+ 'class' => self,
45
+ 'message' => msg
46
+ }
47
+ # JSON.pretty_generate(r, :indent => "\t") #
48
+ Yajl.dump(r).gsub('}', "}\n")
49
+ # r.to_json.gsub('}', "}\n")
50
+ end)
51
+ else
52
+ puts 'Logger type only text or json allowed'
53
+ end
54
+ end
55
+
56
+ def logger=(logger)
57
+ @logger = logger
58
+ end
59
+
60
+ class OCILogger < Logger
61
+ def format_severity(severity)
62
+ SEV_LABEL[severity] || 'ANY'
63
+ end
64
+
65
+ def trace(message)
66
+ add(TRACE, message)
67
+ end
68
+ end
69
+
70
+ class LogConfigs
71
+ @@log_format
72
+ @@log_level
73
+
74
+ def initialize(l_format, l_level)
75
+ @@log_format = l_format
76
+ @@log_level = l_level
77
+ end
78
+
79
+ def self.log_format
80
+ @@log_format
81
+ end
82
+
83
+ def log_format
84
+ @@log_format
85
+ end
86
+
87
+ def self.log_level
88
+ @@log_level
89
+ end
90
+
91
+ def log_level
92
+ @@log_level
93
+ end
94
+ end
95
+ end
96
+ end