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,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'signal/client'
|
|
4
|
+
require_relative 'signal/provisioner'
|
|
5
|
+
|
|
6
|
+
module BrainzLab
|
|
7
|
+
module Signal
|
|
8
|
+
SEVERITIES = %i[info warning error critical].freeze
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Send an alert with a message
|
|
12
|
+
# @param name [String] Alert name (e.g., 'high_error_rate', 'low_disk_space')
|
|
13
|
+
# @param message [String] Alert message
|
|
14
|
+
# @param severity [Symbol] Alert severity (:info, :warning, :error, :critical)
|
|
15
|
+
# @param channels [Array<String>] Channels to send alert to (e.g., ['slack', 'email'])
|
|
16
|
+
# @param data [Hash] Additional data to include with the alert
|
|
17
|
+
def alert(name, message, severity: :warning, channels: nil, data: {})
|
|
18
|
+
return unless enabled?
|
|
19
|
+
|
|
20
|
+
ensure_provisioned!
|
|
21
|
+
return unless BrainzLab.configuration.signal_valid?
|
|
22
|
+
|
|
23
|
+
payload = {
|
|
24
|
+
type: 'alert',
|
|
25
|
+
name: name,
|
|
26
|
+
message: message,
|
|
27
|
+
severity: severity.to_s,
|
|
28
|
+
channels: channels,
|
|
29
|
+
data: data,
|
|
30
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
31
|
+
environment: BrainzLab.configuration.environment,
|
|
32
|
+
service: BrainzLab.configuration.service,
|
|
33
|
+
host: BrainzLab.configuration.host,
|
|
34
|
+
context: context_data
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
client.send_alert(payload)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Send a notification to specific channels
|
|
41
|
+
# @param channel [String, Array<String>] Channel(s) to send to ('slack', 'email', 'webhook')
|
|
42
|
+
# @param message [String] Notification message
|
|
43
|
+
# @param title [String] Optional notification title
|
|
44
|
+
# @param data [Hash] Additional data
|
|
45
|
+
def notify(channel, message, title: nil, data: {})
|
|
46
|
+
return unless enabled?
|
|
47
|
+
|
|
48
|
+
ensure_provisioned!
|
|
49
|
+
return unless BrainzLab.configuration.signal_valid?
|
|
50
|
+
|
|
51
|
+
channels = Array(channel)
|
|
52
|
+
payload = {
|
|
53
|
+
type: 'notification',
|
|
54
|
+
channels: channels,
|
|
55
|
+
message: message,
|
|
56
|
+
title: title,
|
|
57
|
+
data: data,
|
|
58
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
59
|
+
environment: BrainzLab.configuration.environment,
|
|
60
|
+
service: BrainzLab.configuration.service
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
client.send_notification(payload)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Trigger a predefined alert rule
|
|
67
|
+
# @param rule_name [String] Name of the alert rule to trigger
|
|
68
|
+
# @param context [Hash] Context data to pass to the rule
|
|
69
|
+
def trigger(rule_name, context = {})
|
|
70
|
+
return unless enabled?
|
|
71
|
+
|
|
72
|
+
ensure_provisioned!
|
|
73
|
+
return unless BrainzLab.configuration.signal_valid?
|
|
74
|
+
|
|
75
|
+
payload = {
|
|
76
|
+
type: 'trigger',
|
|
77
|
+
rule: rule_name,
|
|
78
|
+
context: context,
|
|
79
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
80
|
+
environment: BrainzLab.configuration.environment,
|
|
81
|
+
service: BrainzLab.configuration.service
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
client.trigger_rule(payload)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Send a test alert to verify configuration
|
|
88
|
+
def test!
|
|
89
|
+
alert(
|
|
90
|
+
'test_alert',
|
|
91
|
+
'This is a test alert from BrainzLab Signal SDK',
|
|
92
|
+
severity: :info,
|
|
93
|
+
data: { test: true, sdk_version: BrainzLab::VERSION }
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# === INTERNAL ===
|
|
98
|
+
|
|
99
|
+
def ensure_provisioned!
|
|
100
|
+
return if @provisioned
|
|
101
|
+
|
|
102
|
+
@provisioned = true
|
|
103
|
+
provisioner.ensure_project!
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def provisioner
|
|
107
|
+
@provisioner ||= Provisioner.new(BrainzLab.configuration)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def client
|
|
111
|
+
@client ||= Client.new(BrainzLab.configuration)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def reset!
|
|
115
|
+
@client = nil
|
|
116
|
+
@provisioner = nil
|
|
117
|
+
@provisioned = false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def enabled?
|
|
123
|
+
BrainzLab.configuration.signal_effectively_enabled?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def context_data
|
|
127
|
+
ctx = BrainzLab::Context.current
|
|
128
|
+
{
|
|
129
|
+
user: ctx.user,
|
|
130
|
+
tags: ctx.tags,
|
|
131
|
+
extra: ctx.extra
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Synapse
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
@base_url = config.synapse_url || 'https://synapse.brainzlab.ai'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List all projects
|
|
16
|
+
def list_projects(status: nil, page: 1, per_page: 20)
|
|
17
|
+
params = { page: page, per_page: per_page }
|
|
18
|
+
params[:status] = status if status
|
|
19
|
+
|
|
20
|
+
response = request(:get, '/api/v1/projects', params: params)
|
|
21
|
+
|
|
22
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
23
|
+
|
|
24
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
25
|
+
data[:projects] || []
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
log_error('list_projects', e)
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get project details
|
|
32
|
+
def get_project(project_id)
|
|
33
|
+
response = request(:get, "/api/v1/projects/#{project_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_project', e)
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Create a new project
|
|
44
|
+
def create_project(name:, repos: [], description: nil, **options)
|
|
45
|
+
response = request(
|
|
46
|
+
:post,
|
|
47
|
+
'/api/v1/projects',
|
|
48
|
+
body: {
|
|
49
|
+
name: name,
|
|
50
|
+
description: description,
|
|
51
|
+
repos: repos,
|
|
52
|
+
**options
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
57
|
+
|
|
58
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
log_error('create_project', e)
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Start project containers
|
|
65
|
+
def start_project(project_id)
|
|
66
|
+
response = request(:post, "/api/v1/projects/#{project_id}/up")
|
|
67
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
log_error('start_project', e)
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Stop project containers
|
|
74
|
+
def stop_project(project_id)
|
|
75
|
+
response = request(:post, "/api/v1/projects/#{project_id}/down")
|
|
76
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
log_error('stop_project', e)
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Restart project containers
|
|
83
|
+
def restart_project(project_id)
|
|
84
|
+
response = request(:post, "/api/v1/projects/#{project_id}/restart")
|
|
85
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
log_error('restart_project', e)
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Deploy project to environment
|
|
92
|
+
def deploy(project_id, environment:, options: {})
|
|
93
|
+
response = request(
|
|
94
|
+
:post,
|
|
95
|
+
"/api/v1/projects/#{project_id}/deploy",
|
|
96
|
+
body: {
|
|
97
|
+
environment: environment,
|
|
98
|
+
**options
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
103
|
+
|
|
104
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
log_error('deploy', e)
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get deployment status
|
|
111
|
+
def get_deployment(deployment_id)
|
|
112
|
+
response = request(:get, "/api/v1/deployments/#{deployment_id}")
|
|
113
|
+
|
|
114
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
115
|
+
|
|
116
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
log_error('get_deployment', e)
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Create an AI task
|
|
123
|
+
def create_task(project_id:, description:, type: nil, priority: nil, **options)
|
|
124
|
+
response = request(
|
|
125
|
+
:post,
|
|
126
|
+
'/api/v1/tasks',
|
|
127
|
+
body: {
|
|
128
|
+
project_id: project_id,
|
|
129
|
+
description: description,
|
|
130
|
+
type: type,
|
|
131
|
+
priority: priority,
|
|
132
|
+
**options
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
137
|
+
|
|
138
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
139
|
+
rescue StandardError => e
|
|
140
|
+
log_error('create_task', e)
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Get task status
|
|
145
|
+
def get_task(task_id)
|
|
146
|
+
response = request(:get, "/api/v1/tasks/#{task_id}")
|
|
147
|
+
|
|
148
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
149
|
+
|
|
150
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
151
|
+
rescue StandardError => e
|
|
152
|
+
log_error('get_task', e)
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get task status and progress
|
|
157
|
+
def get_task_status(task_id)
|
|
158
|
+
response = request(:get, "/api/v1/tasks/#{task_id}/status")
|
|
159
|
+
|
|
160
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
161
|
+
|
|
162
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
log_error('get_task_status', e)
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# List tasks for a project
|
|
169
|
+
def list_tasks(project_id: nil, status: nil, page: 1, per_page: 20)
|
|
170
|
+
params = { page: page, per_page: per_page }
|
|
171
|
+
params[:project_id] = project_id if project_id
|
|
172
|
+
params[:status] = status if status
|
|
173
|
+
|
|
174
|
+
response = request(:get, '/api/v1/tasks', params: params)
|
|
175
|
+
|
|
176
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
177
|
+
|
|
178
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
179
|
+
data[:tasks] || []
|
|
180
|
+
rescue StandardError => e
|
|
181
|
+
log_error('list_tasks', e)
|
|
182
|
+
[]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Cancel a running task
|
|
186
|
+
def cancel_task(task_id)
|
|
187
|
+
response = request(:post, "/api/v1/tasks/#{task_id}/cancel")
|
|
188
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
log_error('cancel_task', e)
|
|
191
|
+
false
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Get container logs
|
|
195
|
+
def get_logs(project_id, container: nil, lines: 100, since: nil)
|
|
196
|
+
params = { lines: lines }
|
|
197
|
+
params[:container] = container if container
|
|
198
|
+
params[:since] = since if since
|
|
199
|
+
|
|
200
|
+
response = request(:get, "/api/v1/projects/#{project_id}/logs", params: params)
|
|
201
|
+
|
|
202
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
203
|
+
|
|
204
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
log_error('get_logs', e)
|
|
207
|
+
nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Execute command in container
|
|
211
|
+
def exec(project_id, command:, container: nil, timeout: 30)
|
|
212
|
+
response = request(
|
|
213
|
+
:post,
|
|
214
|
+
"/api/v1/projects/#{project_id}/exec",
|
|
215
|
+
body: {
|
|
216
|
+
command: command,
|
|
217
|
+
container: container,
|
|
218
|
+
timeout: timeout
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
223
|
+
|
|
224
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
log_error('exec', e)
|
|
227
|
+
nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def provision(project_id:, app_name:)
|
|
231
|
+
response = request(
|
|
232
|
+
:post,
|
|
233
|
+
'/api/v1/projects/provision',
|
|
234
|
+
body: { project_id: project_id, app_name: app_name },
|
|
235
|
+
use_service_key: true
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
239
|
+
rescue StandardError => e
|
|
240
|
+
log_error('provision', e)
|
|
241
|
+
false
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
private
|
|
245
|
+
|
|
246
|
+
def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false)
|
|
247
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
248
|
+
|
|
249
|
+
uri.query = URI.encode_www_form(params) if params
|
|
250
|
+
|
|
251
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
252
|
+
http.use_ssl = uri.scheme == 'https'
|
|
253
|
+
http.open_timeout = 10
|
|
254
|
+
http.read_timeout = 120 # Longer timeout for AI tasks
|
|
255
|
+
|
|
256
|
+
request = case method
|
|
257
|
+
when :get
|
|
258
|
+
Net::HTTP::Get.new(uri)
|
|
259
|
+
when :post
|
|
260
|
+
Net::HTTP::Post.new(uri)
|
|
261
|
+
when :put
|
|
262
|
+
Net::HTTP::Put.new(uri)
|
|
263
|
+
when :delete
|
|
264
|
+
Net::HTTP::Delete.new(uri)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
request['Content-Type'] = 'application/json'
|
|
268
|
+
request['Accept'] = 'application/json'
|
|
269
|
+
|
|
270
|
+
if use_service_key
|
|
271
|
+
request['X-Service-Key'] = @config.synapse_master_key || @config.secret_key
|
|
272
|
+
else
|
|
273
|
+
auth_key = @config.synapse_api_key || @config.secret_key
|
|
274
|
+
request['Authorization'] = "Bearer #{auth_key}" if auth_key
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
headers.each { |k, v| request[k] = v }
|
|
278
|
+
request.body = body.to_json if body
|
|
279
|
+
|
|
280
|
+
http.request(request)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def log_error(operation, error)
|
|
284
|
+
structured_error = ErrorHandler.wrap(error, service: 'Synapse', operation: operation)
|
|
285
|
+
BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{structured_error.message}")
|
|
286
|
+
|
|
287
|
+
# Call on_error callback if configured
|
|
288
|
+
if @config.on_error
|
|
289
|
+
@config.on_error.call(structured_error, { service: 'Synapse', operation: operation })
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def handle_response_error(response, operation)
|
|
294
|
+
return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent) || response.is_a?(Net::HTTPAccepted)
|
|
295
|
+
|
|
296
|
+
structured_error = ErrorHandler.from_response(response, service: 'Synapse', operation: operation)
|
|
297
|
+
BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{structured_error.message}")
|
|
298
|
+
|
|
299
|
+
# Call on_error callback if configured
|
|
300
|
+
if @config.on_error
|
|
301
|
+
@config.on_error.call(structured_error, { service: 'Synapse', operation: operation })
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
structured_error
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Synapse
|
|
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.synapse_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("[Synapse::Provisioner] Project provisioned: #{project_id}")
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
BrainzLab.debug_log("[Synapse::Provisioner] Provisioning failed: #{e.message}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def valid_auth?
|
|
35
|
+
key = @config.synapse_api_key || @config.synapse_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
|