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,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