detector 0.2.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab9800bbb6b9322ca7e8115b438c89cf5da96febc891e1ae6cf55ff8de6ff011
4
- data.tar.gz: 4916567db9f219e16d9fe3bef938dbc51958d786205894a850074686c5e41400
3
+ metadata.gz: c2fdcd9559b7c9d9d40b8908d4ec47ef17ff63d62305832bbe7f69b10b05a35b
4
+ data.tar.gz: 0560305f80f5d1f5aea6eb5959ef7ebb3cc225479492a69594bf227d618804f1
5
5
  SHA512:
6
- metadata.gz: aad9b58db57085e77b3543c50297d57d779506b93e0f89fd7475df0de19c3b45170127588dc7754fae9f5e256feb1939cb5aba88c98bc56d6289f9413f866ad6
7
- data.tar.gz: 3dc3ef92bfe6bb083f90e282d15ab0866722da1dfaddb30b8cae8076150755fad0c31274649eef7e16a19b993fb07443d68d301eb236ffaa5c7beaf91c234216
6
+ metadata.gz: 82be9fdd64b14e11e3ff0ff0d927b2508129eb0a84d5a5a4340c7afd991273ddd83f114f6ffdfb1d2add0e84a9526ba8ccd727887ecf70d694f4fb8cc2a0e053
7
+ data.tar.gz: 53d7ee213ff018c23a7df00df47d8a3cf8525bb475a9eafb4415ee8ba9ffc4db3c2c22dbc48711f06fa2a390ef1f92a3fbf215451a2460022d34e3f23174bf79
data/bin/detector CHANGED
@@ -23,6 +23,10 @@ puts "Detected: #{detector.kind}"
23
23
  puts "Version: #{detector.version}"
24
24
  puts "Host: #{detector.host}:#{detector.port}"
25
25
 
26
+ if detector.respond_to?(:replication_available?) && !detector.replication_available?.nil?
27
+ puts "Replication: #{detector.replication_available? ? 'Available' : 'Not available'}"
28
+ end
29
+
26
30
  if detector.user_access_level
27
31
  puts "User access level: #{detector.user_access_level}"
28
32
  end
@@ -54,9 +58,13 @@ if detector.databases?
54
58
  puts "\nDatabase: #{db_name} (#{db[:size]})"
55
59
 
56
60
  if detector.tables?
57
- tables = detector.tables(db_name).first(3)
58
- puts " Tables: #{detector.table_count(db_name)}"
61
+ if db[:table_count]
62
+ puts " Tables: #{db[:table_count]}"
63
+ else
64
+ puts " Tables: #{detector.table_count(db_name)}"
65
+ end
59
66
 
67
+ tables = detector.tables(db_name).first(3)
60
68
  tables.each do |table|
61
69
  puts " - #{table[:name]}: #{table[:row_count]} rows (#{table[:size]})"
62
70
  end
@@ -31,7 +31,8 @@ module Detector
31
31
  db_list.each do |db_name|
32
32
  size_query = "SELECT
33
33
  IFNULL(FORMAT(SUM(data_length + index_length) / 1024 / 1024, 2), '0.00') AS size_mb,
34
- IFNULL(SUM(data_length + index_length), 0) AS raw_size
34
+ IFNULL(SUM(data_length + index_length), 0) AS raw_size,
35
+ COUNT(table_name) AS table_count
35
36
  FROM information_schema.TABLES
36
37
  WHERE table_schema = '#{db_name}'"
37
38
 
@@ -39,7 +40,8 @@ module Detector
39
40
  result << {
40
41
  name: db_name,
41
42
  size: "#{size_data['size_mb']} MB",
42
- raw_size: size_data['raw_size'].to_i
43
+ raw_size: size_data['raw_size'].to_i,
44
+ table_count: size_data['table_count'].to_i
43
45
  }
44
46
  end
45
47
 
@@ -44,15 +44,22 @@ module Detector
44
44
 
45
45
  def databases
46
46
  return [] unless connection
47
- @databases ||= connection.query("SELECT schema_name AS name,
47
+ @databases ||= connection.query("SELECT
48
+ schema_name AS name,
48
49
  FORMAT(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb,
49
- SUM(data_length + index_length) AS raw_size
50
+ SUM(data_length + index_length) AS raw_size,
51
+ COUNT(table_name) AS table_count
50
52
  FROM information_schema.SCHEMATA
51
- JOIN information_schema.TABLES ON table_schema = schema_name
53
+ LEFT JOIN information_schema.TABLES ON table_schema = schema_name
52
54
  WHERE schema_name NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
53
55
  GROUP BY schema_name
54
56
  ORDER BY raw_size DESC").map do |row|
55
- { name: row['name'], size: "#{row['size_mb']} MB", raw_size: row['raw_size'].to_i }
57
+ {
58
+ name: row['name'],
59
+ size: "#{row['size_mb']} MB",
60
+ raw_size: row['raw_size'].to_i,
61
+ table_count: row['table_count'].to_i
62
+ }
56
63
  end
57
64
  end
58
65
 
@@ -90,6 +97,10 @@ module Detector
90
97
  "mysql"
91
98
  end
92
99
 
100
+ def protocol_type
101
+ :tcp
102
+ end
103
+
93
104
  def user_access_level
94
105
  return nil unless connection
95
106
 
@@ -150,6 +161,32 @@ module Detector
150
161
  "Limited access"
151
162
  end
152
163
  end
164
+
165
+ def replication_available?
166
+ return nil unless connection
167
+
168
+ begin
169
+ # Check master status
170
+ master_result = connection.query("SHOW MASTER STATUS")
171
+ return true if master_result.count > 0
172
+
173
+ # Check if this is a slave
174
+ slave_result = connection.query("SHOW SLAVE STATUS")
175
+ return true if slave_result.count > 0
176
+
177
+ # Check if replication user exists or if binary logging is enabled
178
+ repl_users = connection.query("SELECT user FROM mysql.user WHERE Repl_slave_priv = 'Y'")
179
+ return true if repl_users.count > 0
180
+
181
+ # Check if binary logging is enabled (needed for replication)
182
+ binary_log = connection.query("SHOW VARIABLES LIKE 'log_bin'").first
183
+ return true if binary_log && binary_log['Value'] && binary_log['Value'].downcase == 'on'
184
+
185
+ false
186
+ rescue => e
187
+ nil
188
+ end
189
+ end
153
190
  end
154
191
  end
155
192
 
@@ -92,13 +92,48 @@ module Detector
92
92
 
93
93
  def databases
94
94
  return [] unless connection
95
- @databases ||= connection.exec("SELECT datname, pg_size_pretty(pg_database_size(datname)) as size,
96
- pg_database_size(datname) as raw_size
97
- FROM pg_database
98
- WHERE datistemplate = false
99
- ORDER BY raw_size DESC").map do |row|
100
- { name: row['datname'], size: row['size'], raw_size: row['raw_size'].to_i }
95
+ result = []
96
+
97
+ # Get the list of databases and their sizes
98
+ db_list = connection.exec("SELECT datname, pg_size_pretty(pg_database_size(datname)) as size,
99
+ pg_database_size(datname) as raw_size
100
+ FROM pg_database
101
+ WHERE datistemplate = false
102
+ ORDER BY raw_size DESC")
103
+
104
+ # For each database, get table count
105
+ db_list.each do |row|
106
+ db_name = row['datname']
107
+
108
+ # Skip system databases or databases we can't connect to
109
+ next if ['postgres', 'template0', 'template1'].include?(db_name)
110
+
111
+ # Get table count for this database
112
+ table_count = 0
113
+
114
+ begin
115
+ # Create a temporary connection to count tables
116
+ temp_conn = PG::Connection.new(host: host, port: port, user: uri.user,
117
+ password: uri.password, dbname: db_name) rescue nil
118
+
119
+ if temp_conn
120
+ table_count = temp_conn.exec("SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'").first['count'].to_i
121
+ temp_conn.close
122
+ end
123
+ rescue
124
+ # Skip if we can't connect
125
+ next
126
+ end
127
+
128
+ result << {
129
+ name: db_name,
130
+ size: row['size'],
131
+ raw_size: row['raw_size'].to_i,
132
+ table_count: table_count
133
+ }
101
134
  end
135
+
136
+ @databases = result
102
137
  end
103
138
 
104
139
  def connection_count
@@ -115,6 +150,10 @@ module Detector
115
150
  "psql"
116
151
  end
117
152
 
153
+ def protocol_type
154
+ :tcp
155
+ end
156
+
118
157
  def user_access_level
119
158
  return nil unless connection
120
159
 
@@ -154,6 +193,17 @@ module Detector
154
193
  end
155
194
  end
156
195
  end
196
+
197
+ def replication_available?
198
+ return nil unless connection
199
+
200
+ begin
201
+ replication_roles = connection.exec("SELECT rolname, rolreplication FROM pg_roles WHERE rolreplication = true;")
202
+ !replication_roles.values.empty?
203
+ rescue => e
204
+ nil
205
+ end
206
+ end
157
207
  end
158
208
  end
159
209
 
@@ -72,6 +72,10 @@ module Detector
72
72
  "redis-cli"
73
73
  end
74
74
 
75
+ def protocol_type
76
+ :tcp
77
+ end
78
+
75
79
  def user_access_level
76
80
  return nil unless connection
77
81
 
@@ -147,6 +151,26 @@ module Detector
147
151
  end
148
152
  end
149
153
  end
154
+
155
+ def replication_available?
156
+ return nil unless connection && info
157
+
158
+ begin
159
+ # Check if this is a master in a replication setup
160
+ if info['role'] == 'master'
161
+ return true
162
+ end
163
+
164
+ # Check if server has replication enabled
165
+ if info['connected_slaves'].to_i > 0 || info['slave_read_only'] == '0'
166
+ return true
167
+ end
168
+
169
+ false
170
+ rescue => e
171
+ nil
172
+ end
173
+ end
150
174
  end
151
175
  end
152
176
 
@@ -33,6 +33,10 @@ module Detector
33
33
  "telnet"
34
34
  end
35
35
 
36
+ def protocol_type
37
+ :tcp
38
+ end
39
+
36
40
  def user_access_level
37
41
  return nil unless connection
38
42
 
data/lib/detector/base.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'socket'
2
2
  require 'geocoder'
3
+ require_relative 'region'
4
+ require_relative 'vendor'
3
5
 
4
6
  module Detector
5
7
  class Base
@@ -108,201 +110,7 @@ module Detector
108
110
 
109
111
  def region
110
112
  return nil unless valid?
111
-
112
- # Try to determine region from hostname first
113
- hostname = host.to_s.downcase
114
-
115
- # AWS regions from hostname
116
- if hostname =~ /amazonaws\.com/ || hostname =~ /aws/
117
- return "us-east-1" if hostname =~ /us-east-1|virginia|nova/
118
- return "us-east-2" if hostname =~ /us-east-2|ohio/
119
- return "us-west-1" if hostname =~ /us-west-1|california|norcal/
120
- return "us-west-2" if hostname =~ /us-west-2|oregon/
121
- return "af-south-1" if hostname =~ /af-south|cape-town/
122
- return "ap-east-1" if hostname =~ /ap-east|hong-kong/
123
- return "ap-south-1" if hostname =~ /ap-south|mumbai/
124
- return "ap-northeast-1" if hostname =~ /ap-northeast-1|tokyo/
125
- return "ap-northeast-2" if hostname =~ /ap-northeast-2|seoul/
126
- return "ap-northeast-3" if hostname =~ /ap-northeast-3|osaka/
127
- return "ap-southeast-1" if hostname =~ /ap-southeast-1|singapore/
128
- return "ap-southeast-2" if hostname =~ /ap-southeast-2|sydney/
129
- return "ca-central-1" if hostname =~ /ca-central|canada|montreal/
130
- return "eu-central-1" if hostname =~ /eu-central-1|frankfurt/
131
- return "eu-west-1" if hostname =~ /eu-west-1|ireland|dublin/
132
- return "eu-west-2" if hostname =~ /eu-west-2|london/
133
- return "eu-west-3" if hostname =~ /eu-west-3|paris/
134
- return "eu-north-1" if hostname =~ /eu-north-1|stockholm/
135
- return "eu-south-1" if hostname =~ /eu-south-1|milan/
136
- return "me-south-1" if hostname =~ /me-south-1|bahrain/
137
- return "sa-east-1" if hostname =~ /sa-east-1|sao-paulo/
138
- end
139
-
140
- # Azure regions from hostname
141
- if hostname =~ /azure|windows\.net|cloudapp/
142
- return "eastus" if hostname =~ /eastus|virginia/
143
- return "eastus2" if hostname =~ /eastus2/
144
- return "centralus" if hostname =~ /centralus|iowa/
145
- return "northcentralus" if hostname =~ /northcentralus|illinois/
146
- return "southcentralus" if hostname =~ /southcentralus|texas/
147
- return "westus" if hostname =~ /westus|california/
148
- return "westus2" if hostname =~ /westus2|washington/
149
- return "westus3" if hostname =~ /westus3|phoenix/
150
- return "australiaeast" if hostname =~ /australiaeast|sydney/
151
- return "brazilsouth" if hostname =~ /brazilsouth|sao-paulo/
152
- return "canadacentral" if hostname =~ /canadacentral|toronto/
153
- return "centralindia" if hostname =~ /centralindia|pune/
154
- return "eastasia" if hostname =~ /eastasia|hong-kong/
155
- return "francecentral" if hostname =~ /francecentral|paris/
156
- return "germanywestcentral" if hostname =~ /germanywestcentral|frankfurt/
157
- return "japaneast" if hostname =~ /japaneast|tokyo/
158
- return "koreacentral" if hostname =~ /koreacentral|seoul/
159
- return "northeurope" if hostname =~ /northeurope|ireland/
160
- return "southeastasia" if hostname =~ /southeastasia|singapore/
161
- return "southindia" if hostname =~ /southindia|chennai/
162
- return "swedencentral" if hostname =~ /swedencentral|stockholm/
163
- return "switzerlandnorth" if hostname =~ /switzerlandnorth|zurich/
164
- return "uksouth" if hostname =~ /uksouth|london/
165
- return "westeurope" if hostname =~ /westeurope|netherlands/
166
- end
167
-
168
- # Google Cloud regions from hostname
169
- if hostname =~ /google|googlecloud|gcp|appspot/
170
- return "us-central1" if hostname =~ /us-central1|iowa/
171
- return "us-east1" if hostname =~ /us-east1|south-carolina/
172
- return "us-east4" if hostname =~ /us-east4|virginia/
173
- return "us-west1" if hostname =~ /us-west1|oregon/
174
- return "us-west2" if hostname =~ /us-west2|los-angeles/
175
- return "us-west3" if hostname =~ /us-west3|salt-lake-city/
176
- return "us-west4" if hostname =~ /us-west4|las-vegas/
177
- return "northamerica-northeast1" if hostname =~ /northamerica-northeast1|montreal/
178
- return "southamerica-east1" if hostname =~ /southamerica-east1|sao-paulo/
179
- return "europe-west1" if hostname =~ /europe-west1|belgium/
180
- return "europe-west2" if hostname =~ /europe-west2|london/
181
- return "europe-west3" if hostname =~ /europe-west3|frankfurt/
182
- return "europe-west4" if hostname =~ /europe-west4|netherlands/
183
- return "europe-west6" if hostname =~ /europe-west6|zurich/
184
- return "europe-north1" if hostname =~ /europe-north1|finland/
185
- return "asia-east1" if hostname =~ /asia-east1|taiwan/
186
- return "asia-east2" if hostname =~ /asia-east2|hong-kong/
187
- return "asia-northeast1" if hostname =~ /asia-northeast1|tokyo/
188
- return "asia-northeast2" if hostname =~ /asia-northeast2|osaka/
189
- return "asia-northeast3" if hostname =~ /asia-northeast3|seoul/
190
- return "asia-south1" if hostname =~ /asia-south1|mumbai/
191
- return "asia-southeast1" if hostname =~ /asia-southeast1|singapore/
192
- return "asia-southeast2" if hostname =~ /asia-southeast2|jakarta/
193
- return "australia-southeast1" if hostname =~ /australia-southeast1|sydney/
194
- end
195
-
196
- # Fallback to IP-based lookup via Geocoder
197
- return nil unless geo
198
-
199
- # City-based detection for common cloud cities
200
- city = geo&.data&.dig('city')&.downcase
201
- if city
202
- case city
203
- when 'ashburn', 'sterling', 'herndon', 'chantilly'
204
- return "us-east-1" # AWS us-east-1 or equivalent
205
- when 'columbus', 'dublin', 'hilliard'
206
- return "us-east-2" # AWS us-east-2
207
- when 'san jose', 'santa clara', 'milpitas', 'fremont'
208
- return "us-west-1" # AWS us-west-1
209
- when 'portland', 'hillsboro', 'prineville', 'the dalles'
210
- return "us-west-2" # AWS us-west-2 / GCP us-west1
211
- when 'phoenix', 'tempe', 'mesa'
212
- return "westus3" # Azure westus3
213
- when 'dallas', 'fort worth', 'san antonio'
214
- return "southcentralus" # Azure southcentralus
215
- when 'montreal', 'beauharnois', 'quebec'
216
- return "ca-central-1" # AWS ca-central-1
217
- when 'toronto'
218
- return "canadacentral" # Azure canadacentral
219
- when 'frankfurt', 'munich'
220
- return "eu-central-1" # AWS eu-central-1
221
- when 'london'
222
- return "eu-west-2" # AWS eu-west-2
223
- when 'paris'
224
- return "eu-west-3" # AWS eu-west-3
225
- when 'dublin', 'clondalkin'
226
- return "eu-west-1" # AWS eu-west-1
227
- when 'stockholm'
228
- return "eu-north-1" # AWS eu-north-1
229
- when 'milan'
230
- return "eu-south-1" # AWS eu-south-1
231
- when 'sydney', 'melbourne'
232
- return "ap-southeast-2" # AWS ap-southeast-2
233
- when 'singapore'
234
- return "ap-southeast-1" # AWS ap-southeast-1
235
- when 'tokyo', 'osaka'
236
- return "ap-northeast-1" # AWS ap-northeast-1
237
- when 'seoul'
238
- return "ap-northeast-2" # AWS ap-northeast-2
239
- when 'mumbai'
240
- return "ap-south-1" # AWS ap-south-1
241
- when 'hong kong'
242
- return "ap-east-1" # AWS ap-east-1
243
- when 'são paulo', 'sao paulo'
244
- return "sa-east-1" # AWS sa-east-1
245
- end
246
- end
247
-
248
- # Region/State-based mapping to approximate cloud region
249
- region_name = geo&.data&.dig('region')
250
- country = geo&.data&.dig('country')
251
-
252
- if country == 'United States'
253
- case region_name
254
- when 'Virginia', 'Maryland', 'District of Columbia'
255
- return "us-east-1"
256
- when 'Ohio', 'Indiana', 'Michigan'
257
- return "us-east-2"
258
- when 'California'
259
- return "us-west-1"
260
- when 'Oregon', 'Washington', 'Idaho'
261
- return "us-west-2"
262
- when 'Nevada', 'Utah', 'Arizona'
263
- return "us-west-2"
264
- when 'Texas', 'Oklahoma', 'Louisiana'
265
- return "southcentralus"
266
- when 'Illinois', 'Iowa', 'Minnesota', 'Missouri', 'Wisconsin'
267
- return "us-central1"
268
- end
269
- elsif country == 'Canada'
270
- case region_name
271
- when 'Quebec'
272
- return "ca-central-1"
273
- when 'Ontario'
274
- return "canadacentral"
275
- end
276
- elsif country == 'Brazil'
277
- return "sa-east-1"
278
- elsif country == 'Ireland'
279
- return "eu-west-1"
280
- elsif country == 'United Kingdom'
281
- return "eu-west-2"
282
- elsif country == 'France'
283
- return "eu-west-3"
284
- elsif country == 'Germany'
285
- return "eu-central-1"
286
- elsif country == 'Sweden' || country == 'Norway' || country == 'Finland'
287
- return "eu-north-1"
288
- elsif country == 'Italy'
289
- return "eu-south-1"
290
- elsif country == 'India'
291
- return "ap-south-1"
292
- elsif country == 'Singapore'
293
- return "ap-southeast-1"
294
- elsif country == 'Australia'
295
- return "ap-southeast-2"
296
- elsif country == 'Japan'
297
- return "ap-northeast-1"
298
- elsif country == 'South Korea'
299
- return "ap-northeast-2"
300
- elsif country == 'Hong Kong'
301
- return "ap-east-1"
302
- end
303
-
304
- # Final fallback
305
- region_name || geo&.data&.dig('country_code')&.downcase
113
+ Region.detect_region(host, geo)
306
114
  end
307
115
 
308
116
  def asn
@@ -320,15 +128,53 @@ module Detector
320
128
 
321
129
  def ping
322
130
  return nil unless valid?
323
- tcp_test
131
+ transport?
132
+ end
133
+
134
+ def transport?
135
+ protocol_type == :tcp ? tcp_test : udp_test
136
+ end
137
+
138
+ # Should be implemented by subclasses
139
+ def protocol_type
140
+ :tcp # Default to TCP
324
141
  end
325
142
 
326
143
  def tcp_test
327
- TCPSocket.new(ip, port).present? rescue nil
144
+ return nil unless ip && port
145
+ begin
146
+ socket = TCPSocket.new(ip, port)
147
+ socket.close
148
+ true
149
+ rescue => e
150
+ nil
151
+ end
152
+ end
153
+
154
+ def udp_test
155
+ return nil unless ip && port
156
+ begin
157
+ socket = UDPSocket.new
158
+ socket.connect(ip, port)
159
+ socket.send("", 0) # Send empty packet as probe
160
+ socket.close
161
+ true
162
+ rescue => e
163
+ nil
164
+ end
328
165
  end
329
166
 
330
167
  def table_count
331
- nil
168
+ return nil unless valid? && tables? && connection?
169
+
170
+ count = 0
171
+ database_list = databases
172
+
173
+ database_list.each do |db|
174
+ count += tables(db).size
175
+ end
176
+
177
+ count.zero? ? nil : count
332
178
  end
333
179
 
334
180
  def database_count
@@ -363,78 +209,15 @@ module Detector
363
209
  def infrastructure
364
210
  return nil unless valid?
365
211
 
366
- hostname = host.to_s.downcase
367
- case hostname
368
- when /amazon/, /aws/, /amazonaws/, /ec2/, /s3/, /dynamodb/, /rds\./, /elasticbeanstalk/
369
- "Amazon Web Services"
370
- when /google/, /googlecloud/, /appspot/, /gcp/, /compute\./, /cloud\.g/
371
- "Google Cloud Platform"
372
- when /azure/, /azurewebsites/, /cloudapp\./, /windows\.net/
373
- "Microsoft Azure"
374
- when /antimony/
375
- "Build.io"
376
- when /heroku/, /herokuapp/
377
- "Heroku"
378
- when /digitalocean/, /droplet/
379
- "DigitalOcean"
380
- when /linode/, /linodeobjects/
381
- "Linode"
382
- when /vultr/
383
- "Vultr"
384
- when /netlify/
385
- "Netlify"
386
- when /vercel/, /zeit\.co/, /now\.sh/
387
- "Vercel"
388
- when /github\.io/, /githubusercontent/, /github\.dev/
389
- "GitHub"
390
- when /gitlab\.io/, /gitlab-static/
391
- "GitLab"
392
- when /oracle/, /oraclecloud/
393
- "Oracle Cloud"
394
- when /ibm/, /bluemix/, /ibmcloud/
395
- "IBM Cloud"
396
- when /cloudflare/, /workers\.dev/
397
- "Cloudflare"
398
- when /fastly/
399
- "Fastly"
400
- when /akamai/
401
- "Akamai"
402
- when /render\.com/
403
- "Render"
404
- when /fly\.io/
405
- "Fly.io"
406
- when /railway\.app/
407
- "Railway"
408
- when /upcloud/
409
- "UpCloud"
410
- when /hetzner/
411
- "Hetzner"
412
- when /ovh/, /ovhcloud/
413
- "OVH"
414
- when /scaleway/
415
- "Scaleway"
416
- when /contabo/
417
- "Contabo"
418
- when /dreamhost/
419
- "DreamHost"
420
- when /hostgator/
421
- "HostGator"
422
- when /bluehost/
423
- "Bluehost"
424
- when /siteground/
425
- "SiteGround"
426
- when /namecheap/
427
- "Namecheap"
428
- when /godaddy/
429
- "GoDaddy"
430
- when /ionos/
431
- "IONOS"
432
- when /hostinger/
433
- "Hostinger"
434
- else
435
- # If geo data available, return organization, otherwise nil
436
- geo&.data&.dig('org')
437
- end
212
+ provider = Vendor.detect_provider(host)
213
+ return provider if provider
214
+
215
+ # If geo data available, return organization, otherwise nil
216
+ geo&.data&.dig('org')
217
+ end
218
+
219
+ def replication_available?
220
+ nil
438
221
  end
439
222
  end
440
223
  end
@@ -0,0 +1,240 @@
1
+ module Detector
2
+ class Region
3
+ class << self
4
+ def detect_region(host, geo)
5
+ return nil unless host || geo
6
+
7
+ hostname = host.to_s.downcase
8
+
9
+ # Try to determine region from hostname first
10
+ aws_region = detect_aws_region(hostname)
11
+ return aws_region if aws_region
12
+
13
+ azure_region = detect_azure_region(hostname)
14
+ return azure_region if azure_region
15
+
16
+ gcp_region = detect_gcp_region(hostname)
17
+ return gcp_region if gcp_region
18
+
19
+ # Fallbacks to geocoder data
20
+ return nil unless geo
21
+
22
+ # Try city-based detection
23
+ city_region = detect_region_by_city(geo&.data&.dig('city')&.downcase)
24
+ return city_region if city_region
25
+
26
+ # Try region/country based detection
27
+ geo_region = detect_region_by_geography(geo&.data&.dig('region'), geo&.data&.dig('country'))
28
+ return geo_region if geo_region
29
+
30
+ # Final fallback
31
+ geo&.data&.dig('region') || geo&.data&.dig('country_code')&.downcase
32
+ end
33
+
34
+ private
35
+
36
+ def detect_aws_region(hostname)
37
+ return nil unless hostname =~ /amazonaws\.com/ || hostname =~ /aws/
38
+
39
+ return "us-east-1" if hostname =~ /us-east-1|virginia|nova/
40
+ return "us-east-2" if hostname =~ /us-east-2|ohio/
41
+ return "us-west-1" if hostname =~ /us-west-1|california|norcal/
42
+ return "us-west-2" if hostname =~ /us-west-2|oregon/
43
+ return "af-south-1" if hostname =~ /af-south|cape-town/
44
+ return "ap-east-1" if hostname =~ /ap-east|hong-kong/
45
+ return "ap-south-1" if hostname =~ /ap-south|mumbai/
46
+ return "ap-northeast-1" if hostname =~ /ap-northeast-1|tokyo/
47
+ return "ap-northeast-2" if hostname =~ /ap-northeast-2|seoul/
48
+ return "ap-northeast-3" if hostname =~ /ap-northeast-3|osaka/
49
+ return "ap-southeast-1" if hostname =~ /ap-southeast-1|singapore/
50
+ return "ap-southeast-2" if hostname =~ /ap-southeast-2|sydney/
51
+ return "ca-central-1" if hostname =~ /ca-central|canada|montreal/
52
+ return "eu-central-1" if hostname =~ /eu-central-1|frankfurt/
53
+ return "eu-west-1" if hostname =~ /eu-west-1|ireland|dublin/
54
+ return "eu-west-2" if hostname =~ /eu-west-2|london/
55
+ return "eu-west-3" if hostname =~ /eu-west-3|paris/
56
+ return "eu-north-1" if hostname =~ /eu-north-1|stockholm/
57
+ return "eu-south-1" if hostname =~ /eu-south-1|milan/
58
+ return "me-south-1" if hostname =~ /me-south-1|bahrain/
59
+ return "sa-east-1" if hostname =~ /sa-east-1|sao-paulo/
60
+
61
+ nil
62
+ end
63
+
64
+ def detect_azure_region(hostname)
65
+ return nil unless hostname =~ /azure|windows\.net|cloudapp/
66
+
67
+ return "eastus" if hostname =~ /eastus|virginia/
68
+ return "eastus2" if hostname =~ /eastus2/
69
+ return "centralus" if hostname =~ /centralus|iowa/
70
+ return "northcentralus" if hostname =~ /northcentralus|illinois/
71
+ return "southcentralus" if hostname =~ /southcentralus|texas/
72
+ return "westus" if hostname =~ /westus|california/
73
+ return "westus2" if hostname =~ /westus2|washington/
74
+ return "westus3" if hostname =~ /westus3|phoenix/
75
+ return "australiaeast" if hostname =~ /australiaeast|sydney/
76
+ return "brazilsouth" if hostname =~ /brazilsouth|sao-paulo/
77
+ return "canadacentral" if hostname =~ /canadacentral|toronto/
78
+ return "centralindia" if hostname =~ /centralindia|pune/
79
+ return "eastasia" if hostname =~ /eastasia|hong-kong/
80
+ return "francecentral" if hostname =~ /francecentral|paris/
81
+ return "germanywestcentral" if hostname =~ /germanywestcentral|frankfurt/
82
+ return "japaneast" if hostname =~ /japaneast|tokyo/
83
+ return "koreacentral" if hostname =~ /koreacentral|seoul/
84
+ return "northeurope" if hostname =~ /northeurope|ireland/
85
+ return "southeastasia" if hostname =~ /southeastasia|singapore/
86
+ return "southindia" if hostname =~ /southindia|chennai/
87
+ return "swedencentral" if hostname =~ /swedencentral|stockholm/
88
+ return "switzerlandnorth" if hostname =~ /switzerlandnorth|zurich/
89
+ return "uksouth" if hostname =~ /uksouth|london/
90
+ return "westeurope" if hostname =~ /westeurope|netherlands/
91
+
92
+ nil
93
+ end
94
+
95
+ def detect_gcp_region(hostname)
96
+ return nil unless hostname =~ /google|googlecloud|gcp|appspot/
97
+
98
+ return "us-central1" if hostname =~ /us-central1|iowa/
99
+ return "us-east1" if hostname =~ /us-east1|south-carolina/
100
+ return "us-east4" if hostname =~ /us-east4|virginia/
101
+ return "us-west1" if hostname =~ /us-west1|oregon/
102
+ return "us-west2" if hostname =~ /us-west2|los-angeles/
103
+ return "us-west3" if hostname =~ /us-west3|salt-lake-city/
104
+ return "us-west4" if hostname =~ /us-west4|las-vegas/
105
+ return "northamerica-northeast1" if hostname =~ /northamerica-northeast1|montreal/
106
+ return "southamerica-east1" if hostname =~ /southamerica-east1|sao-paulo/
107
+ return "europe-west1" if hostname =~ /europe-west1|belgium/
108
+ return "europe-west2" if hostname =~ /europe-west2|london/
109
+ return "europe-west3" if hostname =~ /europe-west3|frankfurt/
110
+ return "europe-west4" if hostname =~ /europe-west4|netherlands/
111
+ return "europe-west6" if hostname =~ /europe-west6|zurich/
112
+ return "europe-north1" if hostname =~ /europe-north1|finland/
113
+ return "asia-east1" if hostname =~ /asia-east1|taiwan/
114
+ return "asia-east2" if hostname =~ /asia-east2|hong-kong/
115
+ return "asia-northeast1" if hostname =~ /asia-northeast1|tokyo/
116
+ return "asia-northeast2" if hostname =~ /asia-northeast2|osaka/
117
+ return "asia-northeast3" if hostname =~ /asia-northeast3|seoul/
118
+ return "asia-south1" if hostname =~ /asia-south1|mumbai/
119
+ return "asia-southeast1" if hostname =~ /asia-southeast1|singapore/
120
+ return "asia-southeast2" if hostname =~ /asia-southeast2|jakarta/
121
+ return "australia-southeast1" if hostname =~ /australia-southeast1|sydney/
122
+
123
+ nil
124
+ end
125
+
126
+ def detect_region_by_city(city)
127
+ return nil unless city
128
+
129
+ case city
130
+ when 'ashburn', 'sterling', 'herndon', 'chantilly'
131
+ "us-east-1" # AWS us-east-1 or equivalent
132
+ when 'columbus', 'dublin', 'hilliard'
133
+ "us-east-2" # AWS us-east-2
134
+ when 'san jose', 'santa clara', 'milpitas', 'fremont'
135
+ "us-west-1" # AWS us-west-1
136
+ when 'portland', 'hillsboro', 'prineville', 'the dalles'
137
+ "us-west-2" # AWS us-west-2 / GCP us-west1
138
+ when 'phoenix', 'tempe', 'mesa'
139
+ "westus3" # Azure westus3
140
+ when 'dallas', 'fort worth', 'san antonio'
141
+ "southcentralus" # Azure southcentralus
142
+ when 'montreal', 'beauharnois', 'quebec'
143
+ "ca-central-1" # AWS ca-central-1
144
+ when 'toronto'
145
+ "canadacentral" # Azure canadacentral
146
+ when 'frankfurt', 'munich'
147
+ "eu-central-1" # AWS eu-central-1
148
+ when 'london'
149
+ "eu-west-2" # AWS eu-west-2
150
+ when 'paris'
151
+ "eu-west-3" # AWS eu-west-3
152
+ when 'dublin', 'clondalkin'
153
+ "eu-west-1" # AWS eu-west-1
154
+ when 'stockholm'
155
+ "eu-north-1" # AWS eu-north-1
156
+ when 'milan'
157
+ "eu-south-1" # AWS eu-south-1
158
+ when 'sydney', 'melbourne'
159
+ "ap-southeast-2" # AWS ap-southeast-2
160
+ when 'singapore'
161
+ "ap-southeast-1" # AWS ap-southeast-1
162
+ when 'tokyo', 'osaka'
163
+ "ap-northeast-1" # AWS ap-northeast-1
164
+ when 'seoul'
165
+ "ap-northeast-2" # AWS ap-northeast-2
166
+ when 'mumbai'
167
+ "ap-south-1" # AWS ap-south-1
168
+ when 'hong kong'
169
+ "ap-east-1" # AWS ap-east-1
170
+ when 'são paulo', 'sao paulo'
171
+ "sa-east-1" # AWS sa-east-1
172
+ else
173
+ nil
174
+ end
175
+ end
176
+
177
+ def detect_region_by_geography(region_name, country)
178
+ return nil unless region_name || country
179
+
180
+ if country == 'United States'
181
+ case region_name
182
+ when 'Virginia', 'Maryland', 'District of Columbia'
183
+ "us-east-1"
184
+ when 'Ohio', 'Indiana', 'Michigan'
185
+ "us-east-2"
186
+ when 'California'
187
+ "us-west-1"
188
+ when 'Oregon', 'Washington', 'Idaho'
189
+ "us-west-2"
190
+ when 'Nevada', 'Utah', 'Arizona'
191
+ "us-west-2"
192
+ when 'Texas', 'Oklahoma', 'Louisiana'
193
+ "southcentralus"
194
+ when 'Illinois', 'Iowa', 'Minnesota', 'Missouri', 'Wisconsin'
195
+ "us-central1"
196
+ else
197
+ nil
198
+ end
199
+ elsif country == 'Canada'
200
+ case region_name
201
+ when 'Quebec'
202
+ "ca-central-1"
203
+ when 'Ontario'
204
+ "canadacentral"
205
+ else
206
+ nil
207
+ end
208
+ elsif country == 'Brazil'
209
+ "sa-east-1"
210
+ elsif country == 'Ireland'
211
+ "eu-west-1"
212
+ elsif country == 'United Kingdom'
213
+ "eu-west-2"
214
+ elsif country == 'France'
215
+ "eu-west-3"
216
+ elsif country == 'Germany'
217
+ "eu-central-1"
218
+ elsif country == 'Sweden' || country == 'Norway' || country == 'Finland'
219
+ "eu-north-1"
220
+ elsif country == 'Italy'
221
+ "eu-south-1"
222
+ elsif country == 'India'
223
+ "ap-south-1"
224
+ elsif country == 'Singapore'
225
+ "ap-southeast-1"
226
+ elsif country == 'Australia'
227
+ "ap-southeast-2"
228
+ elsif country == 'Japan'
229
+ "ap-northeast-1"
230
+ elsif country == 'South Korea'
231
+ "ap-northeast-2"
232
+ elsif country == 'Hong Kong'
233
+ "ap-east-1"
234
+ else
235
+ nil
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,82 @@
1
+ module Detector
2
+ class Vendor
3
+ class << self
4
+ def detect_provider(hostname)
5
+ return nil unless hostname
6
+
7
+ hostname = hostname.to_s.downcase
8
+
9
+ case hostname
10
+ when /amazon/, /aws/, /amazonaws/, /ec2/, /s3/, /dynamodb/, /rds\./, /elasticbeanstalk/
11
+ "Amazon Web Services"
12
+ when /google/, /googlecloud/, /appspot/, /gcp/, /compute\./, /cloud\.g/
13
+ "Google Cloud Platform"
14
+ when /azure/, /azurewebsites/, /cloudapp\./, /windows\.net/
15
+ "Microsoft Azure"
16
+ when /antimony/
17
+ "Build.io"
18
+ when /heroku/, /herokuapp/
19
+ "Heroku"
20
+ when /digitalocean/, /droplet/
21
+ "DigitalOcean"
22
+ when /linode/, /linodeobjects/
23
+ "Linode"
24
+ when /vultr/
25
+ "Vultr"
26
+ when /netlify/
27
+ "Netlify"
28
+ when /vercel/, /zeit\.co/, /now\.sh/
29
+ "Vercel"
30
+ when /github\.io/, /githubusercontent/, /github\.dev/
31
+ "GitHub"
32
+ when /gitlab\.io/, /gitlab-static/
33
+ "GitLab"
34
+ when /oracle/, /oraclecloud/
35
+ "Oracle Cloud"
36
+ when /ibm/, /bluemix/, /ibmcloud/
37
+ "IBM Cloud"
38
+ when /cloudflare/, /workers\.dev/
39
+ "Cloudflare"
40
+ when /fastly/
41
+ "Fastly"
42
+ when /akamai/
43
+ "Akamai"
44
+ when /render\.com/
45
+ "Render"
46
+ when /fly\.io/
47
+ "Fly.io"
48
+ when /railway\.app/
49
+ "Railway"
50
+ when /upcloud/
51
+ "UpCloud"
52
+ when /hetzner/
53
+ "Hetzner"
54
+ when /ovh/, /ovhcloud/
55
+ "OVH"
56
+ when /scaleway/
57
+ "Scaleway"
58
+ when /contabo/
59
+ "Contabo"
60
+ when /dreamhost/
61
+ "DreamHost"
62
+ when /hostgator/
63
+ "HostGator"
64
+ when /bluehost/
65
+ "Bluehost"
66
+ when /siteground/
67
+ "SiteGround"
68
+ when /namecheap/
69
+ "Namecheap"
70
+ when /godaddy/
71
+ "GoDaddy"
72
+ when /ionos/
73
+ "IONOS"
74
+ when /hostinger/
75
+ "Hostinger"
76
+ else
77
+ nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,3 +1,3 @@
1
1
  module Detector
2
- VERSION = "0.2.1"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: detector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
@@ -183,6 +183,8 @@ files:
183
183
  - lib/detector/addons/redis.rb
184
184
  - lib/detector/addons/smtp.rb
185
185
  - lib/detector/base.rb
186
+ - lib/detector/region.rb
187
+ - lib/detector/vendor.rb
186
188
  - lib/detector/version.rb
187
189
  homepage: https://github.com/usiegj00/detector
188
190
  licenses: