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.
- checksums.yaml +7 -0
- data/.github/workflows/lint_rubocop.yml +15 -0
- data/.github/workflows/test_ruby_3.x.yml +40 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.rubocop.yml +96 -0
- data/Appraisals +76 -0
- data/CHANGELOG.md +248 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +1311 -0
- data/Rakefile +8 -0
- data/_config.yml +1 -0
- data/app/controllers/cloudtasker/worker_controller.rb +107 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/cloudtasker.gemspec +42 -0
- data/config/routes.rb +5 -0
- data/docs/BATCH_JOBS.md +144 -0
- data/docs/CRON_JOBS.md +129 -0
- data/docs/STORABLE_JOBS.md +68 -0
- data/docs/UNIQUE_JOBS.md +190 -0
- data/exe/cloudtasker +30 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/google_cloud_tasks_1.0.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_1.1.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_1.2.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_1.3.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_1.4.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_1.5.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_2.0.gemfile +17 -0
- data/gemfiles/google_cloud_tasks_2.1.gemfile +17 -0
- data/gemfiles/rails_6.1.gemfile +20 -0
- data/gemfiles/rails_7.0.gemfile +18 -0
- data/gemfiles/rails_7.1.gemfile +18 -0
- data/gemfiles/rails_8.0.gemfile +18 -0
- data/gemfiles/rails_8.1.gemfile +18 -0
- data/gemfiles/semantic_logger_3.4.gemfile +16 -0
- data/gemfiles/semantic_logger_4.6.gemfile +16 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile +16 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile +16 -0
- data/lib/active_job/queue_adapters/cloudtasker_adapter.rb +89 -0
- data/lib/cloudtasker/authentication_error.rb +6 -0
- data/lib/cloudtasker/authenticator.rb +90 -0
- data/lib/cloudtasker/backend/google_cloud_task_v1.rb +228 -0
- data/lib/cloudtasker/backend/google_cloud_task_v2.rb +231 -0
- data/lib/cloudtasker/backend/memory_task.rb +202 -0
- data/lib/cloudtasker/backend/redis_task.rb +291 -0
- data/lib/cloudtasker/batch/batch_progress.rb +142 -0
- data/lib/cloudtasker/batch/extension/worker.rb +13 -0
- data/lib/cloudtasker/batch/job.rb +558 -0
- data/lib/cloudtasker/batch/middleware/server.rb +14 -0
- data/lib/cloudtasker/batch/middleware.rb +25 -0
- data/lib/cloudtasker/batch.rb +5 -0
- data/lib/cloudtasker/cli.rb +194 -0
- data/lib/cloudtasker/cloud_task.rb +130 -0
- data/lib/cloudtasker/config.rb +319 -0
- data/lib/cloudtasker/cron/job.rb +205 -0
- data/lib/cloudtasker/cron/middleware/server.rb +14 -0
- data/lib/cloudtasker/cron/middleware.rb +20 -0
- data/lib/cloudtasker/cron/schedule.rb +308 -0
- data/lib/cloudtasker/cron.rb +5 -0
- data/lib/cloudtasker/dead_worker_error.rb +6 -0
- data/lib/cloudtasker/engine.rb +24 -0
- data/lib/cloudtasker/invalid_worker_error.rb +6 -0
- data/lib/cloudtasker/local_server.rb +99 -0
- data/lib/cloudtasker/max_task_size_exceeded_error.rb +14 -0
- data/lib/cloudtasker/meta_store.rb +86 -0
- data/lib/cloudtasker/middleware/chain.rb +250 -0
- data/lib/cloudtasker/missing_worker_arguments_error.rb +6 -0
- data/lib/cloudtasker/redis_client.rb +166 -0
- data/lib/cloudtasker/retry_worker_error.rb +6 -0
- data/lib/cloudtasker/storable/worker.rb +78 -0
- data/lib/cloudtasker/storable.rb +3 -0
- data/lib/cloudtasker/testing.rb +184 -0
- data/lib/cloudtasker/unique_job/conflict_strategy/base_strategy.rb +39 -0
- data/lib/cloudtasker/unique_job/conflict_strategy/raise.rb +28 -0
- data/lib/cloudtasker/unique_job/conflict_strategy/reject.rb +11 -0
- data/lib/cloudtasker/unique_job/conflict_strategy/reschedule.rb +30 -0
- data/lib/cloudtasker/unique_job/job.rb +168 -0
- data/lib/cloudtasker/unique_job/lock/base_lock.rb +70 -0
- data/lib/cloudtasker/unique_job/lock/no_op.rb +11 -0
- data/lib/cloudtasker/unique_job/lock/until_completed.rb +40 -0
- data/lib/cloudtasker/unique_job/lock/until_executed.rb +36 -0
- data/lib/cloudtasker/unique_job/lock/until_executing.rb +30 -0
- data/lib/cloudtasker/unique_job/lock/while_executing.rb +25 -0
- data/lib/cloudtasker/unique_job/lock_error.rb +8 -0
- data/lib/cloudtasker/unique_job/middleware/client.rb +15 -0
- data/lib/cloudtasker/unique_job/middleware/server.rb +14 -0
- data/lib/cloudtasker/unique_job/middleware.rb +36 -0
- data/lib/cloudtasker/unique_job.rb +32 -0
- data/lib/cloudtasker/version.rb +5 -0
- data/lib/cloudtasker/worker.rb +487 -0
- data/lib/cloudtasker/worker_handler.rb +250 -0
- data/lib/cloudtasker/worker_logger.rb +231 -0
- data/lib/cloudtasker/worker_wrapper.rb +52 -0
- data/lib/cloudtasker.rb +57 -0
- data/lib/tasks/setup_queue.rake +20 -0
- metadata +241 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cloudtasker'
|
|
4
|
+
require 'cloudtasker/local_server'
|
|
5
|
+
|
|
6
|
+
module Cloudtasker
|
|
7
|
+
# Cloudtasker executable logic
|
|
8
|
+
module CLI
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Return the current environment.
|
|
13
|
+
#
|
|
14
|
+
# @return [String] The environment name.
|
|
15
|
+
#
|
|
16
|
+
def environment
|
|
17
|
+
Cloudtasker.config.environment
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Return true if we are running in Rails.
|
|
22
|
+
#
|
|
23
|
+
# @return [Boolean] True if rails is loaded.
|
|
24
|
+
#
|
|
25
|
+
def rails_app?
|
|
26
|
+
defined?(::Rails)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Return true if we are running in JRuby.
|
|
31
|
+
#
|
|
32
|
+
# @return [Boolean] True if JRuby is loaded.
|
|
33
|
+
#
|
|
34
|
+
def jruby?
|
|
35
|
+
defined?(::JRUBY_VERSION)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Return the Cloudtasker logger
|
|
40
|
+
#
|
|
41
|
+
# @return [Logger] The Cloudtasker logger.
|
|
42
|
+
#
|
|
43
|
+
def logger
|
|
44
|
+
Cloudtasker.logger
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# Return the local Cloudtasker server.
|
|
49
|
+
#
|
|
50
|
+
# @return [Cloudtasker::LocalServer] The local Cloudtasker server.
|
|
51
|
+
#
|
|
52
|
+
def local_server
|
|
53
|
+
@local_server ||= LocalServer.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Load Rails if defined
|
|
58
|
+
#
|
|
59
|
+
def boot_system
|
|
60
|
+
# Sync logs
|
|
61
|
+
$stdout.sync = true
|
|
62
|
+
|
|
63
|
+
# Check for Rails
|
|
64
|
+
return false unless File.exist?('./config/environment.rb')
|
|
65
|
+
|
|
66
|
+
require 'rails'
|
|
67
|
+
require 'cloudtasker/engine'
|
|
68
|
+
require File.expand_path('./config/environment.rb')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# Run the cloudtasker development server.
|
|
73
|
+
#
|
|
74
|
+
def run(opts = {})
|
|
75
|
+
boot_system
|
|
76
|
+
|
|
77
|
+
# Print banner
|
|
78
|
+
environment == 'development' ? print_banner : print_non_dev_warning
|
|
79
|
+
|
|
80
|
+
# Print rails info
|
|
81
|
+
if rails_app?
|
|
82
|
+
logger.info "[Cloudtasker/Server] Booted Rails #{::Rails.version} application in #{environment} environment"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get internal read/write pipe
|
|
86
|
+
self_read, self_write = IO.pipe
|
|
87
|
+
|
|
88
|
+
# Setup signals to trap
|
|
89
|
+
setup_signals(self_write)
|
|
90
|
+
|
|
91
|
+
logger.info "[Cloudtasker/Server] Running in #{RUBY_DESCRIPTION}"
|
|
92
|
+
|
|
93
|
+
# Wait for signals
|
|
94
|
+
run_server(self_read, opts)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#
|
|
98
|
+
# Run server and wait for signals.
|
|
99
|
+
#
|
|
100
|
+
# @param [IO] read_pipe Where to read signals.
|
|
101
|
+
# @param [Hash] opts Server options.
|
|
102
|
+
#
|
|
103
|
+
def run_server(read_pipe, opts = {})
|
|
104
|
+
local_server.start(opts)
|
|
105
|
+
|
|
106
|
+
while (readable_io = read_pipe.wait_readable)
|
|
107
|
+
signal = readable_io.first.chomp
|
|
108
|
+
handle_signal(signal)
|
|
109
|
+
end
|
|
110
|
+
rescue Interrupt
|
|
111
|
+
logger.info 'Shutting down'
|
|
112
|
+
local_server.stop
|
|
113
|
+
logger.info 'Stopped'
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
#
|
|
117
|
+
# Define which signals to trap
|
|
118
|
+
#
|
|
119
|
+
# @param [IO] write_pipe Where to write signals.
|
|
120
|
+
#
|
|
121
|
+
def setup_signals(write_pipe)
|
|
122
|
+
# Display signals on log output
|
|
123
|
+
sigs = %w[INT TERM TTIN TSTP]
|
|
124
|
+
# USR1 and USR2 don't work on the JVM
|
|
125
|
+
sigs << 'USR2' unless jruby?
|
|
126
|
+
sigs.each do |sig|
|
|
127
|
+
trap(sig) { write_pipe.puts(sig) }
|
|
128
|
+
rescue ArgumentError
|
|
129
|
+
puts "Signal #{sig} not supported"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Handle process signals
|
|
135
|
+
#
|
|
136
|
+
# @param [String] sig The signal.
|
|
137
|
+
#
|
|
138
|
+
def handle_signal(sig)
|
|
139
|
+
raise(Interrupt) if %w[INT TERM].include?(sig)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
#
|
|
143
|
+
# Return the server banner
|
|
144
|
+
#
|
|
145
|
+
# @return [String] The server banner
|
|
146
|
+
#
|
|
147
|
+
def banner
|
|
148
|
+
<<~'TEXT'
|
|
149
|
+
___ _ _ _ _
|
|
150
|
+
/ __\ | ___ _ _ __| | |_ __ _ ___| | _____ _ __
|
|
151
|
+
/ / | |/ _ \| | | |/ _` | __/ _` / __| |/ / _ \ '__|
|
|
152
|
+
/ /___| | (_) | |_| | (_| | || (_| \__ \ < __/ |
|
|
153
|
+
\____/|_|\___/ \__,_|\__,_|\__\__,_|___/_|\_\___|_|
|
|
154
|
+
|
|
155
|
+
TEXT
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Display a warning message when run in non-dev env.
|
|
160
|
+
#
|
|
161
|
+
# @return [<Type>] <description>
|
|
162
|
+
#
|
|
163
|
+
def print_non_dev_warning
|
|
164
|
+
puts "\e[31m"
|
|
165
|
+
puts non_dev_warning_message
|
|
166
|
+
puts "\e[0m"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
#
|
|
170
|
+
# Return the message to display when users attempt to run
|
|
171
|
+
# the local development server in non-dev environments.
|
|
172
|
+
#
|
|
173
|
+
# @return [String] The warning message.
|
|
174
|
+
#
|
|
175
|
+
def non_dev_warning_message
|
|
176
|
+
<<~'TEXT'
|
|
177
|
+
============================================ /!\ ====================================================
|
|
178
|
+
Your are running the Cloudtasker local development server in a NON-DEVELOPMENT environment.
|
|
179
|
+
This is not recommended as the the development server is not designed for production-like load.
|
|
180
|
+
If you need a job processing server to run yourself please use Sidekiq instead (https://sidekiq.org)
|
|
181
|
+
============================================ /!\ ====================================================
|
|
182
|
+
TEXT
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
#
|
|
186
|
+
# Print the server banner
|
|
187
|
+
#
|
|
188
|
+
def print_banner
|
|
189
|
+
puts "\e[96m"
|
|
190
|
+
puts banner
|
|
191
|
+
puts "\e[0m"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudtasker
|
|
4
|
+
# An interface class to manage tasks on the backend (Cloud Task or Redis)
|
|
5
|
+
class CloudTask
|
|
6
|
+
attr_accessor :id, :http_request, :schedule_time, :retries, :queue, :dispatch_deadline
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# The backend to use for cloud tasks.
|
|
10
|
+
#
|
|
11
|
+
# @return [
|
|
12
|
+
# Backend::MemoryTask,
|
|
13
|
+
# Cloudtasker::Backend::GoogleCloudTaskV1,
|
|
14
|
+
# Cloudtasker::Backend::GoogleCloudTaskV2,
|
|
15
|
+
# Cloudtasker::Backend::RedisTask
|
|
16
|
+
# ] The cloud task backend.
|
|
17
|
+
#
|
|
18
|
+
def self.backend
|
|
19
|
+
# Re-evaluate backend every time if testing mode enabled
|
|
20
|
+
@backend = nil if defined?(Cloudtasker::Testing)
|
|
21
|
+
|
|
22
|
+
@backend ||= if defined?(Cloudtasker::Testing) && Cloudtasker::Testing.in_memory?
|
|
23
|
+
require 'cloudtasker/backend/memory_task'
|
|
24
|
+
Backend::MemoryTask
|
|
25
|
+
elsif Cloudtasker.config.mode.to_sym == :development
|
|
26
|
+
require 'cloudtasker/backend/redis_task'
|
|
27
|
+
Backend::RedisTask
|
|
28
|
+
else
|
|
29
|
+
gct_backend
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Return the GoogleCloudTaskV* backend to use based on the version
|
|
35
|
+
# of the currently installed google-cloud-tasks gem
|
|
36
|
+
#
|
|
37
|
+
# @return [
|
|
38
|
+
# Cloudtasker::Backend::GoogleCloudTaskV1,
|
|
39
|
+
# Cloudtasker::Backend::GoogleCloudTaskV2
|
|
40
|
+
# ] The google cloud task backend.
|
|
41
|
+
#
|
|
42
|
+
def self.gct_backend
|
|
43
|
+
@gct_backend ||= if !defined?(Google::Cloud::Tasks::VERSION) || Google::Cloud::Tasks::VERSION < '2'
|
|
44
|
+
require 'cloudtasker/backend/google_cloud_task_v1'
|
|
45
|
+
Backend::GoogleCloudTaskV1
|
|
46
|
+
else
|
|
47
|
+
require 'cloudtasker/backend/google_cloud_task_v2'
|
|
48
|
+
Backend::GoogleCloudTaskV2
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# Create the google cloud task queue based on provided parameters if it does not exist already.
|
|
54
|
+
#
|
|
55
|
+
# @param [String] :name The queue name
|
|
56
|
+
# @param [Integer] :concurrency The queue concurrency
|
|
57
|
+
# @param [Integer] :retries The number of retries for the queue
|
|
58
|
+
#
|
|
59
|
+
# @return [Google::Cloud::Tasks::V2::Queue, Google::Cloud::Tasks::V2beta3::Queue] The queue
|
|
60
|
+
#
|
|
61
|
+
def self.setup_production_queue(**kwargs)
|
|
62
|
+
gct_backend.setup_queue(**kwargs)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Find a cloud task by id.
|
|
67
|
+
#
|
|
68
|
+
# @param [String] id The id of the task.
|
|
69
|
+
#
|
|
70
|
+
# @return [Cloudtasker::Cloudtask] The task.
|
|
71
|
+
#
|
|
72
|
+
def self.find(id)
|
|
73
|
+
payload = backend.find(id)&.to_h
|
|
74
|
+
payload ? new(**payload) : nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Create a new cloud task.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] payload Thee task payload
|
|
81
|
+
#
|
|
82
|
+
# @return [Cloudtasker::CloudTask] The created task.
|
|
83
|
+
#
|
|
84
|
+
def self.create(payload)
|
|
85
|
+
raise MaxTaskSizeExceededError if payload.to_json.bytesize > Config::MAX_TASK_SIZE
|
|
86
|
+
|
|
87
|
+
resp = backend.create(payload)&.to_h
|
|
88
|
+
resp ? new(**resp) : nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
#
|
|
92
|
+
# Delete a cloud task by id.
|
|
93
|
+
#
|
|
94
|
+
# @param [String] id The task id.
|
|
95
|
+
#
|
|
96
|
+
def self.delete(id)
|
|
97
|
+
backend.delete(id)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Build a new instance of the class using a backend response
|
|
102
|
+
# payload.
|
|
103
|
+
#
|
|
104
|
+
# @param [String] id The task id.
|
|
105
|
+
# @param [Hash] http_request The content of the http request.
|
|
106
|
+
# @param [Integer] schedule_time When to run the job (Unix timestamp)
|
|
107
|
+
# @param [Integer] retries The number of times the job failed.
|
|
108
|
+
# @param [String] queue The queue the task is in.
|
|
109
|
+
#
|
|
110
|
+
def initialize(id:, http_request:, schedule_time: nil, retries: 0, queue: nil, dispatch_deadline: nil)
|
|
111
|
+
@id = id
|
|
112
|
+
@http_request = http_request
|
|
113
|
+
@schedule_time = schedule_time
|
|
114
|
+
@retries = retries || 0
|
|
115
|
+
@queue = queue
|
|
116
|
+
@dispatch_deadline = dispatch_deadline
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# Equality operator.
|
|
121
|
+
#
|
|
122
|
+
# @param [Any] other The object to compare.
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean] True if the object is equal.
|
|
125
|
+
#
|
|
126
|
+
def ==(other)
|
|
127
|
+
other.is_a?(self.class) && other.id == id
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
module Cloudtasker
|
|
6
|
+
# Holds cloudtasker configuration. See Cloudtasker#configure
|
|
7
|
+
class Config
|
|
8
|
+
attr_accessor :redis, :store_payloads_in_redis, :gcp_queue_prefix
|
|
9
|
+
attr_writer :secret, :gcp_location_id, :gcp_project_id,
|
|
10
|
+
:processor_path, :logger, :mode, :max_retries,
|
|
11
|
+
:dispatch_deadline, :on_error, :on_dead, :oidc, :local_server_ssl_verify,
|
|
12
|
+
:base64_encode_body
|
|
13
|
+
|
|
14
|
+
# Max Cloud Task size in bytes
|
|
15
|
+
MAX_TASK_SIZE = 100 * 1024 # 100 KB
|
|
16
|
+
|
|
17
|
+
# Retry header in Cloud Task responses
|
|
18
|
+
#
|
|
19
|
+
# Definitions:
|
|
20
|
+
# X-CloudTasks-TaskRetryCount: total number of retries (including 504 "instance unreachable")
|
|
21
|
+
# X-CloudTasks-TaskExecutionCount: number of non-503 retries (= actual number of job failures)
|
|
22
|
+
#
|
|
23
|
+
RETRY_HEADER = 'X-Cloudtasks-Taskexecutioncount'
|
|
24
|
+
|
|
25
|
+
# Cloud Task ID header
|
|
26
|
+
TASK_ID_HEADER = 'X-CloudTasks-TaskName'
|
|
27
|
+
|
|
28
|
+
# Content-Transfer-Encoding header in Cloud Task responses
|
|
29
|
+
ENCODING_HEADER = 'Content-Transfer-Encoding'
|
|
30
|
+
|
|
31
|
+
# Content Type
|
|
32
|
+
CONTENT_TYPE_HEADER = 'Content-Type'
|
|
33
|
+
|
|
34
|
+
# OIDC Authorization header
|
|
35
|
+
OIDC_AUTHORIZATION_HEADER = 'Authorization'
|
|
36
|
+
|
|
37
|
+
# Custom authentication header that does not conflict with
|
|
38
|
+
# OIDC authorization header
|
|
39
|
+
CT_AUTHORIZATION_HEADER = 'X-Cloudtasker-Authorization'
|
|
40
|
+
CT_SIGNATURE_HEADER = 'X-Cloudtasker-Signature'
|
|
41
|
+
|
|
42
|
+
# Default values
|
|
43
|
+
DEFAULT_LOCATION_ID = 'us-east1'
|
|
44
|
+
DEFAULT_PROCESSOR_PATH = '/cloudtasker/run'
|
|
45
|
+
DEFAULT_LOCAL_SERVER_SSL_VERIFY_MODE = true
|
|
46
|
+
|
|
47
|
+
# Default queue values
|
|
48
|
+
DEFAULT_JOB_QUEUE = 'default'
|
|
49
|
+
DEFAULT_QUEUE_CONCURRENCY = 10
|
|
50
|
+
DEFAULT_QUEUE_RETRIES = -1 # unlimited
|
|
51
|
+
|
|
52
|
+
# Job timeout configuration for Cloud Tasks
|
|
53
|
+
DEFAULT_DISPATCH_DEADLINE = 10 * 60 # 10 minutes
|
|
54
|
+
MIN_DISPATCH_DEADLINE = 15 # seconds
|
|
55
|
+
MAX_DISPATCH_DEADLINE = 30 * 60 # 30 minutes
|
|
56
|
+
|
|
57
|
+
# Default on_error Proc
|
|
58
|
+
DEFAULT_ON_ERROR = ->(error, worker) {}
|
|
59
|
+
|
|
60
|
+
# Default base64 encoding flag
|
|
61
|
+
DEFAULT_BASE64_ENCODE_BODY = true
|
|
62
|
+
|
|
63
|
+
# Cache key prefix used to store workers in cache and retrieve
|
|
64
|
+
# them later.
|
|
65
|
+
WORKER_STORE_PREFIX = 'worker_store'
|
|
66
|
+
|
|
67
|
+
# The number of times jobs will be attempted before declaring them dead.
|
|
68
|
+
#
|
|
69
|
+
# With the default retry configuration (maxDoublings = 16 and minBackoff = 0.100s)
|
|
70
|
+
# it means that jobs will be declared dead after 20h of consecutive failing.
|
|
71
|
+
#
|
|
72
|
+
# Note that this configuration parameter is internal to Cloudtasker and does not
|
|
73
|
+
# affect the Cloud Task queue configuration. The number of retries configured
|
|
74
|
+
# on the Cloud Task queue should be higher than the number below to also cover
|
|
75
|
+
# failures due to the instance being unreachable.
|
|
76
|
+
DEFAULT_MAX_RETRY_ATTEMPTS = 25
|
|
77
|
+
|
|
78
|
+
# How long to wait between retries in local server mode
|
|
79
|
+
LOCAL_SERVER_RETRY_DELAY = (ENV['CLOUDTASKER_LOCAL_RETRY_DELAY'] || 20).to_i # seconds
|
|
80
|
+
|
|
81
|
+
PROCESSOR_HOST_MISSING = <<~DOC
|
|
82
|
+
Missing host for processing.
|
|
83
|
+
Please specify a processor hostname in form of `https://some-public-dns.example.com`'
|
|
84
|
+
DOC
|
|
85
|
+
PROJECT_ID_MISSING_ERROR = <<~DOC
|
|
86
|
+
Missing GCP project ID.
|
|
87
|
+
Please specify a project ID in the cloudtasker configurator.
|
|
88
|
+
DOC
|
|
89
|
+
SECRET_MISSING_ERROR = <<~DOC
|
|
90
|
+
Missing cloudtasker secret.
|
|
91
|
+
Please specify a secret in the cloudtasker initializer or add Rails secret_key_base in your credentials
|
|
92
|
+
DOC
|
|
93
|
+
OIDC_EMAIL_MISSING_ERROR = <<~DOC
|
|
94
|
+
Missing OpenID Connect (OIDC) service account email.
|
|
95
|
+
You specified an OIDC configuration hash but the :service_account_email property is missing.
|
|
96
|
+
DOC
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Return the threshold above which job arguments must be stored
|
|
100
|
+
# in Redis instead of being sent to the backend as part of the job
|
|
101
|
+
# payload.
|
|
102
|
+
#
|
|
103
|
+
# Return nil if redis payload storage is disabled.
|
|
104
|
+
#
|
|
105
|
+
# @return [Integer, nil] The threshold above which payloads will be stored in Redis.
|
|
106
|
+
#
|
|
107
|
+
def redis_payload_storage_threshold
|
|
108
|
+
return nil unless store_payloads_in_redis
|
|
109
|
+
|
|
110
|
+
store_payloads_in_redis.respond_to?(:to_i) ? store_payloads_in_redis.to_i : 0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#
|
|
114
|
+
# The number of times jobs will be retried. This number of
|
|
115
|
+
# retries does not include failures due to the application being unreachable.
|
|
116
|
+
#
|
|
117
|
+
#
|
|
118
|
+
# @return [Integer] The number of retries
|
|
119
|
+
#
|
|
120
|
+
def max_retries
|
|
121
|
+
@max_retries ||= DEFAULT_MAX_RETRY_ATTEMPTS
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
# The operating mode.
|
|
126
|
+
# - :production => process tasks via GCP Cloud Task.
|
|
127
|
+
# - :development => process tasks locally via Redis.
|
|
128
|
+
#
|
|
129
|
+
# @return [<Type>] <description>
|
|
130
|
+
#
|
|
131
|
+
def mode
|
|
132
|
+
@mode ||= environment == 'development' ? :development : :production
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
# Return the current environment.
|
|
137
|
+
#
|
|
138
|
+
# @return [String] The environment name.
|
|
139
|
+
#
|
|
140
|
+
def environment
|
|
141
|
+
ENV['CLOUDTASKER_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#
|
|
145
|
+
# Return the Cloudtasker logger.
|
|
146
|
+
#
|
|
147
|
+
# @return [Logger, any] The cloudtasker logger.
|
|
148
|
+
#
|
|
149
|
+
def logger
|
|
150
|
+
@logger ||= defined?(Rails) ? Rails.logger : ::Logger.new($stdout)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# Return the full URL of the processor. Worker payloads will be sent
|
|
155
|
+
# to this URL.
|
|
156
|
+
#
|
|
157
|
+
# @return [String] The processor URL.
|
|
158
|
+
#
|
|
159
|
+
def processor_url
|
|
160
|
+
File.join(processor_host, processor_path)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
#
|
|
164
|
+
# Set the processor host. In the context of Rails the host will
|
|
165
|
+
# also be added to the list of authorized Rails hosts.
|
|
166
|
+
#
|
|
167
|
+
# @param [String] val The processor host to set.
|
|
168
|
+
#
|
|
169
|
+
def processor_host=(val)
|
|
170
|
+
@processor_host = val
|
|
171
|
+
|
|
172
|
+
# Check if Rails supports host filtering
|
|
173
|
+
return unless val &&
|
|
174
|
+
defined?(Rails) &&
|
|
175
|
+
Rails.application.config.respond_to?(:hosts) &&
|
|
176
|
+
Rails.application.config.hosts&.any?
|
|
177
|
+
|
|
178
|
+
# Add processor host to the list of authorized hosts
|
|
179
|
+
Rails.application.config.hosts << val.gsub(%r{https?://}, '')
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
#
|
|
183
|
+
# The hostname of the application processing the workers. The hostname must
|
|
184
|
+
# be reachable from Cloud Task.
|
|
185
|
+
#
|
|
186
|
+
# @return [String] The processor host.
|
|
187
|
+
#
|
|
188
|
+
def processor_host
|
|
189
|
+
@processor_host || raise(StandardError, PROCESSOR_HOST_MISSING)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
#
|
|
193
|
+
# The path on the host when worker payloads will be sent.
|
|
194
|
+
# Default to `/cloudtasker/run`
|
|
195
|
+
#
|
|
196
|
+
#
|
|
197
|
+
# @return [String] The processor path
|
|
198
|
+
#
|
|
199
|
+
def processor_path
|
|
200
|
+
@processor_path || DEFAULT_PROCESSOR_PATH
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# Return the GCP project ID.
|
|
205
|
+
#
|
|
206
|
+
# @return [String] The ID of the project for which tasks will be processed.
|
|
207
|
+
#
|
|
208
|
+
def gcp_project_id
|
|
209
|
+
@gcp_project_id || raise(StandardError, PROJECT_ID_MISSING_ERROR)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
#
|
|
213
|
+
# Return the GCP location ID. Default to 'us-east1'
|
|
214
|
+
#
|
|
215
|
+
# @return [String] The location ID where tasks will be processed.
|
|
216
|
+
#
|
|
217
|
+
def gcp_location_id
|
|
218
|
+
@gcp_location_id || DEFAULT_LOCATION_ID
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
#
|
|
222
|
+
# Return the Dispatch deadline duration. Cloud Tasks will timeout the job after
|
|
223
|
+
# this duration is elapsed.
|
|
224
|
+
#
|
|
225
|
+
# @return [Integer] The value in seconds.
|
|
226
|
+
#
|
|
227
|
+
def dispatch_deadline
|
|
228
|
+
@dispatch_deadline || DEFAULT_DISPATCH_DEADLINE
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
#
|
|
232
|
+
# Return the secret to use to sign the verification tokens
|
|
233
|
+
# attached to tasks.
|
|
234
|
+
#
|
|
235
|
+
# @return [String] The cloudtasker secret
|
|
236
|
+
#
|
|
237
|
+
def secret
|
|
238
|
+
@secret ||= (
|
|
239
|
+
defined?(Rails) && Rails.application.credentials&.dig(:secret_key_base)
|
|
240
|
+
) || raise(StandardError, SECRET_MISSING_ERROR)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
#
|
|
244
|
+
# Return a Proc invoked whenever a worker runtime error is raised.
|
|
245
|
+
# See Cloudtasker::WorkerHandler.with_worker_handling
|
|
246
|
+
#
|
|
247
|
+
# @return [Proc] A Proc handler
|
|
248
|
+
#
|
|
249
|
+
def on_error
|
|
250
|
+
@on_error || DEFAULT_ON_ERROR
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
#
|
|
254
|
+
# Return a Proc invoked whenever a worker DeadWorkerError is raised.
|
|
255
|
+
# See Cloudtasker::WorkerHandler.with_worker_handling
|
|
256
|
+
#
|
|
257
|
+
# @return [Proc] A Proc handler
|
|
258
|
+
#
|
|
259
|
+
def on_dead
|
|
260
|
+
@on_dead || DEFAULT_ON_ERROR
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
#
|
|
264
|
+
# Return the Open ID Connect configuration to use for tasks.
|
|
265
|
+
#
|
|
266
|
+
# @return [Hash] The OIDC configuration
|
|
267
|
+
#
|
|
268
|
+
def oidc
|
|
269
|
+
return unless @oidc
|
|
270
|
+
raise(StandardError, OIDC_EMAIL_MISSING_ERROR) unless @oidc[:service_account_email]
|
|
271
|
+
|
|
272
|
+
{
|
|
273
|
+
service_account_email: @oidc[:service_account_email],
|
|
274
|
+
audience: @oidc[:audience] || processor_host
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
#
|
|
279
|
+
# Return the chain of client middlewares.
|
|
280
|
+
#
|
|
281
|
+
# @return [Cloudtasker::Middleware::Chain] The chain of middlewares.
|
|
282
|
+
#
|
|
283
|
+
def client_middleware
|
|
284
|
+
@client_middleware ||= Middleware::Chain.new
|
|
285
|
+
yield @client_middleware if block_given?
|
|
286
|
+
@client_middleware
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
#
|
|
290
|
+
# Return the chain of server middlewares.
|
|
291
|
+
#
|
|
292
|
+
# @return [Cloudtasker::Middleware::Chain] The chain of middlewares.
|
|
293
|
+
#
|
|
294
|
+
def server_middleware
|
|
295
|
+
@server_middleware ||= Middleware::Chain.new
|
|
296
|
+
yield @server_middleware if block_given?
|
|
297
|
+
@server_middleware
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
#
|
|
301
|
+
# Return the ssl verify mode for the Cloudtasker local server.
|
|
302
|
+
#
|
|
303
|
+
# @return [Boolean] The ssl verify mode for the Cloudtasker local server.
|
|
304
|
+
#
|
|
305
|
+
def local_server_ssl_verify
|
|
306
|
+
@local_server_ssl_verify.nil? ? DEFAULT_LOCAL_SERVER_SSL_VERIFY_MODE : @local_server_ssl_verify
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
#
|
|
310
|
+
# Return whether to base64 encode the task body when sending to Cloud Tasks.
|
|
311
|
+
# Encoding is enabled by default to support UTF-8 content.
|
|
312
|
+
#
|
|
313
|
+
# @return [Boolean] Whether to base64 encode the body.
|
|
314
|
+
#
|
|
315
|
+
def base64_encode_body
|
|
316
|
+
@base64_encode_body.nil? ? DEFAULT_BASE64_ENCODE_BODY : @base64_encode_body
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|