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.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +175 -0
  4. data/NOTICE +1 -0
  5. data/README.md +168 -0
  6. data/THIRD-PARTY-LICENSES +473 -0
  7. data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
  8. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
  9. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
  10. data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
  11. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
  12. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
  13. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
  14. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
  15. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
  16. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
  17. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
  18. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
  19. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
  20. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
  21. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
  22. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
  23. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
  24. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
  25. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
  26. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
  27. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
  28. data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
  29. data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
  30. data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
  31. data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
  32. data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
  33. data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
  34. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
  35. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
  36. data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
  37. data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
  38. data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
  39. data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
  40. data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
  41. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
  42. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
  43. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
  44. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
  45. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
  46. data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
  47. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
  48. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
  49. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
  50. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
  51. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
  52. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
  53. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
  54. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
  55. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
  56. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
  57. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
  58. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
  59. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
  60. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
  61. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
  62. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
  63. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
  64. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
  65. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
  66. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
  67. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
  68. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
  69. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
  70. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
  71. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
  72. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
  73. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
  74. data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
  75. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
  76. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
  77. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
  78. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
  79. data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
  80. data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
  81. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
  82. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
  83. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
  84. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
  85. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
  86. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
  87. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
  88. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
  89. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
  90. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
  91. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
  92. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
  93. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
  94. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
  95. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
  96. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
  97. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
  98. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
  99. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
  100. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
  101. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
  102. data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
  103. data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
  104. data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
  105. data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
  106. data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
  107. data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
  108. data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
  109. data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
  110. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
  111. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
  112. data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
  113. data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
  114. data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
  115. data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
  116. data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
  117. data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
  118. data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
  119. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
  120. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
  121. data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
  122. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
  123. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
  124. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
  125. data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
  126. data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
  127. data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
  128. data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
  129. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
  130. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
  131. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
  132. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
  133. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
  134. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
  135. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
  136. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
  137. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
  138. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
  139. data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
  140. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
  141. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
  142. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
  143. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
  144. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
  145. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
  146. data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
  147. data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
  148. data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
  149. data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
  150. metadata +227 -0
@@ -0,0 +1,575 @@
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 'rds_url_type'
19
+
20
+ module AwsAdvancedRubyDriverWrapper
21
+ module Utils
22
+ # Utility module for parsing and classifying Amazon RDS/Aurora endpoint hostnames.
23
+ #
24
+ # Determines endpoint type (writer cluster, reader cluster, instance, proxy, etc.),
25
+ # extracts metadata (region, cluster ID, instance ID), and supports blue/green
26
+ # deployment host manipulation.
27
+ #
28
+ # Aurora endpoint format reference:
29
+ # https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.Endpoints.html
30
+ #
31
+ # Cluster (Writer): <db-cluster>.cluster-<xyz>.<region>.rds.amazonaws.com
32
+ # Cluster Reader: <db-cluster>.cluster-ro-<xyz>.<region>.rds.amazonaws.com
33
+ # Custom Cluster: <alias>.cluster-custom-<xyz>.<region>.rds.amazonaws.com
34
+ # Instance: <instance>.<xyz>.<region>.rds.amazonaws.com
35
+ # RDS Proxy: <proxy>.proxy-<xyz>.<region>.rds.amazonaws.com
36
+ # RDS Proxy Custom: <name>.endpoint.proxy-<xyz>.<region>.rds.amazonaws.com
37
+ # Global DB Writer: <gdb>.global-<xyz>.global.rds.amazonaws.com
38
+ # Limitless: <db>.shardgrp-<xyz>.<region>.rds.amazonaws.com
39
+ #
40
+ # China regions use a different structure:
41
+ # New: <xyz>.rds.<region>.amazonaws.com.cn
42
+ # Old: <xyz>.<region>.rds.amazonaws.com.cn
43
+ #
44
+ # Gov/ISO/ISOB regions:
45
+ # <xyz>.(rds|rds-fips).<region>.(amazonaws.com|c2s.ic.gov|sc2s.sgov.gov)
46
+ module RdsUtils
47
+ extend self
48
+
49
+ # Authoritative allow-list of AWS regions that can host an RDS/Aurora endpoint.
50
+ KNOWN_REGIONS = %w[
51
+ af-south-1
52
+ ap-east-1 ap-east-2
53
+ ap-northeast-1 ap-northeast-2 ap-northeast-3
54
+ ap-south-1 ap-south-2
55
+ ap-southeast-1 ap-southeast-2 ap-southeast-3 ap-southeast-4
56
+ ap-southeast-5 ap-southeast-6 ap-southeast-7
57
+ ca-central-1 ca-west-1
58
+ cn-north-1 cn-northwest-1
59
+ eu-central-1 eu-central-2
60
+ eu-isoe-west-1
61
+ eu-north-1
62
+ eu-south-1 eu-south-2
63
+ eu-west-1 eu-west-2 eu-west-3
64
+ eusc-de-east-1
65
+ il-central-1
66
+ me-central-1 me-south-1
67
+ mx-central-1
68
+ sa-east-1
69
+ us-east-1 us-east-2
70
+ us-gov-east-1 us-gov-west-1
71
+ us-iso-east-1 us-iso-west-1
72
+ us-isob-east-1 us-isob-west-1
73
+ us-isof-east-1 us-isof-south-1
74
+ us-west-1 us-west-2
75
+ ].to_set.freeze
76
+
77
+ # -- Standard commercial regions --
78
+ AURORA_DNS_PATTERN = /
79
+ ^(?<instance>.+)\.
80
+ (?<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?
81
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
82
+ \.(?:rds|rds-fips)\.amazonaws\.(?:com|au|eu|uk)\.?)$
83
+ /ix
84
+
85
+ AURORA_CLUSTER_PATTERN = /
86
+ ^(?<instance>.+)\.
87
+ (?<dns>cluster-|cluster-ro-)+
88
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
89
+ \.(?:rds|rds-fips)\.amazonaws\.(?:com|au|eu|uk)\.?)$
90
+ /ix
91
+
92
+ # -- Limitless (covers all TLD variants) --
93
+ AURORA_LIMITLESS_CLUSTER_PATTERN = /
94
+ (?<instance>.+)\.
95
+ (?<dns>shardgrp-)+
96
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
97
+ \.(?:rds|rds-fips)\.(?:amazonaws\.com\.?|amazonaws\.eu\.?|amazonaws\.au\.?|amazonaws\.uk\.?
98
+ |amazonaws\.com\.cn\.?|sc2s\.sgov\.gov\.?|c2s\.ic\.gov\.?))$
99
+ /ix
100
+
101
+ # -- China (new format: <xyz>.rds.<region>.amazonaws.com.cn) --
102
+ AURORA_CHINA_DNS_PATTERN = /
103
+ ^(?<instance>.+)\.
104
+ (?<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?
105
+ (?<domain>[a-zA-Z0-9]+\.(?:rds|rds-fips)\.(?<region>[a-zA-Z0-9-]+)
106
+ \.amazonaws\.com\.cn\.?)$
107
+ /ix
108
+
109
+ AURORA_CHINA_CLUSTER_PATTERN = /
110
+ ^(?<instance>.+)\.
111
+ (?<dns>cluster-|cluster-ro-)+
112
+ (?<domain>[a-zA-Z0-9]+\.(?:rds|rds-fips)\.(?<region>[a-zA-Z0-9-]+)
113
+ \.amazonaws\.com\.cn\.?)$
114
+ /ix
115
+
116
+ # -- China (old/legacy format: <xyz>.<region>.rds.amazonaws.com.cn) --
117
+ AURORA_OLD_CHINA_DNS_PATTERN = /
118
+ ^(?<instance>.+)\.
119
+ (?<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?
120
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
121
+ \.(?:rds|rds-fips)\.amazonaws\.com\.cn\.?)$
122
+ /ix
123
+
124
+ AURORA_OLD_CHINA_CLUSTER_PATTERN = /
125
+ ^(?<instance>.+)\.
126
+ (?<dns>cluster-|cluster-ro-)+
127
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
128
+ \.(?:rds|rds-fips)\.amazonaws\.com\.cn\.?)$
129
+ /ix
130
+
131
+ # -- Gov / ISO / ISOB regions --
132
+ AURORA_GOV_DNS_PATTERN = /
133
+ ^(?<instance>.+)\.
134
+ (?<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?
135
+ (?<domain>[a-zA-Z0-9]+\.(?:rds|rds-fips)\.(?<region>[a-zA-Z0-9-]+)
136
+ \.(?:amazonaws\.com\.?|c2s\.ic\.gov\.?|sc2s\.sgov\.gov\.?))$
137
+ /ix
138
+
139
+ AURORA_GOV_CLUSTER_PATTERN = /
140
+ ^(?<instance>.+)\.
141
+ (?<dns>cluster-|cluster-ro-)+
142
+ (?<domain>[a-zA-Z0-9]+\.(?:rds|rds-fips)\.(?<region>[a-zA-Z0-9-]+)
143
+ \.(?:amazonaws\.com\.?|c2s\.ic\.gov\.?|sc2s\.sgov\.gov\.?))$
144
+ /ix
145
+
146
+ # -- ELB (region extraction only) --
147
+ ELB_PATTERN = /
148
+ ^(?<instance>.+)\.elb\.
149
+ (?<region>[a-zA-Z0-9-]+)\.amazonaws\.(?:com|au|eu|uk)\.?$
150
+ /ix
151
+
152
+ # -- IP addresses --
153
+ IP_V4 = /
154
+ ^(?:[1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.
155
+ (?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}
156
+ (?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
157
+ /x
158
+
159
+ IP_V6 = /^[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){7}$/i
160
+
161
+ IP_V6_COMPRESSED = /
162
+ ^(?:[0-9a-f]{1,4}(?::[0-9a-f]{1,4}){0,5})?
163
+ ::
164
+ (?:[0-9a-f]{1,4}(?::[0-9a-f]{1,4}){0,5})?$
165
+ /ix
166
+
167
+ # -- Blue/green deployment --
168
+ BG_GREEN_HOST_PATTERN = /(?<prefix>-green-[0-9a-z]{6})\./i
169
+ BG_GREEN_HOSTID_PATTERN = /\A(.+)-green-[0-9a-z]{6}\z/i
170
+ BG_OLD_HOST_PATTERN = /(?<prefix>-old1)\./i
171
+
172
+ # -- Global database --
173
+ AURORA_GLOBAL_WRITER_DNS_PATTERN = /
174
+ ^(?<instance>.+)\.
175
+ (?<dns>global-)?
176
+ (?<domain>[a-zA-Z0-9]+\.global\.rds\.amazonaws\.com\.?)$
177
+ /ix
178
+
179
+ # -- RDS Proxy endpoints --
180
+ RDS_PROXY_ENDPOINT_DNS_PATTERN = /
181
+ ^(?<instance>.+)\.endpoint\.
182
+ (?<dns>proxy-)?
183
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
184
+ \.rds\.amazonaws\.com\.?)$
185
+ /ix
186
+
187
+ RDS_PROXY_ENDPOINT_CHINA_DNS_PATTERN = /
188
+ ^(?<instance>.+)\.endpoint\.
189
+ (?<dns>proxy-)+
190
+ (?<domain>[a-zA-Z0-9]+\.rds\.(?<region>[a-zA-Z0-9-]+)
191
+ \.amazonaws\.com\.cn\.?)$
192
+ /ix
193
+
194
+ RDS_PROXY_ENDPOINT_OLD_CHINA_DNS_PATTERN = /
195
+ ^(?<instance>.+)\.endpoint\.
196
+ (?<dns>proxy-)?
197
+ (?<domain>[a-zA-Z0-9]+\.(?<region>[a-zA-Z0-9-]+)
198
+ \.rds\.amazonaws\.com\.cn\.?)$
199
+ /ix
200
+
201
+ DNS_PATTERNS = [
202
+ AURORA_DNS_PATTERN,
203
+ AURORA_CHINA_DNS_PATTERN,
204
+ AURORA_OLD_CHINA_DNS_PATTERN,
205
+ AURORA_GOV_DNS_PATTERN
206
+ ].freeze
207
+
208
+ DNS_GROUP_PATTERNS = [
209
+ AURORA_DNS_PATTERN,
210
+ AURORA_CHINA_DNS_PATTERN,
211
+ AURORA_OLD_CHINA_DNS_PATTERN,
212
+ AURORA_GOV_DNS_PATTERN,
213
+ AURORA_GLOBAL_WRITER_DNS_PATTERN
214
+ ].freeze
215
+
216
+ CLUSTER_PATTERNS = [
217
+ [AURORA_CLUSTER_PATTERN, 'cluster-'],
218
+ [AURORA_CHINA_CLUSTER_PATTERN, 'cluster-'],
219
+ [AURORA_OLD_CHINA_CLUSTER_PATTERN, 'cluster-'],
220
+ [AURORA_GOV_CLUSTER_PATTERN, 'cluster-'],
221
+ [AURORA_LIMITLESS_CLUSTER_PATTERN, 'shardgrp-']
222
+ ].freeze
223
+
224
+ PROXY_ENDPOINT_PATTERNS = [
225
+ RDS_PROXY_ENDPOINT_DNS_PATTERN,
226
+ RDS_PROXY_ENDPOINT_CHINA_DNS_PATTERN,
227
+ RDS_PROXY_ENDPOINT_OLD_CHINA_DNS_PATTERN
228
+ ].freeze
229
+
230
+ # Caches the regex match groups per host. Only positive matches are ever stored (never nil),
231
+ # so a lock-free concurrent map is safe here and its reads avoid a mutex on the hot path.
232
+ @cached_matches = Concurrent::Map.new
233
+
234
+ # Caches the DNS-group token per host. Unlike the other two, this caches nil results (a host
235
+ # with no DNS group), which a concurrent map cannot hold, so it stays a plain hash guarded by
236
+ # @mutex.
237
+ @cached_dns_groups = {}
238
+ @mutex = Mutex.new
239
+ @prepare_host_func = nil
240
+
241
+ # Caches the final endpoint classification per host. Values are never nil, so like
242
+ # @cached_matches it uses a lock-free concurrent map; a hit skips the whole pattern sweep.
243
+ @cached_url_types = Concurrent::Map.new
244
+
245
+ def clear_cache
246
+ @cached_matches.clear
247
+ @cached_url_types.clear
248
+ @mutex.synchronize { @cached_dns_groups.clear }
249
+ end
250
+
251
+ attr_reader :prepare_host_func
252
+
253
+ # Changing how hosts are prepared can change their classification, so the classification cache
254
+ # (keyed by the raw host) must be dropped when the prepare function changes. The pattern caches
255
+ # are keyed by the prepared host and self-invalidate, so they do not need clearing here.
256
+ def prepare_host_func=(func)
257
+ @prepare_host_func = func
258
+ @cached_url_types.clear
259
+ end
260
+
261
+ def reset_prepare_host_func
262
+ @prepare_host_func = nil
263
+ @cached_url_types.clear
264
+ end
265
+
266
+ def rds_dns?(host)
267
+ prepared = prepared_host(host)
268
+ groups = cache_match(prepared, *DNS_PATTERNS)
269
+ if groups
270
+ dns = groups['dns']
271
+ @mutex.synchronize { @cached_dns_groups[prepared] = dns } if dns
272
+ true
273
+ else
274
+ false
275
+ end
276
+ end
277
+
278
+ def rds_cluster_dns?(host)
279
+ dns = dns_group(prepared_host(host))
280
+ !dns.nil? && (dns.casecmp('cluster-').zero? || dns.casecmp('cluster-ro-').zero?)
281
+ end
282
+
283
+ def rds_custom_cluster_dns?(host)
284
+ dns = dns_group(prepared_host(host))
285
+ !dns.nil? && dns.downcase.start_with?('cluster-custom-')
286
+ end
287
+
288
+ def rds_instance?(host)
289
+ prepared = prepared_host(host)
290
+ dns_group(prepared).nil? && rds_dns?(prepared)
291
+ end
292
+
293
+ def rds_proxy_dns?(host)
294
+ dns = dns_group(prepared_host(host))
295
+ !dns.nil? && dns.downcase.start_with?('proxy-')
296
+ end
297
+
298
+ def rds_proxy_endpoint_dns?(host)
299
+ prepared = prepared_host(host)
300
+ return false if blank?(prepared)
301
+
302
+ groups = match_first(prepared, *PROXY_ENDPOINT_PATTERNS)
303
+ !groups.nil? && !groups['dns'].nil? && !groups['instance'].nil?
304
+ end
305
+
306
+ def writer_cluster_dns?(host)
307
+ dns = dns_group(prepared_host(host))
308
+ !dns.nil? && dns.casecmp('cluster-').zero?
309
+ end
310
+
311
+ def reader_cluster_dns?(host)
312
+ dns = dns_group(prepared_host(host))
313
+ !dns.nil? && dns.casecmp('cluster-ro-').zero?
314
+ end
315
+
316
+ def limitless_db_shard_group_dns?(host)
317
+ dns = dns_group(prepared_host(host))
318
+ !dns.nil? && dns.casecmp('shardgrp-').zero?
319
+ end
320
+
321
+ def global_db_writer_cluster_dns?(host)
322
+ dns = dns_group(prepared_host(host))
323
+ !dns.nil? && dns.casecmp('global-').zero?
324
+ end
325
+
326
+ def ip?(host)
327
+ ipv4?(host) || ipv6?(host)
328
+ end
329
+
330
+ def ipv4?(host)
331
+ !blank?(host) && IP_V4.match?(host)
332
+ end
333
+
334
+ def ipv6?(host)
335
+ !blank?(host) && (IP_V6.match?(host) || IP_V6_COMPRESSED.match?(host))
336
+ end
337
+
338
+ # Priority-ordered type checks; first match wins.
339
+ RDS_TYPE_CHECKS = [
340
+ [:ip?, RdsUrlType::IP_ADDRESS],
341
+ [:global_db_writer_cluster_dns?, RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER],
342
+ [:writer_cluster_dns?, RdsUrlType::RDS_WRITER_CLUSTER],
343
+ [:reader_cluster_dns?, RdsUrlType::RDS_READER_CLUSTER],
344
+ [:rds_custom_cluster_dns?, RdsUrlType::RDS_CUSTOM_CLUSTER],
345
+ [:limitless_db_shard_group_dns?, RdsUrlType::RDS_AURORA_LIMITLESS_DB_SHARD_GROUP],
346
+ [:rds_proxy_endpoint_dns?, RdsUrlType::RDS_PROXY_ENDPOINT],
347
+ [:rds_proxy_dns?, RdsUrlType::RDS_PROXY],
348
+ [:rds_dns?, RdsUrlType::RDS_INSTANCE]
349
+ ].freeze
350
+
351
+ def identify_rds_type(host)
352
+ return RdsUrlType::OTHER if blank?(host)
353
+
354
+ @cached_url_types.compute_if_absent(host) { classify_rds_type(host) }
355
+ end
356
+
357
+ def classify_rds_type(host)
358
+ RDS_TYPE_CHECKS.each do |check, type|
359
+ return type if send(check, host)
360
+ end
361
+
362
+ RdsUrlType::OTHER
363
+ end
364
+
365
+ def rds_host_id(host)
366
+ prepared = prepared_host(host)
367
+ return nil if blank?(prepared)
368
+
369
+ groups = cache_match(prepared, *DNS_PATTERNS)
370
+ return nil unless groups
371
+
372
+ groups['dns'] ? groups['instance'] : nil
373
+ end
374
+
375
+ def rds_cluster_id(host)
376
+ prepared = prepared_host(host)
377
+ return nil if blank?(prepared)
378
+
379
+ groups = cache_match(prepared, *DNS_GROUP_PATTERNS)
380
+ return nil unless groups
381
+
382
+ groups['dns'] ? groups['instance'] : nil
383
+ end
384
+
385
+ def rds_instance_name(host)
386
+ prepared = prepared_host(host)
387
+ return nil if blank?(prepared)
388
+
389
+ groups = cache_match(prepared, *DNS_PATTERNS)
390
+ return nil unless groups
391
+
392
+ groups['dns'].nil? ? groups['instance'] : nil
393
+ end
394
+
395
+ def rds_instance_host_pattern(host)
396
+ prepared = prepared_host(host)
397
+ return '?' if blank?(prepared)
398
+
399
+ groups = cache_match(prepared, *DNS_PATTERNS)
400
+ domain = groups&.[]('domain')
401
+ domain ? "?.#{domain}" : '?'
402
+ end
403
+
404
+ def rds_region(host)
405
+ prepared = prepared_host(host)
406
+ return nil if blank?(prepared)
407
+
408
+ groups = cache_match(prepared, *DNS_PATTERNS)
409
+ region = groups&.[]('region')
410
+ return region if valid_region?(region)
411
+
412
+ elb_match = ELB_PATTERN.match(prepared)
413
+ elb_region = elb_match&.[]('region')
414
+ valid_region?(elb_region) ? elb_region : nil
415
+ end
416
+
417
+ # Whether the given value is a real AWS region known to this wrapper.
418
+ #
419
+ # Case-insensitive. Returns false for nil/blank and for typos or otherwise
420
+ # incorrect values (e.g. "us-esat-1").
421
+ #
422
+ # @param region [String, nil]
423
+ # @return [Boolean]
424
+ def valid_region?(region)
425
+ return false if blank?(region)
426
+
427
+ KNOWN_REGIONS.include?(region.downcase)
428
+ end
429
+
430
+ # Whether the host carries a region segment that was parsed out of the
431
+ # endpoint but is NOT a real AWS region (i.e. a likely typo such as
432
+ # "us-esat-1").
433
+ #
434
+ # @param host [String, nil]
435
+ # @return [Boolean] true only when a region was captured and is invalid
436
+ def region_typo?(host)
437
+ prepared = prepared_host(host)
438
+ return false if blank?(prepared)
439
+
440
+ # Aurora Global Database endpoints carry the literal "global" in the region
441
+ # slot (e.g. "<id>.global.rds.amazonaws.com"), must never be treated as a typo.
442
+ return false if AURORA_GLOBAL_WRITER_DNS_PATTERN.match?(prepared)
443
+
444
+ groups = cache_match(prepared, *DNS_PATTERNS)
445
+ captured = groups&.[]('region')
446
+ return false if captured.nil? || captured.empty?
447
+
448
+ !valid_region?(captured)
449
+ end
450
+
451
+ def same_region?(host1, host2)
452
+ return false if blank?(host1) || blank?(host2)
453
+
454
+ region1 = rds_region(host1)
455
+ region2 = rds_region(host2)
456
+ !region1.nil? && region1.casecmp(region2).zero?
457
+ end
458
+
459
+ def rds_cluster_host_url(host)
460
+ prepared = prepared_host(host)
461
+ return nil if blank?(prepared)
462
+
463
+ CLUSTER_PATTERNS.each do |pattern, prefix|
464
+ m = pattern.match(prepared)
465
+ next unless m
466
+
467
+ return "#{m[:instance]}.#{prefix}#{m[:domain]}"
468
+ end
469
+
470
+ nil
471
+ end
472
+
473
+ def dns_pattern_valid?(pattern)
474
+ return false if blank?(pattern)
475
+
476
+ pattern.count('?') == 1
477
+ end
478
+
479
+ def remove_port(host_and_port)
480
+ return host_and_port if blank?(host_and_port)
481
+
482
+ idx = host_and_port.index(':')
483
+ idx ? host_and_port[0...idx] : host_and_port
484
+ end
485
+
486
+ def green_instance?(host)
487
+ prepared = prepared_host(host)
488
+ !blank?(prepared) && BG_GREEN_HOST_PATTERN.match?(prepared)
489
+ end
490
+
491
+ def old_instance?(host)
492
+ prepared = prepared_host(host)
493
+ !blank?(prepared) && BG_OLD_HOST_PATTERN.match?(prepared)
494
+ end
495
+
496
+ def not_old_instance?(host)
497
+ prepared = prepared_host(host)
498
+ blank?(prepared) || !BG_OLD_HOST_PATTERN.match?(prepared)
499
+ end
500
+
501
+ def not_green_and_old_prefix_instance?(host)
502
+ prepared = prepared_host(host)
503
+ !blank?(prepared) &&
504
+ !BG_GREEN_HOST_PATTERN.match?(prepared) &&
505
+ !BG_OLD_HOST_PATTERN.match?(prepared)
506
+ end
507
+
508
+ def remove_green_instance_prefix(host)
509
+ return nil if host.nil?
510
+ return host if host.empty?
511
+
512
+ prepared = prepared_host(host)
513
+ m = BG_GREEN_HOST_PATTERN.match(prepared)
514
+ unless m
515
+ hostid_m = BG_GREEN_HOSTID_PATTERN.match(prepared)
516
+ return hostid_m ? hostid_m[1] : host
517
+ end
518
+
519
+ prefix = m[:prefix]
520
+ return host if blank?(prefix)
521
+
522
+ host.sub("#{prefix}.", '.')
523
+ end
524
+
525
+ private
526
+
527
+ def blank?(str)
528
+ str.nil? || str.empty?
529
+ end
530
+
531
+ def prepared_host(host)
532
+ return host unless @prepare_host_func
533
+
534
+ result = @prepare_host_func.call(host)
535
+ result || host
536
+ end
537
+
538
+ def cache_match(host, *patterns)
539
+ # Only non-nil match groups are stored, so a nil lookup means "not cached".
540
+ cached = @cached_matches[host]
541
+ return cached if cached
542
+
543
+ patterns.each do |pattern|
544
+ m = pattern.match(host)
545
+ next unless m
546
+
547
+ groups = m.named_captures
548
+ @cached_matches[host] = groups
549
+ return groups
550
+ end
551
+
552
+ nil
553
+ end
554
+
555
+ def dns_group(host)
556
+ return nil if blank?(host)
557
+
558
+ @mutex.synchronize { return @cached_dns_groups[host] if @cached_dns_groups.key?(host) }
559
+
560
+ groups = cache_match(host, *DNS_GROUP_PATTERNS)
561
+ dns = groups&.[]('dns')
562
+ @mutex.synchronize { @cached_dns_groups[host] = dns }
563
+ dns
564
+ end
565
+
566
+ def match_first(host, *patterns)
567
+ patterns.each do |pattern|
568
+ m = pattern.match(host)
569
+ return m.named_captures if m
570
+ end
571
+ nil
572
+ end
573
+ end
574
+ end
575
+ end