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.
- checksums.yaml +7 -0
- data/.gitignore +40 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +36 -0
- data/README.md +83 -0
- data/Rakefile +15 -0
- data/bin/console +17 -0
- data/bin/oci-loganalytics-kubernetes-discovery +184 -0
- data/bin/setup +12 -0
- data/lib/config/oci_client_retry_config.rb +34 -0
- data/lib/discover/infrastructure.rb +122 -0
- data/lib/discover/object.rb +347 -0
- data/lib/dto/infra/cluster_entity_payload.rb +22 -0
- data/lib/dto/infra/load_balancers_entity_payload.rb +22 -0
- data/lib/dto/infra/node_pool_entity_payload.rb +24 -0
- data/lib/dto/infra/subnet_entity_payload.rb +22 -0
- data/lib/dto/infra/vcn_entity_payload.rb +22 -0
- data/lib/dto/infra_objects_payload.rb +40 -0
- data/lib/dto/kubernetes_objects_payload.rb +58 -0
- data/lib/dto/payload/log_events.rb +26 -0
- data/lib/dto/payload/log_events_json.rb +22 -0
- data/lib/dto/state.rb +19 -0
- data/lib/enum/auth_type_enum.rb +9 -0
- data/lib/enum/infrastructure_resource_discovery.rb +9 -0
- data/lib/enum/kubernetes_objects_enum.rb +22 -0
- data/lib/enum/object_client_mapping_enum.rb +21 -0
- data/lib/infra_resources.rb +91 -0
- data/lib/objects_resources.rb +174 -0
- data/lib/oci_loganalytics_resources_discovery.rb +293 -0
- data/lib/util/kube_client.rb +141 -0
- data/lib/util/kubectl_ops.rb +229 -0
- data/lib/util/log_analytics.rb +154 -0
- data/lib/util/logging.rb +96 -0
- data/lib/util/oci_clients.rb +228 -0
- data/lib/util/state_manager.rb +61 -0
- data/lib/util/string_utils.rb +16 -0
- data/lib/version.rb +6 -0
- data/oci-logging-analytics-kubernetes-discovery.gemspec +45 -0
- 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
|
data/lib/util/logging.rb
ADDED
@@ -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
|