oci-logging-analytics-kubernetes-discovery 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|