brainzlab 0.1.1 → 0.1.2

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/lib/brainzlab/beacon/client.rb +209 -0
  4. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  5. data/lib/brainzlab/beacon.rb +215 -0
  6. data/lib/brainzlab/configuration.rb +341 -3
  7. data/lib/brainzlab/cortex/cache.rb +59 -0
  8. data/lib/brainzlab/cortex/client.rb +141 -0
  9. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  10. data/lib/brainzlab/cortex.rb +227 -0
  11. data/lib/brainzlab/dendrite/client.rb +232 -0
  12. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  13. data/lib/brainzlab/dendrite.rb +195 -0
  14. data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
  15. data/lib/brainzlab/devtools/assets/devtools.js +322 -0
  16. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  17. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
  18. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  19. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  20. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  21. data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
  22. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  23. data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
  24. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
  25. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
  26. data/lib/brainzlab/devtools.rb +75 -0
  27. data/lib/brainzlab/flux/buffer.rb +96 -0
  28. data/lib/brainzlab/flux/client.rb +70 -0
  29. data/lib/brainzlab/flux/provisioner.rb +57 -0
  30. data/lib/brainzlab/flux.rb +174 -0
  31. data/lib/brainzlab/instrumentation/active_record.rb +18 -1
  32. data/lib/brainzlab/instrumentation/aws.rb +179 -0
  33. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  34. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  35. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  36. data/lib/brainzlab/instrumentation/resque.rb +115 -0
  37. data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
  38. data/lib/brainzlab/instrumentation/stripe.rb +164 -0
  39. data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
  40. data/lib/brainzlab/instrumentation.rb +72 -0
  41. data/lib/brainzlab/nerve/client.rb +217 -0
  42. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  43. data/lib/brainzlab/nerve.rb +219 -0
  44. data/lib/brainzlab/pulse/instrumentation.rb +35 -2
  45. data/lib/brainzlab/pulse/propagation.rb +1 -1
  46. data/lib/brainzlab/pulse/tracer.rb +1 -1
  47. data/lib/brainzlab/pulse.rb +1 -1
  48. data/lib/brainzlab/rails/log_subscriber.rb +1 -2
  49. data/lib/brainzlab/rails/railtie.rb +36 -3
  50. data/lib/brainzlab/recall/provisioner.rb +17 -0
  51. data/lib/brainzlab/recall.rb +6 -1
  52. data/lib/brainzlab/reflex.rb +2 -2
  53. data/lib/brainzlab/sentinel/client.rb +218 -0
  54. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  55. data/lib/brainzlab/sentinel.rb +165 -0
  56. data/lib/brainzlab/signal/client.rb +62 -0
  57. data/lib/brainzlab/signal/provisioner.rb +55 -0
  58. data/lib/brainzlab/signal.rb +136 -0
  59. data/lib/brainzlab/synapse/client.rb +290 -0
  60. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  61. data/lib/brainzlab/synapse.rb +270 -0
  62. data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
  63. data/lib/brainzlab/utilities/health_check.rb +296 -0
  64. data/lib/brainzlab/utilities/log_formatter.rb +256 -0
  65. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  66. data/lib/brainzlab/utilities.rb +17 -0
  67. data/lib/brainzlab/vault/cache.rb +80 -0
  68. data/lib/brainzlab/vault/client.rb +198 -0
  69. data/lib/brainzlab/vault/provisioner.rb +49 -0
  70. data/lib/brainzlab/vault.rb +268 -0
  71. data/lib/brainzlab/version.rb +1 -1
  72. data/lib/brainzlab/vision/client.rb +128 -0
  73. data/lib/brainzlab/vision/provisioner.rb +136 -0
  74. data/lib/brainzlab/vision.rb +157 -0
  75. data/lib/brainzlab.rb +101 -0
  76. metadata +60 -1
@@ -0,0 +1,290 @@
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
+ if params
250
+ uri.query = URI.encode_www_form(params)
251
+ end
252
+
253
+ http = Net::HTTP.new(uri.host, uri.port)
254
+ http.use_ssl = uri.scheme == "https"
255
+ http.open_timeout = 10
256
+ http.read_timeout = 120 # Longer timeout for AI tasks
257
+
258
+ request = case method
259
+ when :get
260
+ Net::HTTP::Get.new(uri)
261
+ when :post
262
+ Net::HTTP::Post.new(uri)
263
+ when :put
264
+ Net::HTTP::Put.new(uri)
265
+ when :delete
266
+ Net::HTTP::Delete.new(uri)
267
+ end
268
+
269
+ request["Content-Type"] = "application/json"
270
+ request["Accept"] = "application/json"
271
+
272
+ if use_service_key
273
+ request["X-Service-Key"] = @config.synapse_master_key || @config.secret_key
274
+ else
275
+ auth_key = @config.synapse_api_key || @config.secret_key
276
+ request["Authorization"] = "Bearer #{auth_key}" if auth_key
277
+ end
278
+
279
+ headers.each { |k, v| request[k] = v }
280
+ request.body = body.to_json if body
281
+
282
+ http.request(request)
283
+ end
284
+
285
+ def log_error(operation, error)
286
+ BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{error.message}")
287
+ end
288
+ end
289
+ end
290
+ 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["BRAINZLAB_PROJECT_ID"]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "synapse/client"
4
+ require_relative "synapse/provisioner"
5
+
6
+ module BrainzLab
7
+ module Synapse
8
+ class << self
9
+ # List all projects
10
+ # @param status [String] Filter by status (running, stopped, deploying)
11
+ # @return [Array<Hash>] List of projects
12
+ #
13
+ # @example
14
+ # projects = BrainzLab::Synapse.projects(status: "running")
15
+ #
16
+ def projects(status: nil, page: 1, per_page: 20)
17
+ return [] unless enabled?
18
+
19
+ ensure_provisioned!
20
+ return [] unless BrainzLab.configuration.synapse_valid?
21
+
22
+ client.list_projects(status: status, page: page, per_page: per_page)
23
+ end
24
+
25
+ # Get project details
26
+ # @param project_id [String] Project ID
27
+ # @return [Hash, nil] Project details
28
+ def project(project_id)
29
+ return nil unless enabled?
30
+
31
+ ensure_provisioned!
32
+ return nil unless BrainzLab.configuration.synapse_valid?
33
+
34
+ client.get_project(project_id)
35
+ end
36
+
37
+ # Create a new project
38
+ # @param name [String] Project name
39
+ # @param repos [Array<Hash>] Repository configurations
40
+ # @param description [String] Project description
41
+ # @return [Hash, nil] Created project
42
+ #
43
+ # @example
44
+ # project = BrainzLab::Synapse.create_project(
45
+ # name: "My App",
46
+ # repos: [
47
+ # { url: "https://github.com/org/api", type: "rails" },
48
+ # { url: "https://github.com/org/frontend", type: "react" }
49
+ # ]
50
+ # )
51
+ #
52
+ def create_project(name:, repos: [], description: nil, **options)
53
+ return nil unless enabled?
54
+
55
+ ensure_provisioned!
56
+ return nil unless BrainzLab.configuration.synapse_valid?
57
+
58
+ client.create_project(name: name, repos: repos, description: description, **options)
59
+ end
60
+
61
+ # Start project containers
62
+ # @param project_id [String] Project ID
63
+ # @return [Boolean] True if started
64
+ def up(project_id)
65
+ return false unless enabled?
66
+
67
+ ensure_provisioned!
68
+ return false unless BrainzLab.configuration.synapse_valid?
69
+
70
+ client.start_project(project_id)
71
+ end
72
+
73
+ # Stop project containers
74
+ # @param project_id [String] Project ID
75
+ # @return [Boolean] True if stopped
76
+ def down(project_id)
77
+ return false unless enabled?
78
+
79
+ ensure_provisioned!
80
+ return false unless BrainzLab.configuration.synapse_valid?
81
+
82
+ client.stop_project(project_id)
83
+ end
84
+
85
+ # Restart project containers
86
+ # @param project_id [String] Project ID
87
+ # @return [Boolean] True if restarted
88
+ def restart(project_id)
89
+ return false unless enabled?
90
+
91
+ ensure_provisioned!
92
+ return false unless BrainzLab.configuration.synapse_valid?
93
+
94
+ client.restart_project(project_id)
95
+ end
96
+
97
+ # Deploy project to environment
98
+ # @param project_id [String] Project ID
99
+ # @param environment [Symbol] Target environment (:staging, :production)
100
+ # @param options [Hash] Deployment options
101
+ # @return [Hash, nil] Deployment info
102
+ #
103
+ # @example
104
+ # deployment = BrainzLab::Synapse.deploy(project_id, environment: :staging)
105
+ #
106
+ def deploy(project_id, environment:, **options)
107
+ return nil unless enabled?
108
+
109
+ ensure_provisioned!
110
+ return nil unless BrainzLab.configuration.synapse_valid?
111
+
112
+ client.deploy(project_id, environment: environment, options: options)
113
+ end
114
+
115
+ # Get deployment status
116
+ # @param deployment_id [String] Deployment ID
117
+ # @return [Hash, nil] Deployment details
118
+ def deployment(deployment_id)
119
+ return nil unless enabled?
120
+
121
+ ensure_provisioned!
122
+ return nil unless BrainzLab.configuration.synapse_valid?
123
+
124
+ client.get_deployment(deployment_id)
125
+ end
126
+
127
+ # Create an AI development task
128
+ # @param project_id [String] Project ID
129
+ # @param description [String] Task description in natural language
130
+ # @param type [Symbol] Task type (:feature, :bugfix, :refactor, :test)
131
+ # @param priority [Symbol] Priority (:low, :medium, :high, :urgent)
132
+ # @return [Hash, nil] Created task
133
+ #
134
+ # @example
135
+ # task = BrainzLab::Synapse.task(
136
+ # project_id: project_id,
137
+ # description: "Add user authentication with OAuth",
138
+ # type: :feature,
139
+ # priority: :high
140
+ # )
141
+ #
142
+ def task(project_id:, description:, type: nil, priority: nil, **options)
143
+ return nil unless enabled?
144
+
145
+ ensure_provisioned!
146
+ return nil unless BrainzLab.configuration.synapse_valid?
147
+
148
+ client.create_task(
149
+ project_id: project_id,
150
+ description: description,
151
+ type: type,
152
+ priority: priority,
153
+ **options
154
+ )
155
+ end
156
+
157
+ # Get task details
158
+ # @param task_id [String] Task ID
159
+ # @return [Hash, nil] Task details
160
+ def get_task(task_id)
161
+ return nil unless enabled?
162
+
163
+ ensure_provisioned!
164
+ return nil unless BrainzLab.configuration.synapse_valid?
165
+
166
+ client.get_task(task_id)
167
+ end
168
+
169
+ # Get task status and progress
170
+ # @param task_id [String] Task ID
171
+ # @return [Hash, nil] Task status with progress
172
+ def task_status(task_id)
173
+ return nil unless enabled?
174
+
175
+ ensure_provisioned!
176
+ return nil unless BrainzLab.configuration.synapse_valid?
177
+
178
+ client.get_task_status(task_id)
179
+ end
180
+
181
+ # List tasks
182
+ # @param project_id [String] Optional filter by project
183
+ # @param status [String] Filter by status (pending, running, completed, failed)
184
+ # @return [Array<Hash>] List of tasks
185
+ def tasks(project_id: nil, status: nil, page: 1, per_page: 20)
186
+ return [] unless enabled?
187
+
188
+ ensure_provisioned!
189
+ return [] unless BrainzLab.configuration.synapse_valid?
190
+
191
+ client.list_tasks(project_id: project_id, status: status, page: page, per_page: per_page)
192
+ end
193
+
194
+ # Cancel a running task
195
+ # @param task_id [String] Task ID
196
+ # @return [Boolean] True if cancelled
197
+ def cancel_task(task_id)
198
+ return false unless enabled?
199
+
200
+ ensure_provisioned!
201
+ return false unless BrainzLab.configuration.synapse_valid?
202
+
203
+ client.cancel_task(task_id)
204
+ end
205
+
206
+ # Get container logs
207
+ # @param project_id [String] Project ID
208
+ # @param container [String] Optional container name
209
+ # @param lines [Integer] Number of lines (default: 100)
210
+ # @param since [String] Start time (ISO8601)
211
+ # @return [Hash, nil] Log data
212
+ def logs(project_id, container: nil, lines: 100, since: nil)
213
+ return nil unless enabled?
214
+
215
+ ensure_provisioned!
216
+ return nil unless BrainzLab.configuration.synapse_valid?
217
+
218
+ client.get_logs(project_id, container: container, lines: lines, since: since)
219
+ end
220
+
221
+ # Execute command in container
222
+ # @param project_id [String] Project ID
223
+ # @param command [String] Command to execute
224
+ # @param container [String] Optional container name
225
+ # @param timeout [Integer] Timeout in seconds
226
+ # @return [Hash, nil] Command output
227
+ #
228
+ # @example
229
+ # result = BrainzLab::Synapse.exec(project_id, command: "rails db:migrate")
230
+ #
231
+ def exec(project_id, command:, container: nil, timeout: 30)
232
+ return nil unless enabled?
233
+
234
+ ensure_provisioned!
235
+ return nil unless BrainzLab.configuration.synapse_valid?
236
+
237
+ client.exec(project_id, command: command, container: container, timeout: timeout)
238
+ end
239
+
240
+ # === INTERNAL ===
241
+
242
+ def ensure_provisioned!
243
+ return if @provisioned
244
+
245
+ @provisioned = true
246
+ provisioner.ensure_project!
247
+ end
248
+
249
+ def provisioner
250
+ @provisioner ||= Provisioner.new(BrainzLab.configuration)
251
+ end
252
+
253
+ def client
254
+ @client ||= Client.new(BrainzLab.configuration)
255
+ end
256
+
257
+ def reset!
258
+ @client = nil
259
+ @provisioner = nil
260
+ @provisioned = false
261
+ end
262
+
263
+ private
264
+
265
+ def enabled?
266
+ BrainzLab.configuration.synapse_enabled
267
+ end
268
+ end
269
+ end
270
+ end