cloudtasker-tonix 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/lint_rubocop.yml +15 -0
  3. data/.github/workflows/test_ruby_3.x.yml +40 -0
  4. data/.gitignore +23 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +96 -0
  7. data/Appraisals +76 -0
  8. data/CHANGELOG.md +248 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +1311 -0
  13. data/Rakefile +8 -0
  14. data/_config.yml +1 -0
  15. data/app/controllers/cloudtasker/worker_controller.rb +107 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/cloudtasker.gemspec +42 -0
  19. data/config/routes.rb +5 -0
  20. data/docs/BATCH_JOBS.md +144 -0
  21. data/docs/CRON_JOBS.md +129 -0
  22. data/docs/STORABLE_JOBS.md +68 -0
  23. data/docs/UNIQUE_JOBS.md +190 -0
  24. data/exe/cloudtasker +30 -0
  25. data/gemfiles/.bundle/config +2 -0
  26. data/gemfiles/google_cloud_tasks_1.0.gemfile +17 -0
  27. data/gemfiles/google_cloud_tasks_1.1.gemfile +17 -0
  28. data/gemfiles/google_cloud_tasks_1.2.gemfile +17 -0
  29. data/gemfiles/google_cloud_tasks_1.3.gemfile +17 -0
  30. data/gemfiles/google_cloud_tasks_1.4.gemfile +17 -0
  31. data/gemfiles/google_cloud_tasks_1.5.gemfile +17 -0
  32. data/gemfiles/google_cloud_tasks_2.0.gemfile +17 -0
  33. data/gemfiles/google_cloud_tasks_2.1.gemfile +17 -0
  34. data/gemfiles/rails_6.1.gemfile +20 -0
  35. data/gemfiles/rails_7.0.gemfile +18 -0
  36. data/gemfiles/rails_7.1.gemfile +18 -0
  37. data/gemfiles/rails_8.0.gemfile +18 -0
  38. data/gemfiles/rails_8.1.gemfile +18 -0
  39. data/gemfiles/semantic_logger_3.4.gemfile +16 -0
  40. data/gemfiles/semantic_logger_4.6.gemfile +16 -0
  41. data/gemfiles/semantic_logger_4.7.0.gemfile +16 -0
  42. data/gemfiles/semantic_logger_4.7.2.gemfile +16 -0
  43. data/lib/active_job/queue_adapters/cloudtasker_adapter.rb +89 -0
  44. data/lib/cloudtasker/authentication_error.rb +6 -0
  45. data/lib/cloudtasker/authenticator.rb +90 -0
  46. data/lib/cloudtasker/backend/google_cloud_task_v1.rb +228 -0
  47. data/lib/cloudtasker/backend/google_cloud_task_v2.rb +231 -0
  48. data/lib/cloudtasker/backend/memory_task.rb +202 -0
  49. data/lib/cloudtasker/backend/redis_task.rb +291 -0
  50. data/lib/cloudtasker/batch/batch_progress.rb +142 -0
  51. data/lib/cloudtasker/batch/extension/worker.rb +13 -0
  52. data/lib/cloudtasker/batch/job.rb +558 -0
  53. data/lib/cloudtasker/batch/middleware/server.rb +14 -0
  54. data/lib/cloudtasker/batch/middleware.rb +25 -0
  55. data/lib/cloudtasker/batch.rb +5 -0
  56. data/lib/cloudtasker/cli.rb +194 -0
  57. data/lib/cloudtasker/cloud_task.rb +130 -0
  58. data/lib/cloudtasker/config.rb +319 -0
  59. data/lib/cloudtasker/cron/job.rb +205 -0
  60. data/lib/cloudtasker/cron/middleware/server.rb +14 -0
  61. data/lib/cloudtasker/cron/middleware.rb +20 -0
  62. data/lib/cloudtasker/cron/schedule.rb +308 -0
  63. data/lib/cloudtasker/cron.rb +5 -0
  64. data/lib/cloudtasker/dead_worker_error.rb +6 -0
  65. data/lib/cloudtasker/engine.rb +24 -0
  66. data/lib/cloudtasker/invalid_worker_error.rb +6 -0
  67. data/lib/cloudtasker/local_server.rb +99 -0
  68. data/lib/cloudtasker/max_task_size_exceeded_error.rb +14 -0
  69. data/lib/cloudtasker/meta_store.rb +86 -0
  70. data/lib/cloudtasker/middleware/chain.rb +250 -0
  71. data/lib/cloudtasker/missing_worker_arguments_error.rb +6 -0
  72. data/lib/cloudtasker/redis_client.rb +166 -0
  73. data/lib/cloudtasker/retry_worker_error.rb +6 -0
  74. data/lib/cloudtasker/storable/worker.rb +78 -0
  75. data/lib/cloudtasker/storable.rb +3 -0
  76. data/lib/cloudtasker/testing.rb +184 -0
  77. data/lib/cloudtasker/unique_job/conflict_strategy/base_strategy.rb +39 -0
  78. data/lib/cloudtasker/unique_job/conflict_strategy/raise.rb +28 -0
  79. data/lib/cloudtasker/unique_job/conflict_strategy/reject.rb +11 -0
  80. data/lib/cloudtasker/unique_job/conflict_strategy/reschedule.rb +30 -0
  81. data/lib/cloudtasker/unique_job/job.rb +168 -0
  82. data/lib/cloudtasker/unique_job/lock/base_lock.rb +70 -0
  83. data/lib/cloudtasker/unique_job/lock/no_op.rb +11 -0
  84. data/lib/cloudtasker/unique_job/lock/until_completed.rb +40 -0
  85. data/lib/cloudtasker/unique_job/lock/until_executed.rb +36 -0
  86. data/lib/cloudtasker/unique_job/lock/until_executing.rb +30 -0
  87. data/lib/cloudtasker/unique_job/lock/while_executing.rb +25 -0
  88. data/lib/cloudtasker/unique_job/lock_error.rb +8 -0
  89. data/lib/cloudtasker/unique_job/middleware/client.rb +15 -0
  90. data/lib/cloudtasker/unique_job/middleware/server.rb +14 -0
  91. data/lib/cloudtasker/unique_job/middleware.rb +36 -0
  92. data/lib/cloudtasker/unique_job.rb +32 -0
  93. data/lib/cloudtasker/version.rb +5 -0
  94. data/lib/cloudtasker/worker.rb +487 -0
  95. data/lib/cloudtasker/worker_handler.rb +250 -0
  96. data/lib/cloudtasker/worker_logger.rb +231 -0
  97. data/lib/cloudtasker/worker_wrapper.rb +52 -0
  98. data/lib/cloudtasker.rb +57 -0
  99. data/lib/tasks/setup_queue.rake +20 -0
  100. metadata +241 -0
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'google/cloud/tasks'
4
+
5
+ module Cloudtasker
6
+ # Build, serialize and schedule tasks on the processing backend.
7
+ class WorkerHandler
8
+ attr_reader :worker
9
+
10
+ # Alrogith used to sign the verification token
11
+ JWT_ALG = 'HS256'
12
+
13
+ # Sub-namespace to use for redis keys when storing
14
+ # payloads in Redis
15
+ REDIS_PAYLOAD_NAMESPACE = 'payload'
16
+
17
+ #
18
+ # Return a namespaced key
19
+ #
20
+ # @param [String, Symbol] val The key to namespace
21
+ #
22
+ # @return [String] The namespaced key.
23
+ #
24
+ def self.key(val)
25
+ return nil if val.nil?
26
+
27
+ [to_s.underscore, val.to_s].join('/')
28
+ end
29
+
30
+ #
31
+ # Return the cloudtasker redis client
32
+ #
33
+ # @return [Cloudtasker::RedisClient] The cloudtasker redis client.
34
+ #
35
+ def self.redis
36
+ @redis ||= begin
37
+ require 'cloudtasker/redis_client'
38
+ RedisClient.new
39
+ end
40
+ end
41
+
42
+ #
43
+ # Log error on execution failure.
44
+ #
45
+ # @param [Cloudtasker::Worker, nil] worker The worker.
46
+ # @param [Exception] error The error to log.
47
+ #
48
+ # @void
49
+ #
50
+ def self.log_execution_error(worker, error)
51
+ # ActiveJob has its own error logging. No need to double log the error.
52
+ # Note: we use string matching instead of class matching as
53
+ # ActiveJob::QueueAdapters::CloudtaskerAdapter::JobWrapper might not be loaded
54
+ return if worker.class.to_s =~ /^ActiveJob::/
55
+
56
+ # Do not log error when a retry was specifically requested
57
+ return if error.is_a?(RetryWorkerError)
58
+
59
+ # Choose logger to use based on context
60
+ # Worker will be nil on InvalidWorkerError - in that case we use generic logging
61
+ logger = worker&.logger || Cloudtasker.logger
62
+
63
+ # Log error
64
+ logger.error(error)
65
+ end
66
+
67
+ #
68
+ # Execute a task worker from a task payload
69
+ #
70
+ # @param [Hash] input_payload The Cloud Task payload.
71
+ #
72
+ # @return [Any] The return value of the worker perform method.
73
+ #
74
+ def self.execute_from_payload!(input_payload)
75
+ with_worker_handling(input_payload, &:execute)
76
+ end
77
+
78
+ #
79
+ # Local middleware used to retrieve the job arg payload from cache
80
+ # if a arg payload reference is present.
81
+ #
82
+ # @param [Hash] payload The full job payload
83
+ #
84
+ # @yield [Hash] The actual payload to use to process the job.
85
+ #
86
+ # @return [Any] The block result
87
+ #
88
+ def self.with_worker_handling(input_payload)
89
+ # Extract payload information
90
+ extracted_payload = extract_payload(input_payload)
91
+ payload = extracted_payload[:payload]
92
+ args_payload_key = extracted_payload[:args_payload_key]
93
+
94
+ # Build worker
95
+ worker = Cloudtasker::Worker.from_hash(payload) || raise(InvalidWorkerError)
96
+
97
+ # Yied worker
98
+ resp = yield(worker)
99
+
100
+ # Delete stored args payload if job has completed
101
+ redis.del(args_payload_key) if args_payload_key && !worker.job_reenqueued
102
+
103
+ resp
104
+ rescue DeadWorkerError => e
105
+ # Delete stored args payload if job is dead
106
+ redis.del(args_payload_key) if args_payload_key
107
+ log_execution_error(worker, e)
108
+ Cloudtasker.config.on_dead.call(e, worker)
109
+ raise(e)
110
+ rescue StandardError => e
111
+ log_execution_error(worker, e)
112
+ Cloudtasker.config.on_error.call(e, worker)
113
+ raise(e)
114
+ end
115
+
116
+ #
117
+ # Return the argument payload key (if present) along with the actual worker payload.
118
+ #
119
+ # If the payload was stored in Redis then retrieve it.
120
+ #
121
+ # @return [Hash] Hash
122
+ #
123
+ def self.extract_payload(input_payload)
124
+ # Get references
125
+ payload = JSON.parse(input_payload.to_json, symbolize_names: true)
126
+ args_payload_id = payload.delete(:job_args_payload_id)
127
+ args_payload_key = args_payload_id ? key([REDIS_PAYLOAD_NAMESPACE, args_payload_id].join('/')) : nil
128
+
129
+ # Retrieve the actual worker args payload
130
+ args_payload = args_payload_key ? redis.fetch(args_payload_key) : payload[:job_args]
131
+
132
+ # Return the payload
133
+ {
134
+ args_payload_key: args_payload_key,
135
+ payload: payload.merge(job_args: args_payload)
136
+ }
137
+ end
138
+
139
+ #
140
+ # Prepare a new cloud task.
141
+ #
142
+ # @param [Cloudtasker::Worker] worker The worker instance.
143
+ #
144
+ def initialize(worker)
145
+ @worker = worker
146
+ end
147
+
148
+ #
149
+ # Return the full task configuration sent to Cloud Task
150
+ #
151
+ # @return [Hash] The task body
152
+ #
153
+ def task_payload
154
+ # Generate content
155
+ worker_payload_json = worker_payload.to_json
156
+
157
+ # Build payload
158
+ {
159
+ http_request: {
160
+ http_method: 'POST',
161
+ url: Cloudtasker.config.processor_url,
162
+ headers: {
163
+ Cloudtasker::Config::CONTENT_TYPE_HEADER => 'application/json',
164
+ Cloudtasker::Config::CT_SIGNATURE_HEADER => Authenticator.sign_payload(worker_payload_json)
165
+ }.compact,
166
+ oidc_token: Cloudtasker.config.oidc,
167
+ body: worker_payload_json
168
+ }.compact,
169
+ dispatch_deadline: worker.dispatch_deadline.to_i,
170
+ queue: worker.job_queue
171
+ }
172
+ end
173
+
174
+ #
175
+ # Return true if the worker args must be stored in Redis.
176
+ #
177
+ # @return [Boolean] True if the payload must be stored in redis.
178
+ #
179
+ def store_payload_in_redis?
180
+ Cloudtasker.config.redis_payload_storage_threshold &&
181
+ worker.job_args.to_json.bytesize > (Cloudtasker.config.redis_payload_storage_threshold * 1024)
182
+ end
183
+
184
+ #
185
+ # Return the payload to use for job arguments. This payload
186
+ # is merged inside the #worker_payload.
187
+ #
188
+ # If the argument payload must be stored in Redis then returns:
189
+ # `{ job_args_payload_id: <worker_id> }`
190
+ #
191
+ # If the argument payload must be natively handled by the backend
192
+ # then returns:
193
+ # `{ job_args: [...] }`
194
+ #
195
+ # @return [Hash] The worker args payload.
196
+ #
197
+ def worker_args_payload
198
+ @worker_args_payload ||= if store_payload_in_redis?
199
+ # Store payload in Redis
200
+ self.class.redis.write(
201
+ self.class.key([REDIS_PAYLOAD_NAMESPACE, worker.job_id].join('/')),
202
+ worker.job_args
203
+ )
204
+
205
+ # Return reference to args payload
206
+ { job_args_payload_id: worker.job_id }
207
+ else
208
+ # Return regular job args payload
209
+ { job_args: worker.job_args }
210
+ end
211
+ end
212
+
213
+ #
214
+ # Return the task payload that Google Task will eventually
215
+ # send to the job processor.
216
+ #
217
+ # The payload includes the worker name and the arguments to
218
+ # pass to the worker.
219
+ #
220
+ # The worker arguments should use primitive types as much
221
+ # as possible as all arguments will be serialized to JSON.
222
+ #
223
+ # @return [Hash] The job payload
224
+ #
225
+ def worker_payload
226
+ @worker_payload ||= {
227
+ worker: worker.job_class_name,
228
+ job_queue: worker.job_queue,
229
+ job_id: worker.job_id,
230
+ job_meta: worker.job_meta.to_h
231
+ }.merge(worker_args_payload)
232
+ end
233
+
234
+ #
235
+ # Schedule the task on GCP Cloud Task.
236
+ #
237
+ # @param [Integer, nil] time_at A unix timestamp specifying when to run the job.
238
+ # Leave to `nil` to run now.
239
+ #
240
+ # @return [Cloudtasker::CloudTask] The Google Task response
241
+ #
242
+ def schedule(time_at: nil)
243
+ # Generate task payload
244
+ task = task_payload.merge(schedule_time: time_at).compact
245
+
246
+ # Create and return remote task
247
+ CloudTask.create(task)
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ # Add contextual information to logs generated
5
+ # by workers
6
+ class WorkerLogger
7
+ attr_accessor :worker
8
+
9
+ class << self
10
+ attr_accessor :log_context_processor
11
+
12
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
13
+ #
14
+ # Truncate an array or hash payload.
15
+ #
16
+ # This can be used to log arguments on jobs while still keeping logs to a
17
+ # reasonable size.
18
+ #
19
+ # @param [Hash,Array] payload The payload to truncate
20
+ # @param [Integer] string_limit The maximum size for strings. Set to -1 to disable.
21
+ # @param [Integer] array_limit The maximum length for arrays. Set to -1 to disable.
22
+ # @param [Hash] max_depth The maximum recursive depth. Set to -1 to disable.
23
+ #
24
+ # @return [Hash,Array] The truncated payload
25
+ #
26
+ def truncate(payload, **kwargs)
27
+ depth = kwargs[:depth].to_i
28
+ max_depth = kwargs[:max_depth] || 3
29
+ string_limit = kwargs[:string_limit] || 64
30
+ array_limit = kwargs[:array_limit] || 10
31
+
32
+ case payload
33
+ when Array
34
+ if max_depth > -1 && depth > max_depth
35
+ ["...#{payload.size} items..."]
36
+ elsif array_limit > -1
37
+ payload.take(array_limit).map { |e| truncate(e, **kwargs, depth: depth + 1) } +
38
+ (payload.size > array_limit ? ["...#{payload.size - array_limit} items..."] : [])
39
+ else
40
+ payload.map { |e| truncate(e, **kwargs, depth: depth + 1) }
41
+ end
42
+ when Hash
43
+ if max_depth > -1 && depth > max_depth
44
+ '{hash}'
45
+ else
46
+ payload.transform_values { |e| truncate(e, **kwargs, depth: depth + 1) }
47
+ end
48
+ when String
49
+ if string_limit > -1 && payload.size > string_limit
50
+ payload.truncate(string_limit)
51
+ else
52
+ payload
53
+ end
54
+ else
55
+ payload
56
+ end
57
+ end
58
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
59
+ end
60
+
61
+ # Only log the job meta information by default (exclude arguments)
62
+ DEFAULT_CONTEXT_PROCESSOR = ->(worker) { worker.to_h.slice(:worker, :job_id, :job_meta, :job_queue, :task_id) }
63
+
64
+ #
65
+ # Build a new instance of the class.
66
+ #
67
+ # @param [Cloudtasker::Worker] worker The worker.
68
+ #
69
+ def initialize(worker)
70
+ @worker = worker
71
+ end
72
+
73
+ #
74
+ # Return the Proc responsible for formatting the log payload.
75
+ #
76
+ # @return [Proc] The context processor.
77
+ #
78
+ def context_processor
79
+ @context_processor ||= worker.class.cloudtasker_options_hash[:log_context_processor] ||
80
+ self.class.log_context_processor ||
81
+ DEFAULT_CONTEXT_PROCESSOR
82
+ end
83
+
84
+ #
85
+ # The block to pass to log messages.
86
+ #
87
+ # @return [Proc] The log block.
88
+ #
89
+ def log_block
90
+ @log_block ||= proc { context_processor.call(worker) }
91
+ end
92
+
93
+ #
94
+ # Return the Cloudtasker logger.
95
+ #
96
+ # @return [Logger, any] The cloudtasker logger.
97
+ #
98
+ def logger
99
+ Cloudtasker.logger
100
+ end
101
+
102
+ #
103
+ # Format the log message as string.
104
+ #
105
+ # @param [Object] msg The log message or object.
106
+ #
107
+ # @return [String] The formatted message
108
+ #
109
+ def formatted_message_as_string(msg)
110
+ # Format message
111
+ msg_content = if msg.is_a?(Exception)
112
+ [msg.inspect, msg.backtrace].flatten(1).join("\n")
113
+ elsif msg.is_a?(String)
114
+ msg
115
+ else
116
+ msg.inspect
117
+ end
118
+
119
+ "[Cloudtasker][#{worker.class}][#{worker.job_id}] #{msg_content}"
120
+ end
121
+
122
+ #
123
+ # Format main log message.
124
+ #
125
+ # @param [String] msg The message to log.
126
+ #
127
+ # @return [String] The formatted log message
128
+ #
129
+ def formatted_message(msg)
130
+ if msg.is_a?(String)
131
+ formatted_message_as_string(msg)
132
+ else
133
+ # Delegate object formatting to logger
134
+ msg
135
+ end
136
+ end
137
+
138
+ #
139
+ # Log an info message.
140
+ #
141
+ # @param [String] msg The message to log.
142
+ # @param [Proc] &block Optional context block.
143
+ #
144
+ def info(msg, &block)
145
+ log_message(:info, msg, &block)
146
+ end
147
+
148
+ #
149
+ # Log an error message.
150
+ #
151
+ # @param [String] msg The message to log.
152
+ # @param [Proc] &block Optional context block.
153
+ #
154
+ def error(msg, &block)
155
+ log_message(:error, msg, &block)
156
+ end
157
+
158
+ #
159
+ # Log an fatal message.
160
+ #
161
+ # @param [String] msg The message to log.
162
+ # @param [Proc] &block Optional context block.
163
+ #
164
+ def fatal(msg, &block)
165
+ log_message(:fatal, msg, &block)
166
+ end
167
+
168
+ #
169
+ # Log an debut message.
170
+ #
171
+ # @param [String] msg The message to log.
172
+ # @param [Proc] &block Optional context block.
173
+ #
174
+ def debug(msg, &block)
175
+ log_message(:debug, msg, &block)
176
+ end
177
+
178
+ #
179
+ # Delegate all methods to the underlying logger.
180
+ #
181
+ # @param [String, Symbol] name The method to delegate.
182
+ # @param [Array<any>] *args The list of method arguments.
183
+ # @param [Proc] &block Block passed to the method.
184
+ #
185
+ # @return [Any] The method return value
186
+ #
187
+ def method_missing(name, ...)
188
+ if logger.respond_to?(name)
189
+ logger.send(name, ...)
190
+ else
191
+ super
192
+ end
193
+ end
194
+
195
+ #
196
+ # Check if the class respond to a certain method.
197
+ #
198
+ # @param [String, Symbol] name The name of the method.
199
+ # @param [Boolean] include_private Whether to check private methods or not. Default to false.
200
+ #
201
+ # @return [Boolean] Return true if the class respond to this method.
202
+ #
203
+ def respond_to_missing?(name, include_private = false)
204
+ logger.respond_to?(name) || super
205
+ end
206
+
207
+ private
208
+
209
+ #
210
+ # Log a message for the provided log level.
211
+ #
212
+ # @param [String, Symbol] level The log level
213
+ # @param [String] msg The message to log.
214
+ # @param [Proc] &block Optional context block.
215
+ #
216
+ def log_message(level, msg, &block)
217
+ # Merge log-specific context into worker-specific context
218
+ payload_block = ->(*_args) { log_block.call.merge(block&.call || {}) }
219
+
220
+ # ActiveSupport::Logger does not support passing a payload through a block on top
221
+ # of a message.
222
+ if defined?(ActiveSupport::Logger) && logger.is_a?(ActiveSupport::Logger)
223
+ # The logger is fairly basic in terms of formatting. All inputs get converted
224
+ # as regular strings.
225
+ logger.send(level) { "#{formatted_message_as_string(msg)} -- #{payload_block.call}" }
226
+ else
227
+ logger.send(level, formatted_message(msg), &payload_block)
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cloudtasker/worker'
4
+
5
+ module Cloudtasker
6
+ # A worker class used to schedule jobs without actually
7
+ # instantiating the worker class. This is useful for middlewares
8
+ # needing to enqueue jobs in a Rails initializer. Rails 6 complains
9
+ # about instantiating workers in an iniitializer because of autoloading
10
+ # in zeitwerk mode.
11
+ #
12
+ # Downside of this wrapper: any cloudtasker_options specified on on the
13
+ # worker_class will be ignored.
14
+ #
15
+ # See: https://github.com/rails/rails/issues/36363
16
+ #
17
+ class WorkerWrapper
18
+ include Worker
19
+
20
+ attr_accessor :worker_name
21
+
22
+ #
23
+ # Build a new instance of the class.
24
+ #
25
+ # @param [String] worker_class The name of the worker class.
26
+ # @param [Hash] **opts The worker arguments.
27
+ #
28
+ def initialize(worker_name:, **opts)
29
+ @worker_name = worker_name
30
+ super(**opts)
31
+ end
32
+
33
+ #
34
+ # Override parent. Return the underlying worker class name.
35
+ #
36
+ # @return [String] The worker class.
37
+ #
38
+ def job_class_name
39
+ worker_name
40
+ end
41
+
42
+ #
43
+ # Return a new instance of the worker with the same args and metadata
44
+ # but with a different id.
45
+ #
46
+ # @return [Cloudtasker::WorkerWrapper] <description>
47
+ #
48
+ def new_instance
49
+ self.class.new(worker_name: worker_name, job_queue: job_queue, job_args: job_args, job_meta: job_meta)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/object/try'
5
+ require 'active_support/core_ext/string/inflections'
6
+ require 'active_support/core_ext/string/filters'
7
+ require 'active_support/security_utils'
8
+
9
+ require 'cloudtasker/version'
10
+ require 'cloudtasker/config'
11
+
12
+ require 'cloudtasker/authentication_error'
13
+ require 'cloudtasker/dead_worker_error'
14
+ require 'cloudtasker/retry_worker_error'
15
+ require 'cloudtasker/invalid_worker_error'
16
+ require 'cloudtasker/missing_worker_arguments_error'
17
+ require 'cloudtasker/max_task_size_exceeded_error'
18
+
19
+ require 'cloudtasker/middleware/chain'
20
+ require 'cloudtasker/authenticator'
21
+ require 'cloudtasker/cloud_task'
22
+ require 'cloudtasker/worker_logger'
23
+ require 'cloudtasker/worker_handler'
24
+ require 'cloudtasker/meta_store'
25
+ require 'cloudtasker/worker'
26
+
27
+ # Define and manage Cloud Task based workers
28
+ module Cloudtasker
29
+ attr_writer :config
30
+
31
+ #
32
+ # Cloudtasker configurator.
33
+ #
34
+ def self.configure
35
+ yield(config)
36
+ end
37
+
38
+ #
39
+ # Return the Cloudtasker configuration.
40
+ #
41
+ # @return [Cloudtasker::Config] The Cloudtasker configuration.
42
+ #
43
+ def self.config
44
+ @config ||= Config.new
45
+ end
46
+
47
+ #
48
+ # Return the Cloudtasker logger.
49
+ #
50
+ # @return [Logger] The Cloudtasker logger.
51
+ #
52
+ def self.logger
53
+ config.logger
54
+ end
55
+ end
56
+
57
+ require 'cloudtasker/engine' if defined?(Rails::Engine)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cloudtasker/config'
4
+ require 'cloudtasker/cloud_task'
5
+
6
+ ENV['GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS'] ||= 'true'
7
+
8
+ namespace :cloudtasker do
9
+ desc 'Setup a Cloud Task queue. (default options: ' \
10
+ "name=#{Cloudtasker::Config::DEFAULT_JOB_QUEUE}, " \
11
+ "concurrency=#{Cloudtasker::Config::DEFAULT_QUEUE_CONCURRENCY}, " \
12
+ "retries=#{Cloudtasker::Config::DEFAULT_QUEUE_RETRIES})"
13
+ task setup_queue: :environment do
14
+ puts Cloudtasker::CloudTask.setup_production_queue(
15
+ name: ENV.fetch('name', nil),
16
+ concurrency: ENV.fetch('concurrency', nil),
17
+ retries: ENV.fetch('retries', nil)
18
+ )
19
+ end
20
+ end