flight_control 2.0.0.beta.1

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +265 -0
  5. data/Rakefile +6 -0
  6. data/lib/active_job/errors/invalid_operation.rb +5 -0
  7. data/lib/active_job/errors/job_not_found_error.rb +14 -0
  8. data/lib/active_job/errors/query_error.rb +5 -0
  9. data/lib/active_job/executing.rb +41 -0
  10. data/lib/active_job/execution_error.rb +8 -0
  11. data/lib/active_job/failed.rb +7 -0
  12. data/lib/active_job/job_proxy.rb +39 -0
  13. data/lib/active_job/jobs_relation.rb +321 -0
  14. data/lib/active_job/querying.rb +45 -0
  15. data/lib/active_job/queue.rb +63 -0
  16. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +53 -0
  17. data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +42 -0
  18. data/lib/active_job/queue_adapters/solid_queue_ext.rb +309 -0
  19. data/lib/active_job/queues.rb +34 -0
  20. data/lib/flight_control/adapter.rb +155 -0
  21. data/lib/flight_control/application.rb +20 -0
  22. data/lib/flight_control/applications.rb +8 -0
  23. data/lib/flight_control/arguments_filter.rb +25 -0
  24. data/lib/flight_control/authentication.rb +68 -0
  25. data/lib/flight_control/console/connect_to.rb +15 -0
  26. data/lib/flight_control/console/context.rb +11 -0
  27. data/lib/flight_control/console/jobs_help.rb +23 -0
  28. data/lib/flight_control/engine.rb +102 -0
  29. data/lib/flight_control/errors/incompatible_adapter.rb +2 -0
  30. data/lib/flight_control/errors/resource_not_found.rb +2 -0
  31. data/lib/flight_control/errors/unsupported_adapter.rb +2 -0
  32. data/lib/flight_control/i18n_config.rb +17 -0
  33. data/lib/flight_control/identified_by_name.rb +18 -0
  34. data/lib/flight_control/identified_elements.rb +24 -0
  35. data/lib/flight_control/server/recurring_tasks.rb +15 -0
  36. data/lib/flight_control/server/serializable.rb +24 -0
  37. data/lib/flight_control/server/workers.rb +13 -0
  38. data/lib/flight_control/server.rb +33 -0
  39. data/lib/flight_control/tasks.rb +6 -0
  40. data/lib/flight_control/version.rb +3 -0
  41. data/lib/flight_control/workers_relation.rb +79 -0
  42. data/lib/flight_control.rb +39 -0
  43. metadata +377 -0
@@ -0,0 +1,309 @@
1
+ module ActiveJob::QueueAdapters::SolidQueueExt
2
+ include FlightControl::Adapter
3
+ include Workers
4
+ include RecurringTasks
5
+
6
+ def queues
7
+ queues = SolidQueue::Queue.all
8
+ pauses = SolidQueue::Pause.where(queue_name: queues.map(&:name)).index_by(&:queue_name)
9
+
10
+ queues.collect do |queue|
11
+ {
12
+ name: queue.name,
13
+ size: queue.size,
14
+ active: pauses[queue.name].nil?
15
+ }
16
+ end
17
+ end
18
+
19
+ def queue_size(queue_name)
20
+ find_queue_by_name(queue_name).size
21
+ end
22
+
23
+ def clear_queue(queue_name)
24
+ find_queue_by_name(queue_name).clear
25
+ end
26
+
27
+ def pause_queue(queue_name)
28
+ find_queue_by_name(queue_name).pause
29
+ end
30
+
31
+ def resume_queue(queue_name)
32
+ find_queue_by_name(queue_name).resume
33
+ end
34
+
35
+ def queue_paused?(queue_name)
36
+ find_queue_by_name(queue_name).paused?
37
+ end
38
+
39
+ def supported_job_statuses
40
+ SolidQueueJobs::STATUS_MAP.keys
41
+ end
42
+
43
+ def supported_job_filters(*)
44
+ [:queue_name, :job_class_name, :finished_at]
45
+ end
46
+
47
+ def jobs_count(jobs_relation)
48
+ SolidQueueJobs.new(jobs_relation).count
49
+ end
50
+
51
+ def fetch_jobs(jobs_relation)
52
+ SolidQueueJobs.new(jobs_relation).jobs.map do |job|
53
+ deserialize_and_proxy_solid_queue_job(job, jobs_relation.status)
54
+ end
55
+ end
56
+
57
+ def retry_all_jobs(jobs_relation)
58
+ SolidQueueJobs.new(jobs_relation).retry_all
59
+ end
60
+
61
+ def retry_job(job, jobs_relation)
62
+ find_solid_queue_job!(job.job_id, jobs_relation).retry
63
+ end
64
+
65
+ def discard_all_jobs(jobs_relation)
66
+ SolidQueueJobs.new(jobs_relation).discard_all
67
+ end
68
+
69
+ def discard_job(job, jobs_relation)
70
+ find_solid_queue_job!(job.job_id, jobs_relation).discard
71
+ end
72
+
73
+ def dispatch_job(job, jobs_relation)
74
+ dispatch_immediately find_solid_queue_job!(job.job_id, jobs_relation)
75
+ end
76
+
77
+ def find_job(job_id, *)
78
+ if (job = SolidQueue::Job.where(active_job_id: job_id).order(:id).last)
79
+ deserialize_and_proxy_solid_queue_job job
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def find_queue_by_name(queue_name)
86
+ SolidQueue::Queue.find_by_name(queue_name)
87
+ end
88
+
89
+ def find_solid_queue_job!(job_id, jobs_relation)
90
+ find_solid_queue_job(job_id, jobs_relation) or raise ActiveJob::Errors::JobNotFoundError.new(job_id, jobs_relation)
91
+ end
92
+
93
+ def find_solid_queue_job(job_id, jobs_relation)
94
+ SolidQueueJobs.new(jobs_relation).find_job(job_id)
95
+ end
96
+
97
+ def deserialize_and_proxy_solid_queue_job(solid_queue_job, job_status = nil)
98
+ job_status ||= status_from_solid_queue_job(solid_queue_job)
99
+
100
+ ActiveJob::JobProxy.new(solid_queue_job.arguments).tap do |job|
101
+ job.status = job_status
102
+ job.last_execution_error = execution_error_from_solid_queue_job(solid_queue_job) if job_status == :failed
103
+ job.raw_data = solid_queue_job.as_json
104
+ job.filtered_raw_data = filter_raw_data_arguments(job.raw_data)
105
+ job.failed_at = solid_queue_job&.failed_execution&.created_at if job_status == :failed
106
+ job.finished_at = solid_queue_job.finished_at
107
+ job.blocked_by = solid_queue_job.concurrency_key
108
+ job.blocked_until = solid_queue_job&.blocked_execution&.expires_at if job_status == :blocked
109
+ job.worker_id = solid_queue_job&.claimed_execution&.process_id if job_status == :in_progress
110
+ job.started_at = solid_queue_job&.claimed_execution&.created_at if job_status == :in_progress
111
+ job.scheduled_at = solid_queue_job.scheduled_at
112
+ end
113
+ end
114
+
115
+ def filter_raw_data_arguments(raw_data)
116
+ raw_data.deep_dup.tap do |filtered_raw_data|
117
+ filtered_raw_data["arguments"]["arguments"] = FlightControl.job_arguments_filter.apply_to(filtered_raw_data.dig("arguments", "arguments"))
118
+ end
119
+ end
120
+
121
+ def status_from_solid_queue_job(solid_queue_job)
122
+ SolidQueueJobs::STATUS_MAP.invert[solid_queue_job.status]
123
+ end
124
+
125
+ def execution_error_from_solid_queue_job(solid_queue_job)
126
+ if solid_queue_job.failed?
127
+ ActiveJob::ExecutionError.new \
128
+ error_class: solid_queue_job.failed_execution.exception_class,
129
+ message: solid_queue_job.failed_execution.message,
130
+ backtrace: solid_queue_job.failed_execution.backtrace || []
131
+ end
132
+ end
133
+
134
+ def dispatch_immediately(job)
135
+ if job.blocked?
136
+ SolidQueue::Job.transaction do
137
+ job.dispatch_bypassing_concurrency_limits
138
+ job.blocked_execution.destroy!
139
+ end
140
+ else
141
+ job.scheduled_execution.update!(scheduled_at: Time.now)
142
+ end
143
+ end
144
+
145
+ class SolidQueueJobs
146
+ STATUS_MAP = {
147
+ pending: :ready,
148
+ failed: :failed,
149
+ in_progress: :claimed,
150
+ blocked: :blocked,
151
+ scheduled: :scheduled,
152
+ finished: :finished
153
+ }
154
+
155
+ def initialize(jobs_relation)
156
+ @jobs_relation = jobs_relation
157
+ end
158
+
159
+ def jobs
160
+ solid_queue_status.finished? ? order_finished_jobs(finished_jobs) : order_executions(executions).map(&:job).compact
161
+ end
162
+
163
+ def count
164
+ limit_value_provided? ? direct_count : internally_limited_count
165
+ end
166
+
167
+ def find_job(active_job_id)
168
+ if (job = SolidQueue::Job.where(active_job_id: active_job_id).order(:id).last)
169
+ job if matches_relation_filters?(job)
170
+ end
171
+ end
172
+
173
+ def discard_all
174
+ execution_class_by_status.discard_all_from_jobs(jobs)
175
+ end
176
+
177
+ def retry_all
178
+ SolidQueue::FailedExecution.retry_all(jobs)
179
+ end
180
+
181
+ private
182
+
183
+ attr_reader :jobs_relation
184
+
185
+ delegate :queue_name, :limit_value, :limit_value_provided?, :offset_value, :job_class_name,
186
+ :default_page_size, :worker_id, :recurring_task_id, :finished_at, to: :jobs_relation
187
+
188
+ def executions
189
+ execution_class_by_status
190
+ .then { |executions| include_execution_association(executions) }
191
+ .then { |executions| filter_executions_by_queue(executions) }
192
+ .then { |executions| filter_executions_by_class(executions) }
193
+ .then { |executions| filter_executions_by_process_id(executions) }
194
+ .then { |executions| filter_executions_by_task_key(executions) }
195
+ .then { |executions| limit(executions) }
196
+ .then { |executions| offset(executions) }
197
+ end
198
+
199
+ def finished_jobs
200
+ SolidQueue::Job.finished
201
+ .then { |jobs| filter_jobs_by_queue(jobs) }
202
+ .then { |jobs| filter_jobs_by_class(jobs) }
203
+ .then { |jobs| filter_jobs_by_finished_at(jobs) }
204
+ .then { |jobs| limit(jobs) }
205
+ .then { |jobs| offset(jobs) }
206
+ end
207
+
208
+ def order_finished_jobs(jobs)
209
+ jobs.order(finished_at: :desc)
210
+ end
211
+
212
+ def order_executions(executions)
213
+ # Follow polling order for scheduled executions, the rest newest first
214
+ if solid_queue_status.scheduled?
215
+ executions.ordered
216
+ else
217
+ executions.order(job_id: :desc)
218
+ end
219
+ end
220
+
221
+ def matches_relation_filters?(job)
222
+ matches_status?(job) && matches_queue_name?(job)
223
+ end
224
+
225
+ def direct_count
226
+ solid_queue_status.finished? ? finished_jobs.count : executions.count
227
+ end
228
+
229
+ def internally_limited_count
230
+ count_limit = FlightControl.internal_query_count_limit + 1
231
+ limited_count = solid_queue_status.finished? ? finished_jobs.limit(count_limit).count : executions.limit(count_limit).count
232
+ (limited_count == count_limit) ? Float::INFINITY : limited_count
233
+ end
234
+
235
+ def execution_class_by_status
236
+ if recurring_task_id.present?
237
+ SolidQueue::RecurringExecution
238
+ elsif solid_queue_status.present? && !solid_queue_status.finished?
239
+ "SolidQueue::#{solid_queue_status.capitalize}Execution".safe_constantize
240
+ else
241
+ raise ActiveJob::Errors::QueryError, "Status not supported: #{jobs_relation.status}"
242
+ end
243
+ end
244
+
245
+ def include_execution_association(executions)
246
+ solid_queue_status.present? ? executions.includes(job: "#{solid_queue_status}_execution") : executions.includes(:job)
247
+ end
248
+
249
+ def filter_executions_by_queue(executions)
250
+ return executions unless queue_name.present?
251
+
252
+ if solid_queue_status.ready?
253
+ executions.where(queue_name: queue_name)
254
+ else
255
+ executions.where(job: {queue_name: queue_name})
256
+ end
257
+ end
258
+
259
+ def filter_jobs_by_queue(jobs)
260
+ queue_name.present? ? jobs.where(queue_name: queue_name) : jobs
261
+ end
262
+
263
+ def filter_executions_by_class(executions)
264
+ job_class_name.present? ? executions.where(job: {class_name: job_class_name}) : executions
265
+ end
266
+
267
+ def filter_executions_by_process_id(executions)
268
+ return executions unless worker_id.present?
269
+
270
+ if solid_queue_status.claimed?
271
+ executions.where(process_id: worker_id)
272
+ else
273
+ raise ActiveJob::Errors::QueryError, "Filtering by worker id is not supported for status #{jobs_relation.status}"
274
+ end
275
+ end
276
+
277
+ def filter_executions_by_task_key(executions)
278
+ recurring_task_id.present? ? executions.where(task_key: recurring_task_id) : executions
279
+ end
280
+
281
+ def filter_jobs_by_class(jobs)
282
+ job_class_name.present? ? jobs.where(class_name: job_class_name) : jobs
283
+ end
284
+
285
+ def filter_jobs_by_finished_at(jobs)
286
+ finished_at.present? ? jobs.where(finished_at: finished_at) : jobs
287
+ end
288
+
289
+ def limit(executions_or_jobs)
290
+ limit_value.present? ? executions_or_jobs.limit(limit_value) : executions_or_jobs
291
+ end
292
+
293
+ def offset(executions_or_jobs)
294
+ offset_value.present? ? executions_or_jobs.offset(offset_value) : executions_or_jobs
295
+ end
296
+
297
+ def matches_status?(job)
298
+ solid_queue_status.blank? || job.public_send("#{solid_queue_status}?")
299
+ end
300
+
301
+ def matches_queue_name?(job)
302
+ queue_name.blank? || job.queue_name == queue_name
303
+ end
304
+
305
+ def solid_queue_status
306
+ STATUS_MAP[jobs_relation.status].to_s.inquiry
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,34 @@
1
+ # An enumerable collection of queues that supports direct access to queues by name.
2
+ #
3
+ # queue_1 = ApplicationJob::Queue.new("queue_1")
4
+ # queue_2 = ApplicationJob::Queue.new("queue_2")
5
+ # queues = ApplicationJob::Queues.new([queue_1, queue_2])
6
+ #
7
+ # queues[:queue_1] #=> queue_1
8
+ # queues[:queue_2] #=> queue_2
9
+ # queues.to_a #=> [ queue_1, queue_2 ] # Enumerable
10
+ #
11
+ # See +ActiveJob::Queue+.
12
+ class ActiveJob::Queues
13
+ include Enumerable
14
+
15
+ delegate :each, to: :values
16
+ delegate :values, to: :queues_by_id, private: true
17
+ delegate :size, :length, :to_s, :inspect, to: :queues_by_id
18
+
19
+ def initialize(queues)
20
+ @queues_by_id = queues.index_by(&:id).with_indifferent_access
21
+ end
22
+
23
+ def to_h
24
+ queues_by_id.dup
25
+ end
26
+
27
+ def [](name)
28
+ queues_by_id[name.to_s.parameterize]
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :queues_by_id
34
+ end
@@ -0,0 +1,155 @@
1
+ module FlightControl::Adapter
2
+ def activating(&block)
3
+ block.call
4
+ end
5
+
6
+ def supports_job_status?(status)
7
+ supported_job_statuses.include?(status)
8
+ end
9
+
10
+ def supported_job_statuses
11
+ # All adapters need to support these at a minimum
12
+ [:pending, :failed]
13
+ end
14
+
15
+ def supports_job_filter?(jobs_relation, filter)
16
+ supported_job_filters(jobs_relation).include?(filter)
17
+ end
18
+
19
+ # List of filters supported natively. Non-supported filters are done in memory.
20
+ def supported_job_filters(jobs_relation)
21
+ []
22
+ end
23
+
24
+ def supports_queue_pausing?
25
+ true
26
+ end
27
+
28
+ def exposes_workers?
29
+ false
30
+ end
31
+
32
+ def supports_recurring_tasks?
33
+ false
34
+ end
35
+
36
+ # Returns an array with the list of recurring tasks. Each task is represented as a hash
37
+ # with these attributes:
38
+ # {
39
+ # id: "periodic-job",
40
+ # job_class_name: "MyJob",
41
+ # arguments: [ 123, { arg: :value }]
42
+ # schedule: "every monday at 9 am",
43
+ # last_enqueued_at: Fri, 26 Jan 2024 20:31:09.652174000 UTC +00:00,
44
+ # }
45
+ def recurring_tasks
46
+ if supports_recurring_tasks?
47
+ raise_incompatible_adapter_error_from :recurring_tasks
48
+ end
49
+ end
50
+
51
+ # Returns a recurring task represented by a hash as indicated above
52
+ def find_recurring_task(recurring_task_id)
53
+ if supports_recurring_tasks?
54
+ raise_incompatible_adapter_error_from :find_recurring_task
55
+ end
56
+ end
57
+
58
+ # Returns an array with the list of workers. Each worker is represented as a hash
59
+ # with these attributes:
60
+ # {
61
+ # id: 123,
62
+ # name: "worker-name",
63
+ # hostname: "hey-default-101",
64
+ # last_heartbeat_at: Fri, 26 Jan 2024 20:31:09.652174000 UTC +00:00,
65
+ # configuration: { ... }
66
+ # raw_data: { ... }
67
+ # }
68
+ def workers
69
+ if exposes_workers?
70
+ raise_incompatible_adapter_error_from :workers
71
+ end
72
+ end
73
+
74
+ # Returns a worker represented by a hash as indicated above
75
+ def find_worker(worker_id)
76
+ if exposes_workers?
77
+ raise_incompatible_adapter_error_from :find_worker
78
+ end
79
+ end
80
+
81
+ # Returns an array with the list of queues. Each queue is represented as a hash
82
+ # with these attributes:
83
+ # {
84
+ # name: "queue_name",
85
+ # size: 1,
86
+ # active: true
87
+ # }
88
+ def queues
89
+ raise_incompatible_adapter_error_from :queue_names
90
+ end
91
+
92
+ def queue_size(queue_name)
93
+ raise_incompatible_adapter_error_from :queue_size
94
+ end
95
+
96
+ def clear_queue(queue_name)
97
+ raise_incompatible_adapter_error_from :clear_queue
98
+ end
99
+
100
+ def pause_queue(queue_name)
101
+ if supports_queue_pausing?
102
+ raise_incompatible_adapter_error_from :pause_queue
103
+ end
104
+ end
105
+
106
+ def resume_queue(queue_name)
107
+ if supports_queue_pausing?
108
+ raise_incompatible_adapter_error_from :resume_queue
109
+ end
110
+ end
111
+
112
+ def queue_paused?(queue_name)
113
+ if supports_queue_pausing?
114
+ raise_incompatible_adapter_error_from :queue_paused?
115
+ end
116
+ end
117
+
118
+ def jobs_count(jobs_relation)
119
+ raise_incompatible_adapter_error_from :jobs_count
120
+ end
121
+
122
+ def fetch_jobs(jobs_relation)
123
+ raise_incompatible_adapter_error_from :fetch_jobs
124
+ end
125
+
126
+ def retry_all_jobs(jobs_relation)
127
+ raise_incompatible_adapter_error_from :retry_all_jobs
128
+ end
129
+
130
+ def retry_job(job, jobs_relation)
131
+ raise_incompatible_adapter_error_from :retry_job
132
+ end
133
+
134
+ def discard_all_jobs(jobs_relation)
135
+ raise_incompatible_adapter_error_from :discard_all_jobs
136
+ end
137
+
138
+ def discard_job(job, jobs_relation)
139
+ raise_incompatible_adapter_error_from :discard_job
140
+ end
141
+
142
+ def dispatch_job(job, jobs_relation)
143
+ raise_incompatible_adapter_error_from :dispatch_job
144
+ end
145
+
146
+ def find_job(job_id, *)
147
+ raise_incompatible_adapter_error_from :find_job
148
+ end
149
+
150
+ private
151
+
152
+ def raise_incompatible_adapter_error_from(method_name)
153
+ raise FlightControl::Errors::IncompatibleAdapter, "Adapter #{ActiveJob.adapter_name(self)} must implement `#{method_name}`"
154
+ end
155
+ end
@@ -0,0 +1,20 @@
1
+ # An application containing backend jobs servers
2
+ class FlightControl::Application
3
+ include FlightControl::IdentifiedByName
4
+
5
+ attr_reader :servers
6
+
7
+ def initialize(name:)
8
+ super
9
+ @servers = FlightControl::IdentifiedElements.new
10
+ end
11
+
12
+ def add_servers(queue_adapters_by_name)
13
+ queue_adapters_by_name.each do |name, queue_adapter|
14
+ adapter, cleaner = queue_adapter
15
+
16
+ servers << FlightControl::Server.new(name: name.to_s, queue_adapter: adapter,
17
+ backtrace_cleaner: cleaner, application: self)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # A container to register applications
2
+ class FlightControl::Applications < FlightControl::IdentifiedElements
3
+ def add(name, queue_adapters_by_name = {})
4
+ self << FlightControl::Application.new(name: name).tap do |application|
5
+ application.add_servers(queue_adapters_by_name)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ # Replaces argument values with [FILTERED] for any keys that match a filter.
2
+ class FlightControl::ArgumentsFilter
3
+ FILTERED = "[FILTERED]"
4
+
5
+ def initialize(filter)
6
+ @filter = filter
7
+ end
8
+
9
+ def apply_to(arguments)
10
+ case arguments
11
+ when Array
12
+ arguments.map { |a| apply_to(a) }
13
+ when Hash
14
+ arguments.map do |k, v|
15
+ [k, filter.include?(k.to_s) ? FILTERED : v]
16
+ end.to_h
17
+ else
18
+ arguments
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :filter
25
+ end
@@ -0,0 +1,68 @@
1
+ require "rails/command"
2
+
3
+ class FlightControl::Authentication < Rails::Command::Base
4
+ def self.configure
5
+ new.configure
6
+ end
7
+
8
+ def configure
9
+ if credentials_accessible?
10
+ if authentication_configured?
11
+ say "HTTP Basic Authentication is already configured for `#{Rails.env}`. You can edit it using `credentials:edit`"
12
+ else
13
+ say "Setting up credentials for HTTP Basic Authentication for `#{Rails.env}` environment."
14
+ say ""
15
+
16
+ username = ask "Enter username: "
17
+ password = SecureRandom.base58(64)
18
+
19
+ store_credentials(username, password)
20
+ say "Username and password stored in Rails encrypted credentials."
21
+ say ""
22
+ say "You can now access Flight Control with: "
23
+ say ""
24
+ say " - Username: #{username}"
25
+ say " - password: #{password}"
26
+ say ""
27
+ say "You can also edit these in the future via `credentials:edit`"
28
+ end
29
+ else
30
+ say "Rails credentials haven't been configured or aren't accessible. Configure them following the instructions in `credentials:help`"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :environment
37
+
38
+ def credentials_accessible?
39
+ credentials.read.present?
40
+ end
41
+
42
+ def authentication_configured?
43
+ %i[http_basic_auth_user http_basic_auth_password].any? do |key|
44
+ credentials.dig(:flight_control, key).present?
45
+ end
46
+ end
47
+
48
+ def store_credentials(username, password)
49
+ content = credentials.read + "\n" + http_authentication_entry(username, password) + "\n"
50
+ credentials.write(content)
51
+ end
52
+
53
+ def credentials
54
+ @credentials ||= Rails.application.encrypted(config.content_path, key_path: config.key_path)
55
+ end
56
+
57
+ def config
58
+ Rails.application.config.credentials
59
+ end
60
+
61
+ def http_authentication_entry(username, password)
62
+ <<~ENTRY
63
+ flight_control:
64
+ http_basic_auth_user: #{username}
65
+ http_basic_auth_password: #{password}
66
+ ENTRY
67
+ end
68
+ end
@@ -0,0 +1,15 @@
1
+ require "irb/command"
2
+
3
+ module FlightControl::Console
4
+ class ConnectTo < IRB::Command::Base
5
+ category "Flight Control jobs"
6
+ description "Connect to a job server"
7
+
8
+ def execute(server_locator)
9
+ server = FlightControl::Server.from_global_id(server_locator)
10
+ FlightControl::Current.server = server
11
+
12
+ puts "Connected to #{server_locator}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module FlightControl::Console::Context
2
+ mattr_accessor :jobs_server
3
+
4
+ def evaluate(*)
5
+ if FlightControl::Current.server
6
+ FlightControl::Current.server.activating { super }
7
+ else
8
+ super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ require "irb/command"
2
+
3
+ module FlightControl::Console
4
+ class JobsHelp < IRB::Command::Base
5
+ category "Flight Control jobs"
6
+ description "Show help for managing jobs"
7
+
8
+ def execute(*)
9
+ puts "You are currently connected to #{FlightControl::Current.server}" if FlightControl::Current.server
10
+
11
+ puts "You can connect to a job server with"
12
+ puts %( connect_to <app_id>:<server_id>\n\n)
13
+
14
+ puts "Available job servers:\n"
15
+
16
+ FlightControl.applications.each do |application|
17
+ application.servers.each do |server|
18
+ puts " * #{server.to_global_id}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end