aws_advanced_ruby_driver_wrapper 1.0.1 → 1.1.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 +4 -4
- data/CHANGELOG.md +32 -2
- data/README.md +115 -6
- data/THIRD-PARTY-LICENSES +70 -11
- data/aws_advanced_ruby_driver_wrapper.gemspec +9 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +17 -3
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_trilogy_adapter.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +11 -4
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql2_driver_dialect.rb +129 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +9 -91
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +18 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/trilogy_driver_dialect.rb +118 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +16 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +10 -5
- data/lib/aws_advanced_ruby_driver_wrapper/errors/trilogy_error_handler.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +27 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +115 -7
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/drop_blue_connections_marker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +3 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +7 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +68 -41
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +4 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +5 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +7 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +6 -13
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +90 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +11 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +14 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +68 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/base_saml_auth_plugin.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/federated_auth_plugin.rb +134 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/okta_auth_plugin.rb +113 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +30 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +16 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +6 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +29 -6
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_mysql.sql +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_pg.sql +54 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +14 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +41 -23
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +63 -1
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +3 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +20 -7
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +34 -8
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +80 -29
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +10 -2
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/trilogy.rb +167 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +25 -15
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +2 -2
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +15 -31
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +55 -76
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_text.rb +215 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +11 -20
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +2 -3
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +21 -14
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +2 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/saml_utils.rb +133 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +9 -5
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/null_telemetry.rb +89 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_context.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_counter.rb +39 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_factory.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_gauge.rb +36 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_const.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_factory.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +9 -2
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper.rb +9 -2
- metadata +54 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
-- pgcrypto provides hmac(). If it is already installed in a schema other than public,
|
|
2
|
+
-- replace public in public.hmac below with that schema.
|
|
3
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
|
|
4
|
+
|
|
5
|
+
-- Replace 'encrypt' below if encryption_metadata_schema is set to something else.
|
|
6
|
+
-- A trigger runs with the search_path of the session that fires it. Pinning it to pg_catalog
|
|
7
|
+
-- stops a session from putting its own functions or operators, such as hmac() or <>, ahead of
|
|
8
|
+
-- the built-in ones to get a plaintext value past this check.
|
|
9
|
+
CREATE OR REPLACE FUNCTION enforce_encrypted_column() RETURNS trigger
|
|
10
|
+
SET search_path = pg_catalog, pg_temp
|
|
11
|
+
AS $$
|
|
12
|
+
DECLARE
|
|
13
|
+
col_name text := TG_ARGV[0];
|
|
14
|
+
col_value bytea;
|
|
15
|
+
hmac_key bytea;
|
|
16
|
+
unchanged boolean;
|
|
17
|
+
BEGIN
|
|
18
|
+
-- An UPDATE that leaves the encrypted value as it was is not re-checked; see
|
|
19
|
+
-- "Updates after a key rotation".
|
|
20
|
+
IF TG_OP = 'UPDATE' THEN
|
|
21
|
+
EXECUTE format('SELECT ($1).%I IS NOT DISTINCT FROM ($2).%I', col_name, col_name)
|
|
22
|
+
INTO unchanged USING NEW, OLD;
|
|
23
|
+
IF unchanged THEN
|
|
24
|
+
RETURN NEW;
|
|
25
|
+
END IF;
|
|
26
|
+
END IF;
|
|
27
|
+
|
|
28
|
+
EXECUTE format('SELECT ($1).%I', col_name) INTO col_value USING NEW;
|
|
29
|
+
IF col_value IS NULL THEN
|
|
30
|
+
RETURN NEW;
|
|
31
|
+
END IF;
|
|
32
|
+
|
|
33
|
+
-- Read the column's current HMAC key from key_storage.
|
|
34
|
+
SELECT ks.hmac_key INTO hmac_key
|
|
35
|
+
FROM encrypt.encryption_metadata em
|
|
36
|
+
JOIN encrypt.key_storage ks ON em.key_id = ks.id
|
|
37
|
+
WHERE em.table_name = TG_TABLE_NAME AND em.column_name = col_name;
|
|
38
|
+
|
|
39
|
+
IF hmac_key IS NULL THEN
|
|
40
|
+
RAISE EXCEPTION 'No HMAC key is configured for %.%', TG_TABLE_NAME, col_name;
|
|
41
|
+
END IF;
|
|
42
|
+
|
|
43
|
+
IF length(col_value) < 65
|
|
44
|
+
OR substring(col_value from 1 for 32) <> public.hmac(substring(col_value from 33), hmac_key, 'sha256'::text) THEN
|
|
45
|
+
RAISE EXCEPTION 'Column %.% does not carry a valid HMAC tag (plaintext or tampered value)', TG_TABLE_NAME, col_name;
|
|
46
|
+
END IF;
|
|
47
|
+
|
|
48
|
+
RETURN NEW;
|
|
49
|
+
END;
|
|
50
|
+
$$ LANGUAGE plpgsql;
|
|
51
|
+
|
|
52
|
+
-- Records which version of this SQL is installed. Check it with:
|
|
53
|
+
-- SELECT obj_description('enforce_encrypted_column'::regproc, 'pg_proc');
|
|
54
|
+
COMMENT ON FUNCTION enforce_encrypted_column() IS 'aws-advanced-ruby-driver-wrapper kms_encryption trigger v2';
|
|
@@ -19,6 +19,7 @@ require_relative '../errors'
|
|
|
19
19
|
require_relative '../logging'
|
|
20
20
|
require_relative '../property_definition'
|
|
21
21
|
require_relative '../utils/aws_credentials_utils'
|
|
22
|
+
require_relative '../utils/telemetry/telemetry_const'
|
|
22
23
|
|
|
23
24
|
module AwsAdvancedRubyDriverWrapper
|
|
24
25
|
module Plugins
|
|
@@ -27,6 +28,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
27
28
|
|
|
28
29
|
SUBSCRIBED_METHODS = Set['connect', 'internal_connect'].freeze
|
|
29
30
|
SECRETS_MANAGER_CACHE_NAME = :secrets_manager
|
|
31
|
+
TELEMETRY_FETCH_CREDENTIALS = 'fetch credentials'
|
|
30
32
|
MIN_EXPIRATION_SEC = 300
|
|
31
33
|
SYNC_FETCH_TIMEOUT_SEC = 60
|
|
32
34
|
# The partition segment of an ARN varies across AWS partitions, so match any of them.
|
|
@@ -126,6 +128,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
126
128
|
)
|
|
127
129
|
|
|
128
130
|
@subscribed_methods = SUBSCRIBED_METHODS
|
|
131
|
+
@telemetry_factory = service_container.telemetry_factory
|
|
132
|
+
@fetch_credentials_counter = @telemetry_factory.create_counter('secrets_manager.fetch_credentials.count')
|
|
129
133
|
end
|
|
130
134
|
|
|
131
135
|
def connect(_host_info, driver_props, _is_initial_connection, pipeline_callable)
|
|
@@ -211,7 +215,17 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
211
215
|
end
|
|
212
216
|
|
|
213
217
|
# Returns true if credentials were freshly fetched from the service (suppresses login-error retry).
|
|
218
|
+
#
|
|
219
|
+
# Each lookup is counted and recorded as a span, whether it is answered from the cache or from
|
|
220
|
+
# the service.
|
|
214
221
|
def fetch_secret_and_report_if_fresh?(force:)
|
|
222
|
+
@fetch_credentials_counter.inc
|
|
223
|
+
@telemetry_factory.in_context(TELEMETRY_FETCH_CREDENTIALS, Utils::Telemetry::TraceLevel::NESTED, record_failure: true) do
|
|
224
|
+
load_secret_and_report_if_fresh?(force:)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def load_secret_and_report_if_fresh?(force:)
|
|
215
229
|
credentials = Utils::AwsCredentialsUtils.snapshot(@credentials_provider)
|
|
216
230
|
cache_key = secret_cache_key(credentials)
|
|
217
231
|
entry = cache_key && @service_container.storage_service.get(SECRETS_MANAGER_CACHE_NAME, cache_key)
|
|
@@ -20,6 +20,7 @@ require_relative '../aws_advanced_ruby_driver_wrapper'
|
|
|
20
20
|
require_relative 'utils/connection_config_parser'
|
|
21
21
|
require_relative 'utils/sql_encoding'
|
|
22
22
|
require_relative 'services/service_utility'
|
|
23
|
+
require_relative 'utils/telemetry/telemetry_factory'
|
|
23
24
|
require_relative 'ruby_method'
|
|
24
25
|
require_relative 'errors'
|
|
25
26
|
|
|
@@ -45,17 +46,20 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
45
46
|
def initialize(*, **)
|
|
46
47
|
ensure_pg!
|
|
47
48
|
config = Utils::ConnectionConfigParser.parse(:postgresql, *, **)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
telemetry_factory = Utils::Telemetry::TelemetryFactory.new(config.wrapper_props)
|
|
50
|
+
telemetry_factory.in_context("#{self.class.name}.new", Utils::Telemetry::TraceLevel::TOP_LEVEL, record_result: true) do
|
|
51
|
+
@service_container = Services::ServiceUtility.create_standard_container(config, telemetry_factory:)
|
|
52
|
+
@service_container.host_service.refresh_host_list
|
|
53
|
+
@prepared_on = {}
|
|
54
|
+
@prepared_sql = {}
|
|
55
|
+
@async_conn = nil
|
|
56
|
+
@async_sql = nil
|
|
57
|
+
@copy_conn = nil
|
|
58
|
+
@copy_sql = nil
|
|
59
|
+
@lo_conn = nil
|
|
60
|
+
conn_service = @service_container.connection_service
|
|
61
|
+
@service_container.plugin_manager.connect(conn_service.initial_host_info, conn_service.driver_props, true)
|
|
62
|
+
end
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
# Every canonical pg operation that talks to the server, and everything this class has to know about one: the
|
|
@@ -173,6 +177,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
173
177
|
lotell: :lo_tell, lotruncate: :lo_truncate
|
|
174
178
|
}.freeze
|
|
175
179
|
|
|
180
|
+
# The operations pg yields the result of when given a block, clearing it once the block returns and
|
|
181
|
+
# returning the block's value in its place.
|
|
182
|
+
YIELDS_RESULT = %i[exec async_exec exec_params exec_prepared].freeze
|
|
183
|
+
|
|
176
184
|
# A statement can also be prepared by sending a +PREPARE+ rather than by calling pg's own
|
|
177
185
|
# +prepare+, and the +exec_prepared+ that runs it looks no different either way. The two are read
|
|
178
186
|
# here so that a statement prepared the first way is remembered like one prepared the second, and
|
|
@@ -194,24 +202,24 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
194
202
|
|
|
195
203
|
# Explicitly define critical methods (bypass method_missing to avoid method_missing overhead).
|
|
196
204
|
|
|
197
|
-
def exec(sql, *params)
|
|
198
|
-
execute_operation(:exec, [sql, *params])
|
|
205
|
+
def exec(sql, *params, &)
|
|
206
|
+
execute_operation(:exec, [sql, *params], &)
|
|
199
207
|
end
|
|
200
208
|
|
|
201
209
|
# pg spells this operation +exec+, +query+, +async_exec+ and +async_query+, all of which run the
|
|
202
210
|
# same libpq call. +query+ is defined here rather than left to method_missing because it is the
|
|
203
211
|
# spelling applications use most after +exec+, and it enters the pipeline as +connection.exec+,
|
|
204
212
|
# since that is the libpq operation being performed.
|
|
205
|
-
def query(sql, *params)
|
|
206
|
-
execute_operation(:exec, [sql, *params], spelling: :query)
|
|
213
|
+
def query(sql, *params, &)
|
|
214
|
+
execute_operation(:exec, [sql, *params], spelling: :query, &)
|
|
207
215
|
end
|
|
208
216
|
|
|
209
|
-
def exec_params(sql, params, result_format = 0, type_map = nil)
|
|
210
|
-
execute_operation(:exec_params, [sql, params, result_format, type_map])
|
|
217
|
+
def exec_params(sql, params, result_format = 0, type_map = nil, &)
|
|
218
|
+
execute_operation(:exec_params, [sql, params, result_format, type_map], &)
|
|
211
219
|
end
|
|
212
220
|
|
|
213
|
-
def async_exec(sql, *params)
|
|
214
|
-
execute_operation(:async_exec, [sql, *params])
|
|
221
|
+
def async_exec(sql, *params, &)
|
|
222
|
+
execute_operation(:async_exec, [sql, *params], &)
|
|
215
223
|
end
|
|
216
224
|
|
|
217
225
|
def transaction(&)
|
|
@@ -250,8 +258,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
250
258
|
execute_operation(:send_prepare, [stmt_name, sql, param_types])
|
|
251
259
|
end
|
|
252
260
|
|
|
253
|
-
def exec_prepared(stmt_name, params = [], result_format = 0, type_map = nil)
|
|
254
|
-
execute_operation(:exec_prepared, [stmt_name, params, result_format, type_map])
|
|
261
|
+
def exec_prepared(stmt_name, params = [], result_format = 0, type_map = nil, &)
|
|
262
|
+
execute_operation(:exec_prepared, [stmt_name, params, result_format, type_map], &)
|
|
255
263
|
end
|
|
256
264
|
|
|
257
265
|
def describe_prepared(stmt_name)
|
|
@@ -312,7 +320,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
312
320
|
|
|
313
321
|
def method_missing(method_name, *args, **kwargs, &)
|
|
314
322
|
conn = current_conn
|
|
315
|
-
raise
|
|
323
|
+
raise PG::ConnectionBad, 'connection is not initialized' if conn.nil?
|
|
316
324
|
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless conn.respond_to?(method_name)
|
|
317
325
|
|
|
318
326
|
operation = operation_for(method_name)
|
|
@@ -344,10 +352,14 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
344
352
|
# made, since an `after` step may be what forgets it, and it is handed to the result as well, so that a
|
|
345
353
|
# plugin which has to inspect the statement still sees it when the rows are read.
|
|
346
354
|
def execute_operation(operation, args = [], kwargs = {}, spelling: operation, &)
|
|
355
|
+
if block_given? && YIELDS_RESULT.include?(operation)
|
|
356
|
+
return yield_result(execute_operation(operation, args, kwargs, spelling: spelling), &)
|
|
357
|
+
end
|
|
358
|
+
|
|
347
359
|
spec = OPERATIONS[operation] || { method: "connection.#{operation}" }
|
|
348
360
|
conn = current_conn
|
|
349
361
|
# Guard against a missing connection. Fail loudly instead.
|
|
350
|
-
raise
|
|
362
|
+
raise PG::ConnectionBad, 'connection is not initialized' if conn.nil?
|
|
351
363
|
|
|
352
364
|
# Only forward keyword arguments when there are any.
|
|
353
365
|
sql = sql_for(spec, args)
|
|
@@ -518,6 +530,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
518
530
|
@network_bound_methods ||= driver_dialect.network_bound_methods
|
|
519
531
|
end
|
|
520
532
|
|
|
533
|
+
def yield_result(result)
|
|
534
|
+
yield result
|
|
535
|
+
ensure
|
|
536
|
+
result.clear
|
|
537
|
+
end
|
|
538
|
+
|
|
521
539
|
def wrap_pg_result(result, sql = nil)
|
|
522
540
|
return result unless result.is_a?(PG::Result)
|
|
523
541
|
|
|
@@ -54,8 +54,41 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
54
54
|
type: String)
|
|
55
55
|
DIALECT = WrapperProperty.new(:wrapper_dialect, 'The database dialect identifier for the database in use.', type: String)
|
|
56
56
|
AWS_CREDENTIALS_PROVIDER = WrapperProperty.new(:aws_credentials_provider,
|
|
57
|
-
'AWS credentials provider for
|
|
57
|
+
'AWS credentials provider for the plugins that call AWS services',
|
|
58
58
|
default_value: nil)
|
|
59
|
+
# -- Telemetry --
|
|
60
|
+
ENABLE_TELEMETRY = WrapperProperty.new(
|
|
61
|
+
:enable_telemetry,
|
|
62
|
+
'Enables telemetry and observability of the wrapper.',
|
|
63
|
+
default_value: false,
|
|
64
|
+
type: :boolean
|
|
65
|
+
)
|
|
66
|
+
TELEMETRY_SUBMIT_TOP_LEVEL = WrapperProperty.new(
|
|
67
|
+
:telemetry_submit_top_level,
|
|
68
|
+
'Submit the traces of wrapper calls as top-level traces, rather than only as children of a trace already in progress.',
|
|
69
|
+
default_value: false,
|
|
70
|
+
type: :boolean
|
|
71
|
+
)
|
|
72
|
+
TELEMETRY_TRACES_BACKEND = WrapperProperty.new(
|
|
73
|
+
:telemetry_traces_backend,
|
|
74
|
+
'Method to export the telemetry traces of the wrapper. Valid values are otlp and none.',
|
|
75
|
+
default_value: nil,
|
|
76
|
+
type: String
|
|
77
|
+
)
|
|
78
|
+
TELEMETRY_METRICS_BACKEND = WrapperProperty.new(
|
|
79
|
+
:telemetry_metrics_backend,
|
|
80
|
+
'Method to export the telemetry metrics of the wrapper. Valid values are otlp and none.',
|
|
81
|
+
default_value: nil,
|
|
82
|
+
type: String
|
|
83
|
+
)
|
|
84
|
+
TELEMETRY_FAILOVER_ADDITIONAL_TOP_TRACE = WrapperProperty.new(
|
|
85
|
+
:telemetry_failover_additional_top_trace,
|
|
86
|
+
'Also submit the trace of each failover as a top-level trace of its own. Only a failover that was ' \
|
|
87
|
+
'traced is submitted: one inside a trace in progress, or inside a wrapper call that was submitted ' \
|
|
88
|
+
'as a top-level trace.',
|
|
89
|
+
default_value: false,
|
|
90
|
+
type: :boolean
|
|
91
|
+
)
|
|
59
92
|
# -- Failover --
|
|
60
93
|
FAILOVER_TIMEOUT_SEC = WrapperProperty.new(
|
|
61
94
|
:failover_timeout_sec,
|
|
@@ -159,6 +192,15 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
159
192
|
default_value: 0.1, type: Float, validator: POSITIVE_FLOAT)
|
|
160
193
|
BG_SWITCHOVER_TIMEOUT_SEC = WrapperProperty.new(:bg_switchover_timeout_sec, 'Blue/Green Deployment switchover timeout (in seconds).',
|
|
161
194
|
default_value: 180.0, type: Float, validator: POSITIVE_FLOAT)
|
|
195
|
+
BG_DROP_BLUE_CONNECTIONS = WrapperProperty.new(
|
|
196
|
+
:bg_drop_blue_connections,
|
|
197
|
+
'Whether to invalidate connections that were opened to the old blue environment once a Blue/Green ' \
|
|
198
|
+
'switchover completes. When enabled, the next use of such a connection closes it: with the failover ' \
|
|
199
|
+
'plugin enabled it reconnects through Blue/Green routing and raises FailoverSuccessError, and without ' \
|
|
200
|
+
'failover the driver raises its closed-connection error. Either way the caller gets an explicit error ' \
|
|
201
|
+
'instead of a stale read from old blue.',
|
|
202
|
+
default_value: true, type: :boolean
|
|
203
|
+
)
|
|
162
204
|
|
|
163
205
|
# -- IAM Authentication --
|
|
164
206
|
IAM_HOST = WrapperProperty.new(:iam_host, 'Overrides the host used to generate the IAM token', default_value: nil, type: String)
|
|
@@ -171,6 +213,26 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
171
213
|
IAM_ACCESS_TOKEN_PROPERTY_NAME = WrapperProperty.new(:iam_access_token_property_name, 'Property name used to pass the IAM token',
|
|
172
214
|
default_value: :password, type: Symbol)
|
|
173
215
|
|
|
216
|
+
# -- Federated / Okta Authentication (SAML) --
|
|
217
|
+
IDP_ENDPOINT = WrapperProperty.new(:idp_endpoint, 'The hosting URL of the Identity Provider', default_value: nil, type: String)
|
|
218
|
+
IDP_PORT = WrapperProperty.new(:idp_port, 'The hosting port of the Identity Provider', default_value: 443, type: Integer)
|
|
219
|
+
IDP_USERNAME = WrapperProperty.new(:idp_username, 'The federated user name', default_value: nil, type: String)
|
|
220
|
+
IDP_PASSWORD = WrapperProperty.new(:idp_password, 'The federated user password', default_value: nil, type: String)
|
|
221
|
+
IAM_ROLE_ARN = WrapperProperty.new(:iam_role_arn, 'The ARN of the IAM Role that is to be assumed', default_value: nil, type: String)
|
|
222
|
+
IAM_IDP_ARN = WrapperProperty.new(:iam_idp_arn, 'The ARN of the Identity Provider', default_value: nil, type: String)
|
|
223
|
+
RP_IDENTIFIER = WrapperProperty.new(:rp_identifier, 'The relying party identifier', default_value: 'urn:amazon:webservices',
|
|
224
|
+
type: String)
|
|
225
|
+
DB_USER = WrapperProperty.new(:db_user, 'The database user used to access the database', default_value: nil, type: String)
|
|
226
|
+
SSL_INSECURE = WrapperProperty.new(:ssl_insecure, "Whether the IdP server's TLS certificate chain should not be verified",
|
|
227
|
+
default_value: false, type: :boolean)
|
|
228
|
+
APP_ID = WrapperProperty.new(:app_id, 'The ID of the AWS application configured on Okta', default_value: nil, type: String)
|
|
229
|
+
HTTP_CLIENT_SOCKET_TIMEOUT_SEC = WrapperProperty.new(:http_client_socket_timeout_sec,
|
|
230
|
+
'Socket timeout in seconds for IdP HTTP requests',
|
|
231
|
+
default_value: 60.0, type: Float, validator: POSITIVE_FLOAT)
|
|
232
|
+
HTTP_CLIENT_CONNECT_TIMEOUT_SEC = WrapperProperty.new(:http_client_connect_timeout_sec,
|
|
233
|
+
'Connect timeout in seconds for IdP HTTP requests',
|
|
234
|
+
default_value: 60.0, type: Float, validator: POSITIVE_FLOAT)
|
|
235
|
+
|
|
174
236
|
# -- Initial Connection Strategy --
|
|
175
237
|
INITIAL_CONNECTION_SUBSTITUTE_HOST = WrapperProperty.new(
|
|
176
238
|
:initial_connection_substitute_host,
|
|
@@ -46,6 +46,9 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
46
46
|
CONNECTION_ABANDON_RESULTS = define('connection.abandon_results!', check_bounded_connection: true)
|
|
47
47
|
CONNECTION_SET_SERVER_OPTION = define('connection.set_server_option', check_bounded_connection: false)
|
|
48
48
|
|
|
49
|
+
# -- Connection methods (trilogy-specific) --
|
|
50
|
+
CONNECTION_CHANGE_DB = define('connection.change_db', check_bounded_connection: false)
|
|
51
|
+
|
|
49
52
|
# -- Connection methods (pg-specific) --
|
|
50
53
|
CONNECTION_EXEC = define('connection.exec', check_bounded_connection: false)
|
|
51
54
|
CONNECTION_ASYNC_EXEC = define('connection.async_exec', check_bounded_connection: false)
|
|
@@ -61,7 +61,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
61
61
|
}.freeze
|
|
62
62
|
|
|
63
63
|
# @param connection_service [ConnectionService]
|
|
64
|
-
# @param driver_name [Symbol] :mysql2 or :postgresql
|
|
64
|
+
# @param driver_name [Symbol] :mysql2, :trilogy or :postgresql
|
|
65
65
|
def initialize(connection_service, driver_name)
|
|
66
66
|
@connection_service = connection_service
|
|
67
67
|
@dialect_cache = {}
|
|
@@ -164,6 +164,21 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
164
164
|
@error_handler.read_only_error?(error)
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
+
# Whether the driver reported the error as a database error (it carries a SQLSTATE, or for
|
|
168
|
+
# trilogy a MySQL error code). Not a strict server-vs-client split; see ErrorHandler#database_error?.
|
|
169
|
+
# @param error [Exception]
|
|
170
|
+
# @return [Boolean]
|
|
171
|
+
def database_error?(error)
|
|
172
|
+
@error_handler.database_error?(error)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Whether the error is a schema/syntax error (SQLSTATE class '42' or its per-driver equivalent).
|
|
176
|
+
# @param error [Exception]
|
|
177
|
+
# @return [Boolean]
|
|
178
|
+
def schema_error?(error)
|
|
179
|
+
@error_handler.schema_error?(error)
|
|
180
|
+
end
|
|
181
|
+
|
|
167
182
|
private
|
|
168
183
|
|
|
169
184
|
# Resolves the initial database dialect from the initial connection info.
|
|
@@ -205,12 +220,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
205
220
|
|
|
206
221
|
rds_type = Utils::RdsUtils.identify_rds_type(host)
|
|
207
222
|
|
|
208
|
-
@dialect_code =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
else
|
|
213
|
-
DialectCodes::UNKNOWN
|
|
223
|
+
@dialect_code = case @driver_dialect.database_engine
|
|
224
|
+
when :mysql then resolve_mysql_dialect(rds_type)
|
|
225
|
+
when :postgresql then resolve_pg_dialect(rds_type)
|
|
226
|
+
else DialectCodes::UNKNOWN
|
|
214
227
|
end
|
|
215
228
|
|
|
216
229
|
@db_dialect = dialect_for_code(@dialect_code)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
require 'concurrent/map'
|
|
18
|
+
require_relative '../errors'
|
|
18
19
|
require_relative '../host/random_host_selector'
|
|
19
20
|
|
|
20
21
|
module AwsAdvancedRubyDriverWrapper
|
|
@@ -47,18 +48,45 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
47
48
|
# Register a non-default host selector. The selector is shared by every HostService in the process,
|
|
48
49
|
# so it must be safe to call from multiple threads.
|
|
49
50
|
#
|
|
51
|
+
# Strategy names are case-insensitive: they are normalized here and in #host_selector, so a
|
|
52
|
+
# selector registered as 'LeastLag' is found when a connection names 'leastlag'.
|
|
53
|
+
#
|
|
50
54
|
# @param name [String] strategy name
|
|
51
55
|
# @param selector [#select_host] any object responding to select_host(hosts, role, props)
|
|
52
56
|
def register_host_selector(name, selector)
|
|
53
|
-
|
|
57
|
+
normalized = normalize_strategy(name)
|
|
58
|
+
raise Errors::AwsError, "Cannot override default host selection strategy: '#{name}'" if DEFAULT_HOST_SELECTORS.key?(normalized)
|
|
54
59
|
|
|
55
|
-
@strategies[
|
|
60
|
+
@strategies[normalized] = selector
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
# @param name [String] strategy name
|
|
59
64
|
# @return [#select_host, nil] the registered selector, or nil if the name is unknown
|
|
60
65
|
def host_selector(name)
|
|
61
|
-
@strategies[name]
|
|
66
|
+
@strategies[normalize_strategy(name)]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Raises when no selector is registered under the given strategy name. Plugins call this when
|
|
70
|
+
# they are constructed, because they treat a selection failure during connect or failover as
|
|
71
|
+
# "no eligible host", which would otherwise hide a misspelled strategy.
|
|
72
|
+
#
|
|
73
|
+
# @param name [String] strategy name
|
|
74
|
+
def validate_strategy!(name)
|
|
75
|
+
raise unsupported_strategy_error(name) if host_selector(name).nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def normalize_strategy(name)
|
|
79
|
+
name.to_s.strip.downcase
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param name [String] the strategy name as configured, reported without normalization
|
|
83
|
+
# @return [Errors::AwsError]
|
|
84
|
+
def unsupported_strategy_error(name)
|
|
85
|
+
Errors::AwsError.new(
|
|
86
|
+
"Unsupported host selection strategy: '#{name}'. Registered strategies: #{@strategies.keys.sort.join(', ')}. " \
|
|
87
|
+
'Register a custom strategy with AwsAdvancedRubyDriverWrapper::Services::HostService.register_host_selector ' \
|
|
88
|
+
'before opening a connection that uses it.'
|
|
89
|
+
)
|
|
62
90
|
end
|
|
63
91
|
|
|
64
92
|
# Removes every non-default host selector. For testing only.
|
|
@@ -76,9 +104,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
76
104
|
# @param props [Hash, nil]
|
|
77
105
|
# @return [Host::HostInfo]
|
|
78
106
|
def select_host(hosts, role, strategy, props = nil)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
raise Errors::AwsError, "Unsupported host selection strategy: '#{strategy}'" if selector.nil?
|
|
107
|
+
selector = self.class.host_selector(strategy)
|
|
108
|
+
raise self.class.unsupported_strategy_error(strategy) if selector.nil?
|
|
82
109
|
|
|
83
110
|
selector.select_host(hosts, role, props)
|
|
84
111
|
end
|
|
@@ -90,8 +117,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
90
117
|
def hosts
|
|
91
118
|
rules = @service_container.storage_service.get_if_registered(
|
|
92
119
|
:custom_endpoint_allowed_blocked,
|
|
93
|
-
@service_container.connection_service.initial_host_info&.url
|
|
94
|
-
register_access: false
|
|
120
|
+
@service_container.connection_service.initial_host_info&.url
|
|
95
121
|
)
|
|
96
122
|
return @all_hosts if rules.nil?
|
|
97
123
|
|
|
@@ -144,7 +144,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
144
144
|
# Processes events from the event publisher.
|
|
145
145
|
# @param event [Event] the event to process.
|
|
146
146
|
def process_event(event)
|
|
147
|
-
return unless event
|
|
147
|
+
return unless event.is_a?(Utils::Events::DataAccessEvent)
|
|
148
148
|
|
|
149
149
|
handle_data_access_event(event)
|
|
150
150
|
end
|
|
@@ -19,10 +19,13 @@ require_relative '../errors'
|
|
|
19
19
|
require_relative '../property_definition'
|
|
20
20
|
require_relative '../ruby_method'
|
|
21
21
|
require_relative '../utils/sql_encoding'
|
|
22
|
+
require_relative '../utils/telemetry/telemetry_const'
|
|
22
23
|
require_relative '../plugins/default_plugin'
|
|
23
24
|
require_relative '../plugins/failover_plugin'
|
|
24
25
|
require_relative '../plugins/gdb/gdb_failover_plugin'
|
|
25
26
|
require_relative '../plugins/iam_auth_plugin'
|
|
27
|
+
require_relative '../plugins/federated_auth/federated_auth_plugin'
|
|
28
|
+
require_relative '../plugins/federated_auth/okta_auth_plugin'
|
|
26
29
|
require_relative '../plugins/initial_connection_strategy_plugin'
|
|
27
30
|
require_relative '../plugins/kms_encryption/kms_encryption_plugin'
|
|
28
31
|
require_relative '../plugins/secrets_manager_plugin'
|
|
@@ -44,6 +47,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
44
47
|
'failover' => Plugins::FailoverPlugin,
|
|
45
48
|
'gdb_failover' => Plugins::Gdb::GdbFailoverPlugin,
|
|
46
49
|
'iam' => Plugins::IamAuthPlugin,
|
|
50
|
+
'federated_auth' => Plugins::FederatedAuth::FederatedAuthPlugin,
|
|
51
|
+
'okta' => Plugins::FederatedAuth::OktaAuthPlugin,
|
|
47
52
|
'initial_connection' => Plugins::InitialConnectionStrategyPlugin,
|
|
48
53
|
'kms_encryption' => Plugins::KmsEncryptionPlugin,
|
|
49
54
|
'secrets_manager' => Plugins::SecretsManagerPlugin
|
|
@@ -60,9 +65,18 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
60
65
|
Plugins::Gdb::GdbFailoverPlugin => 500,
|
|
61
66
|
Plugins::IamAuthPlugin => 1800,
|
|
62
67
|
Plugins::SecretsManagerPlugin => 1900,
|
|
68
|
+
Plugins::FederatedAuth::FederatedAuthPlugin => 2000,
|
|
69
|
+
Plugins::FederatedAuth::OktaAuthPlugin => 2010,
|
|
63
70
|
Plugins::KmsEncryptionPlugin => 2050
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
# Plugins that authenticate by minting an RDS IAM token into the shared IAM token cache.
|
|
74
|
+
IAM_TOKEN_PLUGIN_CLASSES = [
|
|
75
|
+
Plugins::IamAuthPlugin,
|
|
76
|
+
Plugins::FederatedAuth::FederatedAuthPlugin,
|
|
77
|
+
Plugins::FederatedAuth::OktaAuthPlugin
|
|
78
|
+
].freeze
|
|
79
|
+
|
|
66
80
|
class << self
|
|
67
81
|
def register_plugin(plugin_code, plugin_class, weight: WEIGHT_RELATIVE_TO_PRIOR_PLUGIN)
|
|
68
82
|
@plugin_classes[plugin_code] = plugin_class
|
|
@@ -73,30 +87,35 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
73
87
|
end
|
|
74
88
|
|
|
75
89
|
def initialize(service_container)
|
|
90
|
+
@telemetry_factory = service_container.telemetry_factory
|
|
76
91
|
@plugins = load_plugins(service_container)
|
|
77
92
|
@pipeline_cache = {}
|
|
78
93
|
end
|
|
79
94
|
|
|
80
95
|
def connect(host_info, driver_props, is_initial_connection, plugin_to_skip: nil)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
@telemetry_factory.in_context('connect', Utils::Telemetry::TraceLevel::NESTED) do
|
|
97
|
+
execute_with_subscribed_plugins(
|
|
98
|
+
'connect',
|
|
99
|
+
lambda do |plugin, next_plugin_callable|
|
|
100
|
+
plugin.connect(host_info, driver_props, is_initial_connection, next_plugin_callable)
|
|
101
|
+
end,
|
|
102
|
+
NOOP_CALLABLE,
|
|
103
|
+
plugin_to_skip:
|
|
104
|
+
)
|
|
105
|
+
end
|
|
89
106
|
end
|
|
90
107
|
|
|
91
108
|
def internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, plugin_to_skip: nil)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
@telemetry_factory.in_context('internal_connect', Utils::Telemetry::TraceLevel::NESTED) do
|
|
110
|
+
execute_with_subscribed_plugins(
|
|
111
|
+
'internal_connect',
|
|
112
|
+
lambda do |plugin, next_plugin_callable|
|
|
113
|
+
plugin.internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, next_plugin_callable)
|
|
114
|
+
end,
|
|
115
|
+
NOOP_CALLABLE,
|
|
116
|
+
plugin_to_skip:
|
|
117
|
+
)
|
|
118
|
+
end
|
|
100
119
|
end
|
|
101
120
|
|
|
102
121
|
# The context of the call currently being executed, for plugins that need to know more about
|
|
@@ -140,15 +159,18 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
140
159
|
Thread.current[CURRENT_CALL_CONTEXT_KEY] = context
|
|
141
160
|
|
|
142
161
|
begin
|
|
143
|
-
|
|
144
|
-
method_name
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
@telemetry_factory.in_context(method_name, Utils::Telemetry::TraceLevel::TOP_LEVEL, record_result: true) do |telemetry_context|
|
|
163
|
+
telemetry_context.set_attribute(Utils::Telemetry::TelemetryConst::RUBY_CALL_ANNOTATION, method_name)
|
|
164
|
+
execute_with_subscribed_plugins(
|
|
165
|
+
method_name,
|
|
166
|
+
lambda do |plugin, next_plugin_callable|
|
|
167
|
+
# Read from the context rather than from args and block, so that a plugin which
|
|
168
|
+
# replaced either is honoured by the plugins after it and by the target method.
|
|
169
|
+
plugin.execute(method_name, next_plugin_callable, *context.args, **kwargs, &context.block)
|
|
170
|
+
end,
|
|
171
|
+
target_callable
|
|
172
|
+
)
|
|
173
|
+
end
|
|
152
174
|
ensure
|
|
153
175
|
Thread.current[CURRENT_CALL_CONTEXT_KEY] = previous_context
|
|
154
176
|
end
|
|
@@ -164,6 +186,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
164
186
|
@plugins.any?(plugin_class)
|
|
165
187
|
end
|
|
166
188
|
|
|
189
|
+
# Whether any plugin that mints RDS IAM tokens into the shared cache is active.
|
|
190
|
+
# @see IAM_TOKEN_PLUGIN_CLASSES
|
|
191
|
+
def iam_token_plugin_in_use?
|
|
192
|
+
IAM_TOKEN_PLUGIN_CLASSES.any? { |plugin_class| plugin_in_use?(plugin_class) }
|
|
193
|
+
end
|
|
194
|
+
|
|
167
195
|
private
|
|
168
196
|
|
|
169
197
|
def load_plugins(service_container)
|
|
@@ -185,7 +213,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
185
213
|
end
|
|
186
214
|
|
|
187
215
|
def ensure_single_auth_plugin(plugin_code_list)
|
|
188
|
-
auth_plugins_used = plugin_code_list & %w[iam secrets_manager].freeze
|
|
216
|
+
auth_plugins_used = plugin_code_list & %w[iam secrets_manager federated_auth okta].freeze
|
|
189
217
|
|
|
190
218
|
return unless auth_plugins_used.length > 1
|
|
191
219
|
|
|
@@ -245,29 +273,52 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
245
273
|
|
|
246
274
|
# Builds the plugin pipeline function chain. The pipeline allows plugins to perform logic both before and after
|
|
247
275
|
# the target driver function is called.
|
|
276
|
+
#
|
|
277
|
+
# When traces are recorded, each plugin's part of the call is recorded as a span named after the
|
|
278
|
+
# plugin. That is decided here, once per pipeline, so a pipeline built without traces does no
|
|
279
|
+
# tracing work at all.
|
|
248
280
|
def make_pipeline(target_method_name)
|
|
249
281
|
subscribed_plugins = @plugins.select do |p|
|
|
250
282
|
p.subscribed_methods.include?('*') || p.subscribed_methods.include?(target_method_name)
|
|
251
283
|
end
|
|
252
284
|
raise Errors::AwsError, 'Plugin pipeline is nil' if subscribed_plugins.empty?
|
|
253
285
|
|
|
286
|
+
traced = @telemetry_factory.traces_in_use?
|
|
254
287
|
base_plugin = subscribed_plugins.last
|
|
288
|
+
base_plugin_name = plugin_name(base_plugin) if traced
|
|
255
289
|
base = lambda do |plugin_callable, target_driver_callable, *, **|
|
|
256
|
-
|
|
290
|
+
if traced
|
|
291
|
+
call_traced(plugin_callable, base_plugin, base_plugin_name, target_driver_callable)
|
|
292
|
+
else
|
|
293
|
+
plugin_callable.call(base_plugin, target_driver_callable)
|
|
294
|
+
end
|
|
257
295
|
end
|
|
258
296
|
|
|
259
297
|
subscribed_plugins[0...-1].reverse.reduce(base) do |next_plugin_callable, plugin|
|
|
298
|
+
name = plugin_name(plugin) if traced
|
|
260
299
|
lambda do |plugin_callable, target_driver_callable, method_name, plugin_to_skip|
|
|
261
300
|
if plugin_to_skip == plugin
|
|
262
301
|
next_plugin_callable.call(plugin_callable, target_driver_callable, method_name, plugin_to_skip)
|
|
263
302
|
else
|
|
264
|
-
|
|
303
|
+
call_next = lambda do
|
|
265
304
|
next_plugin_callable.call(plugin_callable, target_driver_callable, method_name, plugin_to_skip)
|
|
266
|
-
end
|
|
305
|
+
end
|
|
306
|
+
traced ? call_traced(plugin_callable, plugin, name, call_next) : plugin_callable.call(plugin, call_next)
|
|
267
307
|
end
|
|
268
308
|
end
|
|
269
309
|
end
|
|
270
310
|
end
|
|
311
|
+
|
|
312
|
+
# A plugin's span is not marked as failed when the call raises: the error passes out through every
|
|
313
|
+
# plugin's span, and is recorded on the call's top-level span instead.
|
|
314
|
+
def call_traced(plugin_callable, plugin, name, next_plugin_callable)
|
|
315
|
+
@telemetry_factory.in_context(name, Utils::Telemetry::TraceLevel::NESTED) { plugin_callable.call(plugin, next_plugin_callable) }
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# The plugin's code (such as +failover+), or the class name of a plugin that has none.
|
|
319
|
+
def plugin_name(plugin)
|
|
320
|
+
self.class.plugin_classes.key(plugin.class) || plugin.class.name&.split('::')&.last || plugin.class.to_s
|
|
321
|
+
end
|
|
271
322
|
end
|
|
272
323
|
end
|
|
273
324
|
end
|