fluyenta-ruby 0.1.14
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 +68 -0
- data/LICENSE +11 -0
- data/README.md +571 -0
- data/lib/brainzlab/beacon/client.rb +227 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +676 -0
- data/lib/brainzlab/context.rb +90 -0
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +159 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +250 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/development/logger.rb +150 -0
- data/lib/brainzlab/development/store.rb +121 -0
- data/lib/brainzlab/development.rb +72 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
- data/lib/brainzlab/devtools/assets/devtools.js +396 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/errors.rb +490 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +124 -0
- data/lib/brainzlab/flux.rb +184 -0
- data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
- data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
- data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
- data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
- data/lib/brainzlab/instrumentation/action_view.rb +380 -0
- data/lib/brainzlab/instrumentation/active_job.rb +569 -0
- data/lib/brainzlab/instrumentation/active_record.rb +559 -0
- data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
- data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
- data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +181 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +293 -0
- data/lib/brainzlab/instrumentation/graphql.rb +252 -0
- data/lib/brainzlab/instrumentation/httparty.rb +193 -0
- data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
- data/lib/brainzlab/instrumentation/net_http.rb +114 -0
- data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
- data/lib/brainzlab/instrumentation/railties.rb +134 -0
- data/lib/brainzlab/instrumentation/redis.rb +324 -0
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
- data/lib/brainzlab/instrumentation/stripe.rb +163 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
- data/lib/brainzlab/instrumentation.rb +360 -0
- data/lib/brainzlab/nerve/client.rb +235 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +203 -0
- data/lib/brainzlab/pulse/instrumentation.rb +401 -0
- data/lib/brainzlab/pulse/propagation.rb +241 -0
- data/lib/brainzlab/pulse/provisioner.rb +114 -0
- data/lib/brainzlab/pulse/tracer.rb +111 -0
- data/lib/brainzlab/pulse.rb +294 -0
- data/lib/brainzlab/rails/log_formatter.rb +807 -0
- data/lib/brainzlab/rails/log_subscriber.rb +334 -0
- data/lib/brainzlab/rails/railtie.rb +606 -0
- data/lib/brainzlab/recall/buffer.rb +66 -0
- data/lib/brainzlab/recall/client.rb +158 -0
- data/lib/brainzlab/recall/logger.rb +116 -0
- data/lib/brainzlab/recall/provisioner.rb +130 -0
- data/lib/brainzlab/recall.rb +175 -0
- data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
- data/lib/brainzlab/reflex/client.rb +150 -0
- data/lib/brainzlab/reflex/provisioner.rb +116 -0
- data/lib/brainzlab/reflex.rb +421 -0
- data/lib/brainzlab/sentinel/client.rb +236 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +60 -0
- data/lib/brainzlab/signal/provisioner.rb +115 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +308 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/testing/event_store.rb +377 -0
- data/lib/brainzlab/testing/helpers.rb +650 -0
- data/lib/brainzlab/testing/matchers.rb +391 -0
- data/lib/brainzlab/testing.rb +327 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
- data/lib/brainzlab/utilities/health_check.rb +294 -0
- data/lib/brainzlab/utilities/log_formatter.rb +254 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +216 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -0
- data/lib/brainzlab/version.rb +5 -0
- data/lib/brainzlab/vision/client.rb +175 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +155 -0
- data/lib/brainzlab-sdk.rb +3 -0
- data/lib/brainzlab.rb +306 -0
- data/lib/generators/brainzlab/install/install_generator.rb +63 -0
- data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
- metadata +251 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Sentinel
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
@base_url = config.sentinel_url || 'https://sentinel.brainzlab.ai'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List all registered hosts
|
|
16
|
+
def list_hosts(status: nil, page: 1, per_page: 50)
|
|
17
|
+
params = { page: page, per_page: per_page }
|
|
18
|
+
params[:status] = status if status
|
|
19
|
+
|
|
20
|
+
response = request(:get, '/api/v1/hosts', params: params)
|
|
21
|
+
|
|
22
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
23
|
+
|
|
24
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
25
|
+
data[:hosts] || []
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
log_error('list_hosts', e)
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get host details
|
|
32
|
+
def get_host(host_id)
|
|
33
|
+
response = request(:get, "/api/v1/hosts/#{host_id}")
|
|
34
|
+
|
|
35
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
36
|
+
|
|
37
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
log_error('get_host', e)
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get host metrics
|
|
44
|
+
# @param host_id [String] Host ID
|
|
45
|
+
# @param period [String] Time period (1h, 6h, 24h, 7d, 30d)
|
|
46
|
+
# @param metrics [Array<String>] Specific metrics to fetch (cpu, memory, disk, network)
|
|
47
|
+
def get_metrics(host_id, period: '1h', metrics: nil)
|
|
48
|
+
params = { period: period }
|
|
49
|
+
params[:metrics] = metrics.join(',') if metrics
|
|
50
|
+
|
|
51
|
+
response = request(:get, "/api/v1/hosts/#{host_id}/metrics", params: params)
|
|
52
|
+
|
|
53
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
54
|
+
|
|
55
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
log_error('get_metrics', e)
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get top processes for a host
|
|
62
|
+
def get_processes(host_id, sort_by: 'cpu', limit: 20)
|
|
63
|
+
params = { sort_by: sort_by, limit: limit }
|
|
64
|
+
|
|
65
|
+
response = request(:get, "/api/v1/hosts/#{host_id}/processes", params: params)
|
|
66
|
+
|
|
67
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
68
|
+
|
|
69
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
70
|
+
data[:processes] || []
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
log_error('get_processes', e)
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# List all containers
|
|
77
|
+
def list_containers(host_id: nil, status: nil)
|
|
78
|
+
params = {}
|
|
79
|
+
params[:host_id] = host_id if host_id
|
|
80
|
+
params[:status] = status if status
|
|
81
|
+
|
|
82
|
+
response = request(:get, '/api/v1/containers', params: params)
|
|
83
|
+
|
|
84
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
85
|
+
|
|
86
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
87
|
+
data[:containers] || []
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
log_error('list_containers', e)
|
|
90
|
+
[]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get container details
|
|
94
|
+
def get_container(container_id)
|
|
95
|
+
response = request(:get, "/api/v1/containers/#{container_id}")
|
|
96
|
+
|
|
97
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
98
|
+
|
|
99
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
log_error('get_container', e)
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Get container metrics
|
|
106
|
+
def get_container_metrics(container_id, period: '1h')
|
|
107
|
+
params = { period: period }
|
|
108
|
+
|
|
109
|
+
response = request(:get, "/api/v1/containers/#{container_id}/metrics", params: params)
|
|
110
|
+
|
|
111
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
112
|
+
|
|
113
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
log_error('get_container_metrics', e)
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Get alerts for a host
|
|
120
|
+
def get_alerts(host_id: nil, status: nil, severity: nil)
|
|
121
|
+
params = {}
|
|
122
|
+
params[:host_id] = host_id if host_id
|
|
123
|
+
params[:status] = status if status
|
|
124
|
+
params[:severity] = severity if severity
|
|
125
|
+
|
|
126
|
+
response = request(:get, '/api/v1/alerts', params: params)
|
|
127
|
+
|
|
128
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
129
|
+
|
|
130
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
131
|
+
data[:alerts] || []
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
log_error('get_alerts', e)
|
|
134
|
+
[]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Report metrics from agent (internal use)
|
|
138
|
+
def report_metrics(host_id:, metrics:, timestamp: nil)
|
|
139
|
+
response = request(
|
|
140
|
+
:post,
|
|
141
|
+
'/internal/agent/report',
|
|
142
|
+
body: {
|
|
143
|
+
host_id: host_id,
|
|
144
|
+
metrics: metrics,
|
|
145
|
+
timestamp: timestamp || Time.now.utc.iso8601
|
|
146
|
+
},
|
|
147
|
+
use_agent_key: true
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
151
|
+
rescue StandardError => e
|
|
152
|
+
log_error('report_metrics', e)
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def provision(project_id:, app_name:)
|
|
157
|
+
response = request(
|
|
158
|
+
:post,
|
|
159
|
+
'/api/v1/projects/provision',
|
|
160
|
+
body: { project_id: project_id, app_name: app_name },
|
|
161
|
+
use_service_key: true
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
165
|
+
rescue StandardError => e
|
|
166
|
+
log_error('provision', e)
|
|
167
|
+
false
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false, use_agent_key: false)
|
|
173
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
174
|
+
|
|
175
|
+
uri.query = URI.encode_www_form(params) if params
|
|
176
|
+
|
|
177
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
178
|
+
http.use_ssl = uri.scheme == 'https'
|
|
179
|
+
http.open_timeout = 10
|
|
180
|
+
http.read_timeout = 30
|
|
181
|
+
|
|
182
|
+
request = case method
|
|
183
|
+
when :get
|
|
184
|
+
Net::HTTP::Get.new(uri)
|
|
185
|
+
when :post
|
|
186
|
+
Net::HTTP::Post.new(uri)
|
|
187
|
+
when :put
|
|
188
|
+
Net::HTTP::Put.new(uri)
|
|
189
|
+
when :delete
|
|
190
|
+
Net::HTTP::Delete.new(uri)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
request['Content-Type'] = 'application/json'
|
|
194
|
+
request['Accept'] = 'application/json'
|
|
195
|
+
|
|
196
|
+
if use_service_key
|
|
197
|
+
request['X-Service-Key'] = @config.sentinel_master_key || @config.secret_key
|
|
198
|
+
elsif use_agent_key
|
|
199
|
+
request['X-Agent-Key'] = @config.sentinel_agent_key || @config.sentinel_api_key || @config.secret_key
|
|
200
|
+
else
|
|
201
|
+
auth_key = @config.sentinel_api_key || @config.secret_key
|
|
202
|
+
request['Authorization'] = "Bearer #{auth_key}" if auth_key
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
headers.each { |k, v| request[k] = v }
|
|
206
|
+
request.body = body.to_json if body
|
|
207
|
+
|
|
208
|
+
http.request(request)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def log_error(operation, error)
|
|
212
|
+
structured_error = ErrorHandler.wrap(error, service: 'Sentinel', operation: operation)
|
|
213
|
+
BrainzLab.debug_log("[Sentinel::Client] #{operation} failed: #{structured_error.message}")
|
|
214
|
+
|
|
215
|
+
# Call on_error callback if configured
|
|
216
|
+
if @config.on_error
|
|
217
|
+
@config.on_error.call(structured_error, { service: 'Sentinel', operation: operation })
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def handle_response_error(response, operation)
|
|
222
|
+
return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent) || response.is_a?(Net::HTTPAccepted)
|
|
223
|
+
|
|
224
|
+
structured_error = ErrorHandler.from_response(response, service: 'Sentinel', operation: operation)
|
|
225
|
+
BrainzLab.debug_log("[Sentinel::Client] #{operation} failed: #{structured_error.message}")
|
|
226
|
+
|
|
227
|
+
# Call on_error callback if configured
|
|
228
|
+
if @config.on_error
|
|
229
|
+
@config.on_error.call(structured_error, { service: 'Sentinel', operation: operation })
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
structured_error
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Sentinel
|
|
5
|
+
class Provisioner
|
|
6
|
+
def initialize(config)
|
|
7
|
+
@config = config
|
|
8
|
+
@provisioned = false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ensure_project!
|
|
12
|
+
return if @provisioned
|
|
13
|
+
return unless @config.sentinel_auto_provision
|
|
14
|
+
return unless valid_auth?
|
|
15
|
+
|
|
16
|
+
@provisioned = true
|
|
17
|
+
|
|
18
|
+
project_id = detect_project_id
|
|
19
|
+
return unless project_id
|
|
20
|
+
|
|
21
|
+
client = Client.new(@config)
|
|
22
|
+
client.provision(
|
|
23
|
+
project_id: project_id,
|
|
24
|
+
app_name: @config.app_name || @config.service
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
BrainzLab.debug_log("[Sentinel::Provisioner] Project provisioned: #{project_id}")
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
BrainzLab.debug_log("[Sentinel::Provisioner] Provisioning failed: #{e.message}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def valid_auth?
|
|
35
|
+
key = @config.sentinel_api_key || @config.sentinel_master_key || @config.secret_key
|
|
36
|
+
!key.nil? && !key.empty?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def detect_project_id
|
|
40
|
+
ENV.fetch('BRAINZLAB_PROJECT_ID', nil)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'sentinel/client'
|
|
4
|
+
require_relative 'sentinel/provisioner'
|
|
5
|
+
|
|
6
|
+
module BrainzLab
|
|
7
|
+
module Sentinel
|
|
8
|
+
class << self
|
|
9
|
+
# List all registered hosts
|
|
10
|
+
# @param status [String] Filter by status (online, offline, warning, critical)
|
|
11
|
+
# @param page [Integer] Page number
|
|
12
|
+
# @param per_page [Integer] Results per page
|
|
13
|
+
# @return [Array<Hash>] List of hosts
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# hosts = BrainzLab::Sentinel.hosts(status: "online")
|
|
17
|
+
#
|
|
18
|
+
def hosts(status: nil, page: 1, per_page: 50)
|
|
19
|
+
return [] unless enabled?
|
|
20
|
+
|
|
21
|
+
ensure_provisioned!
|
|
22
|
+
return [] unless BrainzLab.configuration.sentinel_valid?
|
|
23
|
+
|
|
24
|
+
client.list_hosts(status: status, page: page, per_page: per_page)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Get host details
|
|
28
|
+
# @param host_id [String] Host ID
|
|
29
|
+
# @return [Hash, nil] Host details
|
|
30
|
+
def host(host_id)
|
|
31
|
+
return nil unless enabled?
|
|
32
|
+
|
|
33
|
+
ensure_provisioned!
|
|
34
|
+
return nil unless BrainzLab.configuration.sentinel_valid?
|
|
35
|
+
|
|
36
|
+
client.get_host(host_id)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get metrics for a host
|
|
40
|
+
# @param host_id [String] Host ID
|
|
41
|
+
# @param period [String] Time period (1h, 6h, 24h, 7d, 30d)
|
|
42
|
+
# @param metrics [Array<String>] Specific metrics (cpu, memory, disk, network)
|
|
43
|
+
# @return [Hash, nil] Metrics data
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# metrics = BrainzLab::Sentinel.metrics(host_id, period: "24h", metrics: ["cpu", "memory"])
|
|
47
|
+
#
|
|
48
|
+
def metrics(host_id, period: '1h', metrics: nil)
|
|
49
|
+
return nil unless enabled?
|
|
50
|
+
|
|
51
|
+
ensure_provisioned!
|
|
52
|
+
return nil unless BrainzLab.configuration.sentinel_valid?
|
|
53
|
+
|
|
54
|
+
client.get_metrics(host_id, period: period, metrics: metrics)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get top processes for a host
|
|
58
|
+
# @param host_id [String] Host ID
|
|
59
|
+
# @param sort_by [String] Sort by (cpu, memory, time)
|
|
60
|
+
# @param limit [Integer] Max results
|
|
61
|
+
# @return [Array<Hash>] Process list
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# procs = BrainzLab::Sentinel.processes(host_id, sort_by: "memory", limit: 10)
|
|
65
|
+
#
|
|
66
|
+
def processes(host_id, sort_by: 'cpu', limit: 20)
|
|
67
|
+
return [] unless enabled?
|
|
68
|
+
|
|
69
|
+
ensure_provisioned!
|
|
70
|
+
return [] unless BrainzLab.configuration.sentinel_valid?
|
|
71
|
+
|
|
72
|
+
client.get_processes(host_id, sort_by: sort_by, limit: limit)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# List all containers
|
|
76
|
+
# @param host_id [String] Optional filter by host
|
|
77
|
+
# @param status [String] Filter by status (running, stopped, paused)
|
|
78
|
+
# @return [Array<Hash>] Container list
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# containers = BrainzLab::Sentinel.containers(host_id: "host_123", status: "running")
|
|
82
|
+
#
|
|
83
|
+
def containers(host_id: nil, status: nil)
|
|
84
|
+
return [] unless enabled?
|
|
85
|
+
|
|
86
|
+
ensure_provisioned!
|
|
87
|
+
return [] unless BrainzLab.configuration.sentinel_valid?
|
|
88
|
+
|
|
89
|
+
client.list_containers(host_id: host_id, status: status)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get container details
|
|
93
|
+
# @param container_id [String] Container ID
|
|
94
|
+
# @return [Hash, nil] Container details
|
|
95
|
+
def container(container_id)
|
|
96
|
+
return nil unless enabled?
|
|
97
|
+
|
|
98
|
+
ensure_provisioned!
|
|
99
|
+
return nil unless BrainzLab.configuration.sentinel_valid?
|
|
100
|
+
|
|
101
|
+
client.get_container(container_id)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Get container metrics
|
|
105
|
+
# @param container_id [String] Container ID
|
|
106
|
+
# @param period [String] Time period
|
|
107
|
+
# @return [Hash, nil] Container metrics
|
|
108
|
+
def container_metrics(container_id, period: '1h')
|
|
109
|
+
return nil unless enabled?
|
|
110
|
+
|
|
111
|
+
ensure_provisioned!
|
|
112
|
+
return nil unless BrainzLab.configuration.sentinel_valid?
|
|
113
|
+
|
|
114
|
+
client.get_container_metrics(container_id, period: period)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get alerts
|
|
118
|
+
# @param host_id [String] Optional filter by host
|
|
119
|
+
# @param status [String] Filter by status (active, acknowledged, resolved)
|
|
120
|
+
# @param severity [String] Filter by severity (info, warning, critical)
|
|
121
|
+
# @return [Array<Hash>] Alert list
|
|
122
|
+
#
|
|
123
|
+
# @example
|
|
124
|
+
# alerts = BrainzLab::Sentinel.alerts(severity: "critical", status: "active")
|
|
125
|
+
#
|
|
126
|
+
def alerts(host_id: nil, status: nil, severity: nil)
|
|
127
|
+
return [] unless enabled?
|
|
128
|
+
|
|
129
|
+
ensure_provisioned!
|
|
130
|
+
return [] unless BrainzLab.configuration.sentinel_valid?
|
|
131
|
+
|
|
132
|
+
client.get_alerts(host_id: host_id, status: status, severity: severity)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# === INTERNAL ===
|
|
136
|
+
|
|
137
|
+
def ensure_provisioned!
|
|
138
|
+
return if @provisioned
|
|
139
|
+
|
|
140
|
+
@provisioned = true
|
|
141
|
+
provisioner.ensure_project!
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def provisioner
|
|
145
|
+
@provisioner ||= Provisioner.new(BrainzLab.configuration)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def client
|
|
149
|
+
@client ||= Client.new(BrainzLab.configuration)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def reset!
|
|
153
|
+
@client = nil
|
|
154
|
+
@provisioner = nil
|
|
155
|
+
@provisioned = false
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
def enabled?
|
|
161
|
+
BrainzLab.configuration.sentinel_enabled
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Signal
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def send_alert(alert)
|
|
15
|
+
post('/api/v1/alerts', alert)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def send_notification(notification)
|
|
19
|
+
post('/api/v1/notifications', notification)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def trigger_rule(payload)
|
|
23
|
+
post('/api/v1/rules/trigger', payload)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def post(path, body)
|
|
29
|
+
uri = URI.parse("#{base_url}#{path}")
|
|
30
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
31
|
+
http.use_ssl = uri.scheme == 'https'
|
|
32
|
+
http.open_timeout = 5
|
|
33
|
+
http.read_timeout = 10
|
|
34
|
+
|
|
35
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
36
|
+
request['Content-Type'] = 'application/json'
|
|
37
|
+
request['Authorization'] = "Bearer #{api_key}"
|
|
38
|
+
request['User-Agent'] = "brainzlab-sdk/#{BrainzLab::VERSION}"
|
|
39
|
+
request.body = body.to_json
|
|
40
|
+
|
|
41
|
+
response = http.request(request)
|
|
42
|
+
|
|
43
|
+
BrainzLab.debug_log("[Signal] Request failed: #{response.code} - #{response.body}") unless response.is_a?(Net::HTTPSuccess)
|
|
44
|
+
|
|
45
|
+
response
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
BrainzLab.debug_log("[Signal] Request error: #{e.message}")
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def base_url
|
|
52
|
+
@config.signal_url
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def api_key
|
|
56
|
+
@config.signal_auth_key
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
|
|
8
|
+
module BrainzLab
|
|
9
|
+
module Signal
|
|
10
|
+
class Provisioner
|
|
11
|
+
CACHE_DIR = ENV.fetch('BRAINZLAB_CACHE_DIR') { File.join(Dir.home, '.brainzlab') }
|
|
12
|
+
|
|
13
|
+
def initialize(config)
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ensure_project!
|
|
18
|
+
return unless should_provision?
|
|
19
|
+
|
|
20
|
+
# Try cached credentials first
|
|
21
|
+
if (cached = load_cached_credentials)
|
|
22
|
+
apply_credentials(cached)
|
|
23
|
+
return cached
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Provision new project
|
|
27
|
+
project = provision_project
|
|
28
|
+
return unless project
|
|
29
|
+
|
|
30
|
+
# Cache and apply credentials
|
|
31
|
+
cache_credentials(project)
|
|
32
|
+
apply_credentials(project)
|
|
33
|
+
|
|
34
|
+
project
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def should_provision?
|
|
40
|
+
return false unless @config.signal_auto_provision
|
|
41
|
+
return false unless @config.app_name.to_s.strip.length.positive?
|
|
42
|
+
# Only skip if signal_api_key is already set
|
|
43
|
+
return false if @config.signal_api_key.to_s.strip.length.positive?
|
|
44
|
+
return false unless @config.signal_master_key.to_s.strip.length.positive?
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def provision_project
|
|
50
|
+
uri = URI.parse("#{@config.signal_url}/api/v1/projects/provision")
|
|
51
|
+
request = Net::HTTP::Post.new(uri)
|
|
52
|
+
request['Content-Type'] = 'application/json'
|
|
53
|
+
request['X-Master-Key'] = @config.signal_master_key
|
|
54
|
+
request['User-Agent'] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
|
|
55
|
+
request.body = JSON.generate({ name: @config.app_name })
|
|
56
|
+
|
|
57
|
+
response = execute(uri, request)
|
|
58
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
59
|
+
|
|
60
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
log_error("Failed to provision Signal project: #{e.message}")
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def load_cached_credentials
|
|
67
|
+
path = cache_file_path
|
|
68
|
+
return nil unless File.exist?(path)
|
|
69
|
+
|
|
70
|
+
data = JSON.parse(File.read(path), symbolize_names: true)
|
|
71
|
+
|
|
72
|
+
# Validate cached data has required keys
|
|
73
|
+
return nil unless data[:api_key] || data[:ingest_key]
|
|
74
|
+
|
|
75
|
+
data
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
log_error("Failed to load cached Signal credentials: #{e.message}")
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def cache_credentials(project)
|
|
82
|
+
FileUtils.mkdir_p(CACHE_DIR)
|
|
83
|
+
File.write(cache_file_path, JSON.generate(project))
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
log_error("Failed to cache Signal credentials: #{e.message}")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cache_file_path
|
|
89
|
+
File.join(CACHE_DIR, "#{@config.app_name}.signal.json")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def apply_credentials(project)
|
|
93
|
+
@config.signal_api_key = project[:api_key]
|
|
94
|
+
@config.signal_ingest_key = project[:ingest_key]
|
|
95
|
+
|
|
96
|
+
# Also set service name from app_name if not already set
|
|
97
|
+
@config.service ||= @config.app_name
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def execute(uri, request)
|
|
101
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
102
|
+
http.use_ssl = uri.scheme == 'https'
|
|
103
|
+
http.open_timeout = 5
|
|
104
|
+
http.read_timeout = 10
|
|
105
|
+
http.request(request)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def log_error(message)
|
|
109
|
+
return unless @config.logger
|
|
110
|
+
|
|
111
|
+
@config.logger.error("[BrainzLab::Signal] #{message}")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|