aws_advanced_ruby_driver_wrapper 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/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'digest'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Utils
|
|
21
|
+
module AwsCredentialsUtils
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Stands in for the identity when a provider resolves to no credentials.
|
|
25
|
+
NO_CREDENTIALS = 'none'
|
|
26
|
+
IDENTITY_LENGTH = 16
|
|
27
|
+
|
|
28
|
+
# The credentials a provider resolves to right now, copied into a fixed Aws::Credentials, or
|
|
29
|
+
# nil when it resolves to none. Callers that cache what they obtain use one snapshot both to
|
|
30
|
+
# build the cache key ({identity}) and to make the request, so the key always describes the
|
|
31
|
+
# credentials the cached value was obtained with, even if the provider refreshes in between.
|
|
32
|
+
# Requires the AWS SDK to be loaded.
|
|
33
|
+
#
|
|
34
|
+
# @param credentials_provider [#credentials, Aws::Credentials, nil]
|
|
35
|
+
# @return [Aws::Credentials, nil]
|
|
36
|
+
def snapshot(credentials_provider)
|
|
37
|
+
credentials = credentials_provider.respond_to?(:credentials) ? credentials_provider.credentials : credentials_provider
|
|
38
|
+
access_key_id = credentials.respond_to?(:access_key_id) ? credentials.access_key_id : nil
|
|
39
|
+
return nil if access_key_id.nil? || access_key_id.empty?
|
|
40
|
+
|
|
41
|
+
session_token = credentials.respond_to?(:session_token) ? credentials.session_token : nil
|
|
42
|
+
Aws::Credentials.new(access_key_id, credentials.secret_access_key, session_token)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A short, stable identifier for the AWS credentials a provider currently resolves to, for
|
|
46
|
+
# use in cache keys: two providers that resolve to the same access key share it, and a
|
|
47
|
+
# provider that refreshes to a new access key (a new assumed-role session, for example) gets
|
|
48
|
+
# a new one. It is derived from the access key id alone, which is hashed so that not even
|
|
49
|
+
# that appears in a cache key. The secret key is never read.
|
|
50
|
+
#
|
|
51
|
+
# @param credentials_provider [#credentials, Aws::Credentials, nil]
|
|
52
|
+
# @return [String]
|
|
53
|
+
def identity(credentials_provider)
|
|
54
|
+
credentials = credentials_provider.respond_to?(:credentials) ? credentials_provider.credentials : credentials_provider
|
|
55
|
+
access_key_id = credentials.respond_to?(:access_key_id) ? credentials.access_key_id : nil
|
|
56
|
+
return NO_CREDENTIALS if access_key_id.nil? || access_key_id.empty?
|
|
57
|
+
|
|
58
|
+
Digest::SHA256.hexdigest(access_key_id)[0, IDENTITY_LENGTH]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require_relative '../property_definition'
|
|
19
|
+
require_relative '../logging'
|
|
20
|
+
|
|
21
|
+
module AwsAdvancedRubyDriverWrapper
|
|
22
|
+
module Utils
|
|
23
|
+
class ConnectionConfig
|
|
24
|
+
attr_accessor :wrapper_props, :driver_props, :prefixed_wrapper_config, :prefixed_driver_config, :initial_host_info, :driver_name
|
|
25
|
+
attr_reader :original_host, :original_port
|
|
26
|
+
|
|
27
|
+
def initialize(wrapper_props: ::Concurrent::Map.new,
|
|
28
|
+
driver_props: ::Concurrent::Map.new,
|
|
29
|
+
prefixed_wrapper_config: ::Concurrent::Map.new,
|
|
30
|
+
prefixed_driver_config: ::Concurrent::Map.new,
|
|
31
|
+
initial_host_info: nil,
|
|
32
|
+
driver_name: nil,
|
|
33
|
+
original_host: nil,
|
|
34
|
+
original_port: nil,
|
|
35
|
+
multi_host_url: false)
|
|
36
|
+
@wrapper_props = wrapper_props
|
|
37
|
+
@driver_props = driver_props
|
|
38
|
+
@prefixed_wrapper_config = prefixed_wrapper_config
|
|
39
|
+
@prefixed_driver_config = prefixed_driver_config
|
|
40
|
+
@initial_host_info = initial_host_info
|
|
41
|
+
@driver_name = driver_name
|
|
42
|
+
@original_host = original_host
|
|
43
|
+
@original_port = original_port
|
|
44
|
+
@multi_host_url = multi_host_url
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def multi_host_url?
|
|
48
|
+
@multi_host_url
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cluster_id
|
|
52
|
+
PropertyDefinition::CLUSTER_ID.get(wrapper_props)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Redact sensitive property values (passwords, IAM tokens, Secrets Manager
|
|
56
|
+
# credentials) so they are never exposed if a config is logged, interpolated,
|
|
57
|
+
# or rendered in a backtrace. The property maps are the only fields that carry
|
|
58
|
+
# secrets; the rest are host/driver metadata that is safe to show.
|
|
59
|
+
#
|
|
60
|
+
# Most secrets are recognized by name. The IAM token is passed under a property
|
|
61
|
+
# whose name is configurable, so whatever name is configured is redacted too.
|
|
62
|
+
def inspect
|
|
63
|
+
"#<#{self.class.name} " \
|
|
64
|
+
"wrapper_props=#{masked(wrapper_props)}, " \
|
|
65
|
+
"driver_props=#{masked(driver_props)}, " \
|
|
66
|
+
"prefixed_wrapper_config=#{masked(prefixed_wrapper_config)}, " \
|
|
67
|
+
"prefixed_driver_config=#{masked(prefixed_driver_config)}, " \
|
|
68
|
+
"initial_host_info=#{initial_host_info.inspect}, driver_name=#{driver_name.inspect}, " \
|
|
69
|
+
"original_host=#{original_host.inspect}, original_port=#{original_port.inspect}, " \
|
|
70
|
+
"multi_host_url=#{@multi_host_url.inspect}>"
|
|
71
|
+
end
|
|
72
|
+
alias to_s inspect
|
|
73
|
+
|
|
74
|
+
# `pp` / PrettyPrint does not call #inspect; route them through the redacted
|
|
75
|
+
# representation so `pp config` cannot leak credentials.
|
|
76
|
+
def pretty_print(pp)
|
|
77
|
+
pp.text(inspect)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def masked(props)
|
|
83
|
+
AwsAdvancedRubyDriverWrapper.mask_properties(props, [iam_token_property_name])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def iam_token_property_name
|
|
87
|
+
wrapper_props && PropertyDefinition::IAM_ACCESS_TOKEN_PROPERTY_NAME.get(wrapper_props)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'uri'
|
|
18
|
+
require 'concurrent'
|
|
19
|
+
require_relative 'connection_config'
|
|
20
|
+
require_relative '../property_definition'
|
|
21
|
+
require_relative '../host/host_availability'
|
|
22
|
+
require_relative '../host/host_availability_strategy'
|
|
23
|
+
require_relative '../host/host_info'
|
|
24
|
+
require_relative '../host/host_role'
|
|
25
|
+
|
|
26
|
+
module AwsAdvancedRubyDriverWrapper
|
|
27
|
+
module Utils
|
|
28
|
+
module ConnectionConfigParser
|
|
29
|
+
CONNINFO_PATTERN = /(\w+)=(?:'([^']*)'|([^\s]++))/
|
|
30
|
+
BOOLEAN_STRINGS = %w[true false].freeze
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# Main entry point. The driver_name is provided by the wrapper class
|
|
35
|
+
# (e.g. :postgresql from WrapperPgConnection, :mysql2 from WrapperMysql2Client).
|
|
36
|
+
#
|
|
37
|
+
# @param driver_name [Symbol] :postgresql or :mysql2
|
|
38
|
+
def parse(driver_name, *args, **kwargs)
|
|
39
|
+
if driver_name == :mysql2 && !args.empty? && !args.first.is_a?(Hash)
|
|
40
|
+
raise ArgumentError,
|
|
41
|
+
"WrapperMysql2Client only accepts keyword arguments (e.g. host: 'x', port: 3306). " \
|
|
42
|
+
'URI strings and positional arguments are not supported.'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if args.length == 1 && args.first.is_a?(String)
|
|
46
|
+
str = args.first
|
|
47
|
+
return parse_uri(driver_name, str, **kwargs) if str.include?('://')
|
|
48
|
+
|
|
49
|
+
return parse_conninfo(driver_name, str, **kwargs)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return parse_hash(driver_name, args.first.merge(kwargs)) if args.length == 1 && args.first.is_a?(Hash)
|
|
53
|
+
return parse_hash(driver_name, kwargs) if args.empty? && !kwargs.empty?
|
|
54
|
+
|
|
55
|
+
parse_positional(driver_name, args, kwargs)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Parses a URI connection string, e.g.
|
|
59
|
+
# "postgresql://user:pass@host1,host2:5432/mydb?sslmode=require"
|
|
60
|
+
def parse_uri(driver_name, uri_string, **overrides)
|
|
61
|
+
# eg ["postgresql", "user:pass@host1,host2:5432/mydb?sslmode=require"]
|
|
62
|
+
scheme_rest = uri_string.split('://', 2)
|
|
63
|
+
# eg "user:pass@host1,host2:5432/mydb?sslmode=require"
|
|
64
|
+
authority_and_rest = scheme_rest[1] || ''
|
|
65
|
+
# eg "host1,host2:5432"
|
|
66
|
+
host_section = extract_host_section(authority_and_rest)
|
|
67
|
+
|
|
68
|
+
# eg "user", "pass", "/mydb", "sslmode=require"
|
|
69
|
+
user, password, path, query_string = parse_uri_parts(uri_string, authority_and_rest)
|
|
70
|
+
# eg {"sslmode" => "require"}
|
|
71
|
+
query_params = query_string ? URI.decode_www_form(query_string).to_h : {}
|
|
72
|
+
# eg {host: "host1,host2", user: "user", password: "pass", dbname: "mydb", sslmode: "require"}
|
|
73
|
+
driver_config = uri_to_config(user, password, path, driver_name)
|
|
74
|
+
# eg {sslmode: "require"}
|
|
75
|
+
all_props = query_params.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
|
|
76
|
+
overrides.transform_keys(&:to_sym).each { |k, v| all_props[k] = v }
|
|
77
|
+
|
|
78
|
+
# eg {}, {sslmode: "require"}, {}
|
|
79
|
+
wrapper_config, extra_driver, prefixed_wrapper_config, prefixed_driver_config = split_props(all_props)
|
|
80
|
+
extra_driver.each { |k, v| driver_config[k] = v }
|
|
81
|
+
|
|
82
|
+
initial_host_info = string_to_host_info(host_section)
|
|
83
|
+
host, port = host_port_from_uri(host_section)
|
|
84
|
+
driver_config[:host] = host
|
|
85
|
+
driver_config[:port] = port unless port.to_s.tr(',', '').empty?
|
|
86
|
+
|
|
87
|
+
ConnectionConfig.new(
|
|
88
|
+
wrapper_props: wrapper_config,
|
|
89
|
+
driver_props: driver_config,
|
|
90
|
+
prefixed_wrapper_config: prefixed_wrapper_config,
|
|
91
|
+
prefixed_driver_config: prefixed_driver_config,
|
|
92
|
+
initial_host_info:,
|
|
93
|
+
original_host: host,
|
|
94
|
+
original_port: port,
|
|
95
|
+
multi_host_url: host.include?(','),
|
|
96
|
+
driver_name:
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Parses libpq key=value conninfo strings, e.g. "host=localhost port=5432 dbname=mydb"
|
|
101
|
+
def parse_conninfo(driver_name, conninfo, **overrides)
|
|
102
|
+
params = conninfo.scan(CONNINFO_PATTERN).each_with_object({}) do |(k, v1, v2), h|
|
|
103
|
+
h[k.to_sym] = v1 || v2
|
|
104
|
+
end
|
|
105
|
+
params.merge!(overrides)
|
|
106
|
+
parse_hash(driver_name, params)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Parses a flat keyword hash, e.g.
|
|
110
|
+
# parse_hash(:postgresql, { host: "myhost", port: 5432, dbname: "mydb", wrapper_plugins: "failover" })
|
|
111
|
+
def parse_hash(driver_name, params)
|
|
112
|
+
params = params.transform_keys(&:to_sym)
|
|
113
|
+
wrapper_config, driver_config, prefixed_wrapper_config, prefixed_driver_config = split_props(params)
|
|
114
|
+
driver_config[:port] = driver_config[:port].to_s if driver_config.key?(:port)
|
|
115
|
+
initial_host_info = hash_to_host_info(driver_config)
|
|
116
|
+
|
|
117
|
+
original_host = driver_config[:host] || driver_config[:hostname]
|
|
118
|
+
original_port = driver_config[:port]
|
|
119
|
+
|
|
120
|
+
ConnectionConfig.new(
|
|
121
|
+
wrapper_props: wrapper_config,
|
|
122
|
+
driver_props: driver_config,
|
|
123
|
+
prefixed_wrapper_config: prefixed_wrapper_config,
|
|
124
|
+
prefixed_driver_config: prefixed_driver_config,
|
|
125
|
+
initial_host_info:,
|
|
126
|
+
original_host:,
|
|
127
|
+
original_port:,
|
|
128
|
+
multi_host_url: original_host.to_s.include?(','),
|
|
129
|
+
driver_name:
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Parses PG-style positional arguments, e.g.
|
|
134
|
+
# parse_positional(:postgresql, ["myhost", 5432, nil, nil, "mydb", "user", "pass"], { cluster_id: "test" })
|
|
135
|
+
def parse_positional(driver_name, args, kwargs)
|
|
136
|
+
keys = %i[host port options tty dbname user password]
|
|
137
|
+
positional = keys.zip(args).to_h.compact
|
|
138
|
+
all_props = positional.merge(kwargs.transform_keys(&:to_sym))
|
|
139
|
+
|
|
140
|
+
wrapper_config, driver_config, prefixed_wrapper_config, prefixed_driver_config = split_props(all_props)
|
|
141
|
+
driver_config[:port] = driver_config[:port].to_s if driver_config.key?(:port)
|
|
142
|
+
initial_host_info = hash_to_host_info(driver_config)
|
|
143
|
+
|
|
144
|
+
original_host = driver_config[:host]
|
|
145
|
+
original_port = driver_config[:port]
|
|
146
|
+
|
|
147
|
+
ConnectionConfig.new(
|
|
148
|
+
wrapper_props: wrapper_config,
|
|
149
|
+
driver_props: driver_config,
|
|
150
|
+
prefixed_wrapper_config: prefixed_wrapper_config,
|
|
151
|
+
prefixed_driver_config: prefixed_driver_config,
|
|
152
|
+
initial_host_info:,
|
|
153
|
+
original_host:,
|
|
154
|
+
original_port:,
|
|
155
|
+
multi_host_url: original_host.to_s.include?(','),
|
|
156
|
+
driver_name:
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Splits a flat hash into wrapper_config, driver_config, and prefixed_config.
|
|
161
|
+
# Keys matching a known prefix are stripped and grouped by prefix in prefixed_config.
|
|
162
|
+
# Known wrapper properties go to wrapper_config. Everything else goes to driver_config.
|
|
163
|
+
def split_props(props)
|
|
164
|
+
wrapper_config = ::Concurrent::Map.new
|
|
165
|
+
driver_config = ::Concurrent::Map.new
|
|
166
|
+
prefixed_wrapper_config = ::Concurrent::Map.new
|
|
167
|
+
prefixed_driver_config = ::Concurrent::Map.new
|
|
168
|
+
|
|
169
|
+
props.each do |key, value|
|
|
170
|
+
key_s = key.to_s
|
|
171
|
+
prefix = PropertyDefinition::KNOWN_PREFIXES.find { |p| key_s.start_with?(p) }
|
|
172
|
+
if prefix
|
|
173
|
+
prefix_key_sym = key_s.delete_prefix(prefix).to_sym
|
|
174
|
+
if PropertyDefinition.wrapper_property?(prefix_key_sym)
|
|
175
|
+
(prefixed_wrapper_config[prefix] ||= ::Concurrent::Map.new)[prefix_key_sym] = value
|
|
176
|
+
else
|
|
177
|
+
(prefixed_driver_config[prefix] ||= ::Concurrent::Map.new)[prefix_key_sym] = value
|
|
178
|
+
end
|
|
179
|
+
elsif PropertyDefinition.wrapper_property?(key)
|
|
180
|
+
wrapper_config[key.to_sym] = value
|
|
181
|
+
else
|
|
182
|
+
driver_config[key.to_sym] = value
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
validate_props!(wrapper_config)
|
|
187
|
+
prefixed_wrapper_config.each_value { |prefixed| validate_props!(prefixed) }
|
|
188
|
+
|
|
189
|
+
[wrapper_config, driver_config, prefixed_wrapper_config, prefixed_driver_config]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Validates that values in a config hash match the expected type of their corresponding WrapperProperty.
|
|
193
|
+
# Raises TypeError if a value is present but incompatible. Strings that represent valid integers
|
|
194
|
+
# are accepted for Integer-typed properties. Float-typed properties also accept Integers and
|
|
195
|
+
# strings that represent a valid integer or decimal number.
|
|
196
|
+
def validate_props!(config)
|
|
197
|
+
config.each do |key, value|
|
|
198
|
+
prop = PropertyDefinition::KNOWN_PROPERTIES[key]
|
|
199
|
+
next unless prop
|
|
200
|
+
next if value.nil?
|
|
201
|
+
next if prop.type.nil?
|
|
202
|
+
|
|
203
|
+
valid = if prop.type == :boolean
|
|
204
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass) ||
|
|
205
|
+
(value.is_a?(String) && BOOLEAN_STRINGS.include?(value.downcase))
|
|
206
|
+
elsif prop.type == Float
|
|
207
|
+
value.is_a?(Float) || value.is_a?(Integer) ||
|
|
208
|
+
(value.is_a?(String) && value.match?(/\A-?\d+(\.\d+)?\z/))
|
|
209
|
+
else
|
|
210
|
+
value.is_a?(prop.type) ||
|
|
211
|
+
(prop.type == Integer && value.is_a?(String) && value.match?(/\A-?\d+\z/))
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
raise TypeError, "#{key}: expected #{prop.type}, got #{value.class}" unless valid
|
|
215
|
+
|
|
216
|
+
prop.validate!(value)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Forms a HostInfo object from a URI host section string.
|
|
221
|
+
# Each host entry may or may not have a port; missing ports are represented as -1.
|
|
222
|
+
# The resulting port is a comma-delimited string of per-host ports.
|
|
223
|
+
def string_to_host_info(host)
|
|
224
|
+
host_str = host&.strip
|
|
225
|
+
return Host::HostInfo.new if host_str.nil? || host_str.empty?
|
|
226
|
+
|
|
227
|
+
entries = host_str.split(',').map(&:strip)
|
|
228
|
+
hosts = []
|
|
229
|
+
ports = []
|
|
230
|
+
entries.each do |entry|
|
|
231
|
+
if entry.include?(':')
|
|
232
|
+
h, p = entry.split(':', 2)
|
|
233
|
+
hosts << h
|
|
234
|
+
ports << p.to_s
|
|
235
|
+
else
|
|
236
|
+
hosts << entry
|
|
237
|
+
ports << Host::HostInfo::NO_PORT
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
port_str = ports.uniq.length == 1 ? ports.first : ports.join(',')
|
|
242
|
+
Host::HostInfo.new(host: hosts.join(','), port: port_str)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Forms a HostInfo object from hash-style input.
|
|
246
|
+
# If a single port is given (even with multiple hosts), the port is kept as-is.
|
|
247
|
+
# The resulting port is always a string.
|
|
248
|
+
def hash_to_host_info(hash)
|
|
249
|
+
host_str = hash[:host]&.to_s&.strip || hash[:hostname]&.to_s&.strip
|
|
250
|
+
port_str = hash[:port]&.to_s&.strip
|
|
251
|
+
|
|
252
|
+
Host::HostInfo.new(
|
|
253
|
+
host: host_str || Host::HostInfo::NO_HOST,
|
|
254
|
+
port: port_str || Host::HostInfo::NO_PORT
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def extract_host_section(authority_and_rest)
|
|
259
|
+
without_userinfo = if authority_and_rest.include?('@')
|
|
260
|
+
authority_and_rest.split('@', 2).last
|
|
261
|
+
else
|
|
262
|
+
authority_and_rest
|
|
263
|
+
end
|
|
264
|
+
without_userinfo.split(%r{[/?#]}, 2).first || ''
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Extracts original_host and original_port from a URI host section.
|
|
268
|
+
# Missing ports are represented as empty strings in the comma-delimited port string.
|
|
269
|
+
# E.g. "host1,host2:5433" => ["host1,host2", ",5433"]
|
|
270
|
+
# "host1:5432,host2:5433" => ["host1,host2", "5432,5433"]
|
|
271
|
+
# "host1,host2" => ["host1,host2", ","]
|
|
272
|
+
def host_port_from_uri(host_section)
|
|
273
|
+
entries = host_section.split(',').map(&:strip)
|
|
274
|
+
hosts = []
|
|
275
|
+
ports = []
|
|
276
|
+
entries.each do |entry|
|
|
277
|
+
if entry.include?(':')
|
|
278
|
+
h, p = entry.split(':', 2)
|
|
279
|
+
hosts << h
|
|
280
|
+
ports << p
|
|
281
|
+
else
|
|
282
|
+
hosts << entry
|
|
283
|
+
ports << ''
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
original_host = hosts.join(',')
|
|
288
|
+
original_port = ports.join(',')
|
|
289
|
+
[original_host, original_port]
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Parses user, password, path, and query from a PostgreSQL-style URI.
|
|
293
|
+
# Falls back to manual parsing when URI.parse fails (e.g. multi-host with per-host ports).
|
|
294
|
+
def parse_uri_parts(uri_string, authority_and_rest)
|
|
295
|
+
begin
|
|
296
|
+
parsed = URI.parse(uri_string)
|
|
297
|
+
user = parsed.user ? URI.decode_www_form_component(parsed.user) : nil
|
|
298
|
+
password = parsed.password ? URI.decode_www_form_component(parsed.password) : nil
|
|
299
|
+
path = parsed.path
|
|
300
|
+
query_string = parsed.query
|
|
301
|
+
rescue URI::InvalidURIError
|
|
302
|
+
# Manual parsing for multi-host URIs that break standard URI parsing
|
|
303
|
+
user, password = extract_userinfo(authority_and_rest)
|
|
304
|
+
path, query_string = extract_path_and_query(authority_and_rest)
|
|
305
|
+
end
|
|
306
|
+
[user, password, path, query_string]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Extracts user:password from the authority section before the @ sign.
|
|
310
|
+
def extract_userinfo(authority_and_rest)
|
|
311
|
+
return [nil, nil] unless authority_and_rest.include?('@')
|
|
312
|
+
|
|
313
|
+
userinfo = authority_and_rest.split('@', 2).first
|
|
314
|
+
if userinfo.include?(':')
|
|
315
|
+
user, pass = userinfo.split(':', 2)
|
|
316
|
+
[URI.decode_www_form_component(user), URI.decode_www_form_component(pass)]
|
|
317
|
+
else
|
|
318
|
+
[URI.decode_www_form_component(userinfo), nil]
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Extracts path and query string from the authority-and-rest section.
|
|
323
|
+
def extract_path_and_query(authority_and_rest)
|
|
324
|
+
without_userinfo = if authority_and_rest.include?('@')
|
|
325
|
+
authority_and_rest.split('@', 2).last
|
|
326
|
+
else
|
|
327
|
+
authority_and_rest
|
|
328
|
+
end
|
|
329
|
+
# Remove host section
|
|
330
|
+
remainder = without_userinfo.split(%r{[/?#]}, 2)[1] || ''
|
|
331
|
+
|
|
332
|
+
# Determine if we split on / or ?
|
|
333
|
+
first_delim_idx = without_userinfo.index(%r{[/?#]})
|
|
334
|
+
if first_delim_idx.nil?
|
|
335
|
+
['', nil]
|
|
336
|
+
else
|
|
337
|
+
delim = without_userinfo[first_delim_idx]
|
|
338
|
+
if delim == '/'
|
|
339
|
+
path_and_query = "/#{remainder}"
|
|
340
|
+
if path_and_query.include?('?')
|
|
341
|
+
path, query = path_and_query.split('?', 2)
|
|
342
|
+
[path, query]
|
|
343
|
+
else
|
|
344
|
+
[path_and_query, nil]
|
|
345
|
+
end
|
|
346
|
+
elsif delim == '?'
|
|
347
|
+
['', remainder]
|
|
348
|
+
else
|
|
349
|
+
['', nil]
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def uri_to_config(user, password, path, protocol)
|
|
355
|
+
config = {}
|
|
356
|
+
config[:user] = user if user
|
|
357
|
+
config[:password] = password if password
|
|
358
|
+
|
|
359
|
+
db = path&.sub(%r{^/}, '')
|
|
360
|
+
unless db.to_s.empty?
|
|
361
|
+
config[protocol == :postgresql ? :dbname : :database] = db
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
config
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Utils
|
|
19
|
+
# Utility methods for converting values from database query results into Ruby types.
|
|
20
|
+
module ConversionUtils
|
|
21
|
+
require 'time'
|
|
22
|
+
def to_boolean(value)
|
|
23
|
+
case value
|
|
24
|
+
when true, 1, '1', 'true', 't', 'TRUE', 'T'
|
|
25
|
+
true
|
|
26
|
+
else
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_float(value)
|
|
32
|
+
Float(value || 0)
|
|
33
|
+
rescue ArgumentError, TypeError
|
|
34
|
+
0.0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_time(value)
|
|
38
|
+
case value
|
|
39
|
+
when Time
|
|
40
|
+
value
|
|
41
|
+
when String
|
|
42
|
+
Time.parse(value)
|
|
43
|
+
else
|
|
44
|
+
Time.now
|
|
45
|
+
end
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
Time.now
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative '../../logging'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Utils
|
|
21
|
+
module Events
|
|
22
|
+
# Publishes batched events periodically and immediate events synchronously.
|
|
23
|
+
# Batches deduplicate events via Set semantics (eql?/hash).
|
|
24
|
+
#
|
|
25
|
+
# Public API:
|
|
26
|
+
# subscribe(subscriber, event_classes) — register for event types
|
|
27
|
+
# unsubscribe(subscriber, event_classes) — deregister
|
|
28
|
+
# publish(event) — deliver immediate or queue batched
|
|
29
|
+
# release_resources — stop background thread
|
|
30
|
+
class BatchingEventPublisher
|
|
31
|
+
include Logging
|
|
32
|
+
|
|
33
|
+
DEFAULT_MESSAGE_INTERVAL_SEC = 30
|
|
34
|
+
|
|
35
|
+
def initialize(message_interval_sec: DEFAULT_MESSAGE_INTERVAL_SEC)
|
|
36
|
+
@message_interval_sec = message_interval_sec
|
|
37
|
+
@subscribers = {}
|
|
38
|
+
@event_queue = Set.new
|
|
39
|
+
@lock = Mutex.new
|
|
40
|
+
@running = true
|
|
41
|
+
@thread = start_publishing_thread
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def subscribe(subscriber, event_classes)
|
|
45
|
+
@lock.synchronize do
|
|
46
|
+
event_classes.each do |event_class|
|
|
47
|
+
(@subscribers[event_class] ||= Set.new).add(subscriber)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def unsubscribe(subscriber, event_classes)
|
|
53
|
+
@lock.synchronize do
|
|
54
|
+
event_classes.each do |event_class|
|
|
55
|
+
set = @subscribers[event_class]
|
|
56
|
+
next unless set
|
|
57
|
+
|
|
58
|
+
set.delete(subscriber)
|
|
59
|
+
@subscribers.delete(event_class) if set.empty?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def publish(event)
|
|
65
|
+
if event.immediate_delivery?
|
|
66
|
+
deliver_event(event)
|
|
67
|
+
else
|
|
68
|
+
@lock.synchronize { @event_queue.add(event) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def release_resources
|
|
73
|
+
@lock.synchronize { @running = false }
|
|
74
|
+
begin
|
|
75
|
+
@thread&.wakeup
|
|
76
|
+
rescue ThreadError
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
@thread&.join(@message_interval_sec)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def start_publishing_thread
|
|
85
|
+
thread = Thread.new do
|
|
86
|
+
while @lock.synchronize { @running }
|
|
87
|
+
sleep(@message_interval_sec)
|
|
88
|
+
send_messages
|
|
89
|
+
end
|
|
90
|
+
send_messages
|
|
91
|
+
end
|
|
92
|
+
thread.name = 'batching-event-publisher'
|
|
93
|
+
thread
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def send_messages
|
|
97
|
+
events = @lock.synchronize do
|
|
98
|
+
batch = @event_queue.to_a
|
|
99
|
+
@event_queue.clear
|
|
100
|
+
batch
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
events.each { |event| deliver_event(event) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def deliver_event(event)
|
|
107
|
+
subscribers = @lock.synchronize { @subscribers[event.class]&.to_a }
|
|
108
|
+
return unless subscribers
|
|
109
|
+
|
|
110
|
+
subscribers.each do |subscriber|
|
|
111
|
+
subscriber.process_event(event)
|
|
112
|
+
rescue StandardError => e
|
|
113
|
+
logger.error("Error delivering event to subscriber: #{e.message}")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|