cloudtasker 0.1.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +10 -1
- data/Appraisals +25 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile.lock +37 -4
- data/README.md +573 -6
- data/Rakefile +6 -0
- data/app/controllers/cloudtasker/application_controller.rb +2 -0
- data/app/controllers/cloudtasker/worker_controller.rb +24 -2
- data/cloudtasker.gemspec +7 -3
- data/docs/BATCH_JOBS.md +66 -0
- data/docs/CRON_JOBS.md +63 -0
- data/docs/UNIQUE_JOBS.md +127 -0
- data/exe/cloudtasker +15 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/google_cloud_tasks_1.0.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.0.gemfile.lock +263 -0
- data/gemfiles/google_cloud_tasks_1.1.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.1.gemfile.lock +263 -0
- data/gemfiles/google_cloud_tasks_1.2.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.2.gemfile.lock +263 -0
- data/gemfiles/google_cloud_tasks_1.3.gemfile +9 -0
- data/gemfiles/google_cloud_tasks_1.3.gemfile.lock +264 -0
- data/gemfiles/rails_4.0.gemfile +10 -0
- data/gemfiles/rails_4.1.gemfile +9 -0
- data/gemfiles/rails_4.2.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +9 -0
- data/gemfiles/rails_5.2.gemfile +9 -0
- data/gemfiles/rails_5.2.gemfile.lock +247 -0
- data/gemfiles/rails_6.0.gemfile +9 -0
- data/gemfiles/rails_6.0.gemfile.lock +263 -0
- data/lib/cloudtasker.rb +21 -1
- data/lib/cloudtasker/backend/google_cloud_task.rb +139 -0
- data/lib/cloudtasker/backend/memory_task.rb +190 -0
- data/lib/cloudtasker/backend/redis_task.rb +249 -0
- data/lib/cloudtasker/batch/batch_progress.rb +19 -1
- data/lib/cloudtasker/batch/job.rb +88 -23
- data/lib/cloudtasker/batch/middleware.rb +0 -1
- data/lib/cloudtasker/cli.rb +194 -0
- data/lib/cloudtasker/cloud_task.rb +91 -0
- data/lib/cloudtasker/config.rb +64 -2
- data/lib/cloudtasker/cron/job.rb +6 -3
- data/lib/cloudtasker/cron/middleware.rb +0 -1
- data/lib/cloudtasker/cron/schedule.rb +73 -13
- data/lib/cloudtasker/dead_worker_error.rb +6 -0
- data/lib/cloudtasker/local_server.rb +74 -0
- data/lib/cloudtasker/railtie.rb +10 -0
- data/lib/cloudtasker/redis_client.rb +24 -2
- data/lib/cloudtasker/testing.rb +133 -0
- data/lib/cloudtasker/unique_job/job.rb +5 -2
- data/lib/cloudtasker/unique_job/lock/base_lock.rb +1 -1
- data/lib/cloudtasker/unique_job/lock/until_executed.rb +3 -1
- data/lib/cloudtasker/unique_job/lock/while_executing.rb +3 -1
- data/lib/cloudtasker/unique_job/middleware.rb +0 -1
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +59 -16
- data/lib/cloudtasker/{task.rb → worker_handler.rb} +10 -77
- data/lib/cloudtasker/worker_logger.rb +155 -0
- data/lib/tasks/setup_queue.rake +10 -0
- metadata +98 -9
- data/lib/cloudtasker/batch/config.rb +0 -11
- data/lib/cloudtasker/cron/config.rb +0 -11
- data/lib/cloudtasker/unique_job/config.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8721e878a098657f0c3974670df6373c873d1df2fa1042b97d9b37d7a1118299
|
4
|
+
data.tar.gz: ffd5652b0220a833f603682fdb4392a850b13b185bd7516dd170f5314d1b4af6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0f98ff0e0062b28be192f8353363a736f5cb315eb14f86f577586de070127642d123617e8e831dea84271cada640ea3b9ec642ea75f3f9a12cd494432b9e178
|
7
|
+
data.tar.gz: a04500924970efdbc0ceb9364314e0426c92800a4c571b839432de5b9f2faa04e678a67186b0dfd2b32222e8d8606e39ce51b0922ba9687ea827271174dcb23d
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
---
|
2
|
-
sudo: false
|
3
2
|
language: ruby
|
4
3
|
cache: bundler
|
5
4
|
rvm:
|
5
|
+
- 2.3
|
6
|
+
- 2.4
|
6
7
|
- 2.5.5
|
7
8
|
before_install: gem install bundler -v 2.0.2
|
9
|
+
before_script: rubocop
|
10
|
+
gemfile:
|
11
|
+
- gemfiles/google_cloud_tasks_1.0.gemfile
|
12
|
+
- gemfiles/google_cloud_tasks_1.1.gemfile
|
13
|
+
- gemfiles/google_cloud_tasks_1.2.gemfile
|
14
|
+
- gemfiles/google_cloud_tasks_1.3.gemfile
|
15
|
+
- gemfiles/rails_5.2.gemfile
|
16
|
+
- gemfiles/rails_6.0.gemfile
|
data/Appraisals
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
appraise 'google-cloud-tasks-1.0' do
|
4
|
+
gem 'google-cloud-tasks', '1.0'
|
5
|
+
end
|
6
|
+
|
7
|
+
appraise 'google-cloud-tasks-1.1' do
|
8
|
+
gem 'google-cloud-tasks', '1.1'
|
9
|
+
end
|
10
|
+
|
11
|
+
appraise 'google-cloud-tasks-1.2' do
|
12
|
+
gem 'google-cloud-tasks', '1.2'
|
13
|
+
end
|
14
|
+
|
15
|
+
appraise 'google-cloud-tasks-1.3' do
|
16
|
+
gem 'google-cloud-tasks', '1.3'
|
17
|
+
end
|
18
|
+
|
19
|
+
appraise 'rails-5.2' do
|
20
|
+
gem 'rails', '5.2'
|
21
|
+
end
|
22
|
+
|
23
|
+
appraise 'rails-6.0' do
|
24
|
+
gem 'rails', '6.0'
|
25
|
+
end
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [v0.5.0](https://github.com/keypup-io/cloudtasker/tree/v0.5.0) (2019-11-25)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.4.0...v0.5.0)
|
6
|
+
|
7
|
+
## [v0.4.0](https://github.com/keypup-io/cloudtasker/tree/v0.4.0) (2019-11-25)
|
8
|
+
|
9
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.3.0...v0.4.0)
|
10
|
+
|
11
|
+
## [v0.3.0](https://github.com/keypup-io/cloudtasker/tree/v0.3.0) (2019-11-25)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.2.0...v0.3.0)
|
14
|
+
|
15
|
+
## [v0.2.0](https://github.com/keypup-io/cloudtasker/tree/v0.2.0) (2019-11-18)
|
16
|
+
|
17
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.1.0...v0.2.0)
|
18
|
+
|
19
|
+
## [v0.1.0](https://github.com/keypup-io/cloudtasker/tree/v0.1.0) (2019-11-17)
|
20
|
+
|
21
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/c137feb1ceaaaa4e2fecac0d1f0b4c73151ae002...v0.1.0)
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cloudtasker (0.
|
4
|
+
cloudtasker (0.6.0)
|
5
|
+
activesupport
|
5
6
|
fugit
|
6
7
|
google-cloud-tasks
|
7
8
|
jwt
|
@@ -67,9 +68,15 @@ GEM
|
|
67
68
|
zeitwerk (~> 2.1, >= 2.1.8)
|
68
69
|
addressable (2.7.0)
|
69
70
|
public_suffix (>= 2.0.2, < 5.0)
|
71
|
+
appraisal (2.2.0)
|
72
|
+
bundler
|
73
|
+
rake
|
74
|
+
thor (>= 0.14.0)
|
70
75
|
ast (2.4.0)
|
71
76
|
builder (3.2.3)
|
72
77
|
concurrent-ruby (1.1.5)
|
78
|
+
crack (0.4.3)
|
79
|
+
safe_yaml (~> 1.0.0)
|
73
80
|
crass (1.0.5)
|
74
81
|
diff-lcs (1.3)
|
75
82
|
erubi (1.9.0)
|
@@ -77,14 +84,25 @@ GEM
|
|
77
84
|
tzinfo
|
78
85
|
faraday (0.17.0)
|
79
86
|
multipart-post (>= 1.2, < 3)
|
87
|
+
faraday-http-cache (2.0.0)
|
88
|
+
faraday (~> 0.8)
|
80
89
|
fugit (1.3.3)
|
81
90
|
et-orbi (~> 1.1, >= 1.1.8)
|
82
91
|
raabro (~> 1.1)
|
92
|
+
github_changelog_generator (1.15.0)
|
93
|
+
activesupport
|
94
|
+
faraday-http-cache
|
95
|
+
multi_json
|
96
|
+
octokit (~> 4.6)
|
97
|
+
rainbow (>= 2.2.1)
|
98
|
+
rake (>= 10.0)
|
99
|
+
retriable (~> 3.0)
|
83
100
|
globalid (0.4.2)
|
84
101
|
activesupport (>= 4.2.0)
|
85
|
-
google-cloud-tasks (1.
|
102
|
+
google-cloud-tasks (1.3.1)
|
86
103
|
google-gax (~> 1.8)
|
87
104
|
googleapis-common-protos (>= 1.3.9, < 2.0)
|
105
|
+
googleapis-common-protos-types (>= 1.0.4, < 2.0)
|
88
106
|
grpc-google-iam-v1 (~> 0.6.9)
|
89
107
|
google-gax (1.8.1)
|
90
108
|
google-protobuf (~> 3.9)
|
@@ -106,12 +124,13 @@ GEM
|
|
106
124
|
multi_json (~> 1.11)
|
107
125
|
os (>= 0.9, < 2.0)
|
108
126
|
signet (~> 0.12)
|
109
|
-
grpc (1.
|
127
|
+
grpc (1.25.0-universal-darwin)
|
110
128
|
google-protobuf (~> 3.8)
|
111
129
|
googleapis-common-protos-types (~> 1.0)
|
112
130
|
grpc-google-iam-v1 (0.6.9)
|
113
131
|
googleapis-common-protos (>= 1.3.1, < 2.0)
|
114
132
|
grpc (~> 1.0)
|
133
|
+
hashdiff (1.0.0)
|
115
134
|
i18n (1.7.0)
|
116
135
|
concurrent-ruby (~> 1.0)
|
117
136
|
jaro_winkler (1.5.4)
|
@@ -123,7 +142,7 @@ GEM
|
|
123
142
|
mini_mime (>= 0.1.1)
|
124
143
|
marcel (0.3.3)
|
125
144
|
mimemagic (~> 0.3.2)
|
126
|
-
memoist (0.16.
|
145
|
+
memoist (0.16.1)
|
127
146
|
method_source (0.9.2)
|
128
147
|
mimemagic (0.3.3)
|
129
148
|
mini_mime (1.0.2)
|
@@ -134,6 +153,8 @@ GEM
|
|
134
153
|
nio4r (2.5.2)
|
135
154
|
nokogiri (1.10.5)
|
136
155
|
mini_portile2 (~> 2.4.0)
|
156
|
+
octokit (4.14.0)
|
157
|
+
sawyer (~> 0.8.0, >= 0.5.3)
|
137
158
|
os (1.0.1)
|
138
159
|
parallel (1.18.0)
|
139
160
|
parser (2.6.5.0)
|
@@ -172,6 +193,7 @@ GEM
|
|
172
193
|
rainbow (3.0.0)
|
173
194
|
rake (10.5.0)
|
174
195
|
redis (4.1.3)
|
196
|
+
retriable (3.1.2)
|
175
197
|
rly (0.2.3)
|
176
198
|
rspec (3.9.0)
|
177
199
|
rspec-core (~> 3.9.0)
|
@@ -204,6 +226,10 @@ GEM
|
|
204
226
|
rubocop-rspec (1.36.0)
|
205
227
|
rubocop (>= 0.68.1)
|
206
228
|
ruby-progressbar (1.10.1)
|
229
|
+
safe_yaml (1.0.5)
|
230
|
+
sawyer (0.8.2)
|
231
|
+
addressable (>= 2.3.5)
|
232
|
+
faraday (> 0.8, < 2.0)
|
207
233
|
signet (0.12.0)
|
208
234
|
addressable (~> 2.3)
|
209
235
|
faraday (~> 0.9)
|
@@ -223,6 +249,10 @@ GEM
|
|
223
249
|
tzinfo (1.2.5)
|
224
250
|
thread_safe (~> 0.1)
|
225
251
|
unicode-display_width (1.6.0)
|
252
|
+
webmock (3.7.6)
|
253
|
+
addressable (>= 2.3.6)
|
254
|
+
crack (>= 0.3.2)
|
255
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
226
256
|
websocket-driver (0.7.1)
|
227
257
|
websocket-extensions (>= 0.1.0)
|
228
258
|
websocket-extensions (0.1.4)
|
@@ -232,8 +262,10 @@ PLATFORMS
|
|
232
262
|
ruby
|
233
263
|
|
234
264
|
DEPENDENCIES
|
265
|
+
appraisal
|
235
266
|
bundler (~> 2.0)
|
236
267
|
cloudtasker!
|
268
|
+
github_changelog_generator
|
237
269
|
rails
|
238
270
|
rake (~> 10.0)
|
239
271
|
rspec (~> 3.0)
|
@@ -242,6 +274,7 @@ DEPENDENCIES
|
|
242
274
|
rubocop-rspec
|
243
275
|
sqlite3
|
244
276
|
timecop
|
277
|
+
webmock
|
245
278
|
|
246
279
|
BUNDLED WITH
|
247
280
|
2.0.2
|
data/README.md
CHANGED
@@ -1,8 +1,35 @@
|
|
1
1
|
# Cloudtasker
|
2
2
|
|
3
|
-
|
3
|
+
Background jobs for Ruby using Google Cloud Tasks.
|
4
4
|
|
5
|
-
|
5
|
+
Cloudtasker provides an easy to manage interface to Google Cloud Tasks for background job processing. Workers can be defined programmatically using the Cloudtasker DSL and enqueued for processing using a simple to use API.
|
6
|
+
|
7
|
+
Cloudtasker is particularly suited for serverless applications only responding to HTTP requests and where running a dedicated job processing is not an option (e.g. deploy via [Cloud Run](https://cloud.google.com/run)). All jobs enqueued in Cloud Tasks via Cloudtasker eventually gets processed by your application via HTTP requests.
|
8
|
+
|
9
|
+
Cloudtasker also provides optional modules for running [cron jobs](docs/CRON_JOBS.md), [batch jobs](docs/BATCH_JOBS.md) and [unique jobs](docs/UNIQUE_JOBS.md).
|
10
|
+
|
11
|
+
A local processing server is also available in development. This local server processes jobs in lieu of Cloud Tasks and allow you to work offline.
|
12
|
+
|
13
|
+
## Summary
|
14
|
+
|
15
|
+
1. [Installation](#installation)
|
16
|
+
2. [Get started with Rails](#get-started-with-rails)
|
17
|
+
3. [Configuring Cloudtasker](#configuring-cloudtasker)
|
18
|
+
1. [Cloud Tasks authentication & permissions](#cloud-tasks-authentication--permissions)
|
19
|
+
2. [Cloudtasker initializer](#cloudtasker-initializer)
|
20
|
+
4. [Enqueuing jobs](#enqueuing-jobs)
|
21
|
+
5. [Extensions](#extensions)
|
22
|
+
6. [Working locally](#working-locally)
|
23
|
+
1. [Option 1: Cloudtasker local server](#option-1-cloudtasker-local-server)
|
24
|
+
2. [Option 2: Using ngrok](#option-2-using-ngrok)
|
25
|
+
7. [Logging](#logging)
|
26
|
+
1. [Configuring a logger](#configuring-a-logger)
|
27
|
+
2. [Logging context](#logging-context)
|
28
|
+
8. [Error Handling](#error-handling)
|
29
|
+
1. [HTTP Error codes](#http-error-codes)
|
30
|
+
2. [Error callbacks](#error-callbacks)
|
31
|
+
3. [Max retries](#max-retries)
|
32
|
+
9. [Best practices building workers](#best-practices-building-workers)
|
6
33
|
|
7
34
|
## Installation
|
8
35
|
|
@@ -20,9 +47,545 @@ Or install it yourself as:
|
|
20
47
|
|
21
48
|
$ gem install cloudtasker
|
22
49
|
|
23
|
-
##
|
50
|
+
## Get started with Rails
|
51
|
+
|
52
|
+
Cloudtasker is pre-integrated with Rails. Follow the steps below to get started.
|
53
|
+
|
54
|
+
Install redis on your machine (this is required by the Cloudtasker local processing server)
|
55
|
+
```bash
|
56
|
+
# E.g. using brew
|
57
|
+
brew install redis
|
58
|
+
```
|
59
|
+
|
60
|
+
Add the following initializer
|
61
|
+
```ruby
|
62
|
+
# config/initializers/cloudtasker.rb
|
63
|
+
|
64
|
+
Cloudtasker.configure do |config|
|
65
|
+
#
|
66
|
+
# Adapt the server port to be the one used by your Rails web process
|
67
|
+
#
|
68
|
+
config.processor_host = 'http://localhost:3000'
|
69
|
+
|
70
|
+
#
|
71
|
+
# If you do not have any Rails secret_key_base defined, uncomment the following
|
72
|
+
# This secret is used to authenticate jobs sent to the processing endpoint
|
73
|
+
# of your application.
|
74
|
+
#
|
75
|
+
# config.secret = 'some-long-token'
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
Define your first worker:
|
80
|
+
```ruby
|
81
|
+
# app/workers/dummy_worker.rb
|
82
|
+
|
83
|
+
class DummyWorker
|
84
|
+
include Cloudtasker::Worker
|
85
|
+
|
86
|
+
def perform(some_arg)
|
87
|
+
logger.info("Job run with #{some_arg}. This is working!")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
Launch Rails and the local Cloudtasker processing server (or add `cloudtasker` to your foreman config as a `worker` process)
|
93
|
+
```bash
|
94
|
+
# In one terminal
|
95
|
+
> rails s -p 3000
|
96
|
+
|
97
|
+
# In another terminal
|
98
|
+
> cloudtasker
|
99
|
+
```
|
100
|
+
|
101
|
+
Open a Rails console and enqueue some jobs
|
102
|
+
```ruby
|
103
|
+
# Process job as soon as possible
|
104
|
+
DummyWorker.perform_async('foo')
|
105
|
+
|
106
|
+
# Process job in 60 seconds
|
107
|
+
DummyWorker.perform_in(10, 'foo')
|
108
|
+
```
|
109
|
+
|
110
|
+
Your Rails logs should display the following:
|
111
|
+
```log
|
112
|
+
Started POST "/cloudtasker/run" for ::1 at 2019-11-22 09:20:09 +0100
|
113
|
+
|
114
|
+
Processing by Cloudtasker::WorkerController#run as */*
|
115
|
+
Parameters: {"worker"=>"DummyWorker", "job_id"=>"d76040a1-367e-4e3b-854e-e05a74d5f773", "job_args"=>["foo"], "job_meta"=>{}}
|
116
|
+
|
117
|
+
I, [2019-11-22T09:20:09.319336 #49257] INFO -- [Cloudtasker][d76040a1-367e-4e3b-854e-e05a74d5f773] Starting job...: {:worker=>"DummyWorker", :job_id=>"d76040a1-367e-4e3b-854e-e05a74d5f773", :job_meta=>{}}
|
118
|
+
I, [2019-11-22T09:20:09.319938 #49257] INFO -- [Cloudtasker][d76040a1-367e-4e3b-854e-e05a74d5f773] Job run with foo. This is working!: {:worker=>"DummyWorker", :job_id=>"d76040a1-367e-4e3b-854e-e05a74d5f773", :job_meta=>{}}
|
119
|
+
I, [2019-11-22T09:20:09.320966 #49257] INFO -- [Cloudtasker][d76040a1-367e-4e3b-854e-e05a74d5f773] Job done: {:worker=>"DummyWorker", :job_id=>"d76040a1-367e-4e3b-854e-e05a74d5f773", :job_meta=>{}}
|
120
|
+
```
|
121
|
+
|
122
|
+
That's it! Your job was picked up by the Cloudtasker local server and sent for processing to your Rails web process.
|
123
|
+
|
124
|
+
Now jump to the next section to configure your app to use Google Cloud Tasks as a backend.
|
125
|
+
|
126
|
+
## Configuring Cloudtasker
|
127
|
+
|
128
|
+
### Cloud Tasks authentication & permissions
|
129
|
+
|
130
|
+
The Google Cloud library authenticates via the Google Cloud SDK by default. If you do not have it setup then we recommend you [install it](https://cloud.google.com/sdk/docs/quickstarts).
|
131
|
+
|
132
|
+
Other options are available such as using a service account. You can see all authentication options in the [Google Cloud Authentication guide](https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-bigquery/AUTHENTICATION.md).
|
133
|
+
|
134
|
+
In order to function properly Cloudtasker requires the authenticated account to have the following IAM permissions:
|
135
|
+
- `cloudtasks.tasks.get`
|
136
|
+
- `cloudtasks.tasks.create`
|
137
|
+
- `cloudtasks.tasks.delete`
|
138
|
+
|
139
|
+
To get started quickly you can add the `roles/cloudtasks.queueAdmin` role to your account via the [IAM Console](https://console.cloud.google.com/iam-admin/iam). This is not required if your account is a project admin account.
|
140
|
+
|
141
|
+
|
142
|
+
### Cloudtasker initializer
|
143
|
+
|
144
|
+
The gem can be configured through an initializer. See below all the available configuration options.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# config/initializers/cloudtasker.rb
|
148
|
+
|
149
|
+
Cloudtasker.configure do |config|
|
150
|
+
#
|
151
|
+
# If you do not have any Rails secret_key_base defined, uncomment the following.
|
152
|
+
# This secret is used to authenticate jobs sent to the processing endpoint
|
153
|
+
# of your application.
|
154
|
+
#
|
155
|
+
# Default with Rails: Rails.application.credentials.secret_key_base
|
156
|
+
#
|
157
|
+
# config.secret = 'some-long-token'
|
158
|
+
|
159
|
+
#
|
160
|
+
# Specify the details of your Google Cloud Task queue.
|
161
|
+
#
|
162
|
+
# This not required in development using the Cloudtasker local server.
|
163
|
+
#
|
164
|
+
config.gcp_location_id = 'us-central1' # defaults to 'us-east1'
|
165
|
+
config.gcp_project_id = 'my-gcp-project'
|
166
|
+
config.gcp_queue_id = 'my-queue'
|
167
|
+
|
168
|
+
#
|
169
|
+
# Specify the publicly accessible host for your application
|
170
|
+
#
|
171
|
+
# > E.g. in development, using the cloudtasker local server
|
172
|
+
# config.processor_host = 'http://localhost:3000'
|
173
|
+
#
|
174
|
+
# > E.g. in development, using `config.mode = :production` and ngrok
|
175
|
+
# config.processor_host = 'https://111111.ngrok.io'
|
176
|
+
#
|
177
|
+
config.processor_host = 'https://app.mydomain.com'
|
178
|
+
|
179
|
+
#
|
180
|
+
# Specify the mode of operation:
|
181
|
+
# - :development => jobs will be pushed to Redis and picked up by the Cloudtasker local server
|
182
|
+
# - :production => jobs will be pushed to Google Cloud Tasks. Requires a publicly accessible domain.
|
183
|
+
#
|
184
|
+
# Defaults to :development unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set to something else.
|
185
|
+
#
|
186
|
+
# config.mode = Rails.env.production? || Rails.env.my_other_env? ? :production : :development
|
187
|
+
|
188
|
+
#
|
189
|
+
# Specify the logger to use
|
190
|
+
#
|
191
|
+
# Default with Rails: Rails.logger
|
192
|
+
# Default without Rails: Logger.new(STDOUT)
|
193
|
+
#
|
194
|
+
# config.logger = MyLogger.new(STDOUT)
|
195
|
+
|
196
|
+
#
|
197
|
+
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
198
|
+
# connectivity error that would be due to the application being down or unreachable.
|
199
|
+
#
|
200
|
+
# Default: 25
|
201
|
+
#
|
202
|
+
# config.max_retries = 10
|
24
203
|
|
25
|
-
|
204
|
+
#
|
205
|
+
# Specify the redis connection hash.
|
206
|
+
#
|
207
|
+
# This is ONLY required in development for the Cloudtasker local server and in
|
208
|
+
# all environments if you use any cloudtasker extension (unique jobs, cron jobs or batch jobs)
|
209
|
+
#
|
210
|
+
# See https://github.com/redis/redis-rb for examples of configuration hashes.
|
211
|
+
#
|
212
|
+
# Default: redis-rb connects to redis://127.0.0.1:6379/0
|
213
|
+
#
|
214
|
+
# config.redis = { url: 'redis://localhost:6379/5' }
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
If your queue does not exist in Cloud Tasks you should [create it using the gcloud sdk](https://cloud.google.com/tasks/docs/creating-queues).
|
219
|
+
|
220
|
+
Alternatively with Rails you can simply run the following rake task if you have queue admin permissions (`cloudtasks.queues.get` and `cloudtasks.queues.create`).
|
221
|
+
```bash
|
222
|
+
bundle exec rake cloudtasker:setup_queue
|
223
|
+
```
|
224
|
+
|
225
|
+
## Enqueuing jobs
|
226
|
+
|
227
|
+
Cloudtasker provides multiple ways of enqueuing jobs.
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
# Worker will be processed as soon as possible
|
231
|
+
MyWorker.perform_async(arg1, arg2)
|
232
|
+
|
233
|
+
# Worker will be processed in 5 minutes
|
234
|
+
MyWorker.perform_in(5 * 60, arg1, arg2)
|
235
|
+
# or with Rails
|
236
|
+
MyWorker.perform_in(5.minutes, arg1, arg2)
|
237
|
+
|
238
|
+
# Worker will be processed on specific date
|
239
|
+
MyWorker.perform_at(Time.parse('2025-01-01 00:50:00Z'), arg1, arg2)
|
240
|
+
# also with Rails
|
241
|
+
MyWorker.perform_at(3.days.from_now, arg1, arg2)
|
242
|
+
```
|
243
|
+
|
244
|
+
Cloudtasker also provides a helper for re-enqueuing jobs. Re-enqueued jobs keep the same worker id. Some middlewares may rely on this to track the fact that that a job didn't actually complete (e.g. Cloustasker batch). This is optional and you can always fallback to using exception management (raise an error) to retry/re-enqueue jobs.
|
245
|
+
|
246
|
+
E.g.
|
247
|
+
```ruby
|
248
|
+
# app/workers/fetch_resource_worker.rb
|
249
|
+
|
250
|
+
class FetchResourceWorker
|
251
|
+
include Cloudtasker::Worker
|
252
|
+
|
253
|
+
def perform(id)
|
254
|
+
# ...do some logic...
|
255
|
+
if some_condition
|
256
|
+
# Stop and re-enqueue the job to be run again in 10 seconds.
|
257
|
+
return reenqueue(10)
|
258
|
+
else
|
259
|
+
# ...keep going...
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
## Extensions
|
266
|
+
Cloudtasker comes with three optional features:
|
267
|
+
- Cron Jobs [[docs](docs/CRON_JOBS.md)]: Run jobs at fixed intervals.
|
268
|
+
- Batch Jobs [[docs](docs/BATCH_JOBS.md)]: Run jobs in jobs and track completion of the overall batch.
|
269
|
+
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
270
|
+
|
271
|
+
## Working locally
|
272
|
+
|
273
|
+
Cloudtasker pushes jobs to Google Cloud Tasks, which in turn sends jobs for processing to your application via HTTP POST requests to the `/cloudtasker/run` endpoint of the publicly accessible domain of your application.
|
274
|
+
|
275
|
+
When working locally on your application it is usually not possible to have a public domain. So what are the options?
|
276
|
+
|
277
|
+
### Option 1: Cloudtasker local server
|
278
|
+
The Cloudtasker local server is a ruby daemon that looks for jobs pushed to Redis and sends them to your application via HTTP POST requests. The server mimics the way Google Cloud Tasks works, but locally!
|
279
|
+
|
280
|
+
You can configure your applicatiion to use the Cloudtasker local server using the following initializer:
|
281
|
+
```ruby
|
282
|
+
# config/initializers/cloudtasker.rb
|
283
|
+
|
284
|
+
Cloudtasker.configure do |config|
|
285
|
+
# ... other options
|
286
|
+
|
287
|
+
# Push jobs to redis and let the Cloudtasker local server collect them
|
288
|
+
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
289
|
+
# to a non-development environment
|
290
|
+
config.mode = :development
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
The Cloudtasker server can then be started using:
|
295
|
+
```bash
|
296
|
+
cloudtasker
|
297
|
+
# or
|
298
|
+
bundle exec cloudtasker
|
299
|
+
```
|
300
|
+
|
301
|
+
You can as well define a Procfile to manage the cloudtasker process via foreman. Then use `foreman start` to launch both your Rails server and the Cloudtasker local server.
|
302
|
+
```yaml
|
303
|
+
# Procfile
|
304
|
+
web: rails s
|
305
|
+
worker: cloudtasker
|
306
|
+
```
|
307
|
+
|
308
|
+
### Option 2: Using ngrok
|
309
|
+
|
310
|
+
Want to test your application end to end with Google Cloud Task? Then [ngrok](https://ngrok.io) is the way to go.
|
311
|
+
|
312
|
+
First start your ngrok tunnel and take note of the :
|
313
|
+
```bash
|
314
|
+
ngrok http 3000
|
315
|
+
```
|
316
|
+
|
317
|
+
Take note of your ngrok domain and configure Cloudtasker to use Google Cloud Task in development via ngrok.
|
318
|
+
```ruby
|
319
|
+
# config/initializers/cloudtasker.rb
|
320
|
+
|
321
|
+
Cloudtasker.configure do |config|
|
322
|
+
# Specify your Google Cloud Task queue configuration
|
323
|
+
# config.gcp_location_id = 'us-central1'
|
324
|
+
# config.gcp_project_id = 'my-gcp-project'
|
325
|
+
# config.gcp_queue_id = 'my-queue'
|
326
|
+
|
327
|
+
# Use your ngrok domain as the processor host
|
328
|
+
config.processor_host = 'https://your-tunnel-id.ngrok.io'
|
329
|
+
|
330
|
+
# Force Cloudtasker to use Google Cloud Tasks in development
|
331
|
+
config.mode = :production
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
Finally start Rails to accept jobs from Google Cloud Tasks
|
336
|
+
```bash
|
337
|
+
rails s
|
338
|
+
```
|
339
|
+
|
340
|
+
## Logging
|
341
|
+
There are several options available to configure logging and logging context.
|
342
|
+
|
343
|
+
### Configuring a logger
|
344
|
+
Cloudtasker uses `Rails.logger` if Rails is available and falls back on a plain ruby logger `Logger.new(STDOUT)` if not.
|
345
|
+
|
346
|
+
It is also possible to configure your own logger. For example you can setup Cloudtasker with [semantic_logger](http://rocketjob.github.io/semantic_logger) by doing the following your initializer:
|
347
|
+
```ruby
|
348
|
+
# config/initializers/cloudtasker.rb
|
349
|
+
|
350
|
+
Cloudtasker.configure do |config|
|
351
|
+
config.logger = SemanticLogger[Cloudtasker]
|
352
|
+
end
|
353
|
+
```
|
354
|
+
|
355
|
+
### Logging context
|
356
|
+
Cloudtasker provides worker contextual information to the worker `logger` method inside your worker methods.
|
357
|
+
|
358
|
+
For example:
|
359
|
+
```ruby
|
360
|
+
# app/workers/dummy_worker.rb
|
361
|
+
|
362
|
+
class DummyWorker
|
363
|
+
include Cloudtasker::Worker
|
364
|
+
|
365
|
+
def perform(some_arg)
|
366
|
+
logger.info("Job run with #{some_arg}. This is working!")
|
367
|
+
end
|
368
|
+
end
|
369
|
+
```
|
370
|
+
|
371
|
+
Will generate the following log with context `{:worker=> ..., :job_id=> ..., :job_meta=> ...}`
|
372
|
+
```log
|
373
|
+
[Cloudtasker][d76040a1-367e-4e3b-854e-e05a74d5f773] Job run with foo. This is working!: {:worker=>"DummyWorker", :job_id=>"d76040a1-367e-4e3b-854e-e05a74d5f773", :job_meta=>{}}
|
374
|
+
```
|
375
|
+
|
376
|
+
The way contextual information is displayed depends on the logger itself. For example with [semantic_logger](http://rocketjob.github.io/semantic_logger) contextual information might not appear in the log message but show up as payload data on the log entry itself (e.g. using the fluentd adapter).
|
377
|
+
|
378
|
+
Contextual information can be customised globally and locally using a log context_processor. By default the `Cloudtasker::WorkerLogger` is configured the following way:
|
379
|
+
```ruby
|
380
|
+
Cloudtasker::WorkerLogger.log_context_processor = ->(worker) { worker.to_h.slice(:worker, :job_id, :job_meta) }
|
381
|
+
```
|
382
|
+
|
383
|
+
You can decide to add a global identifier for your worker logs using the following:
|
384
|
+
```ruby
|
385
|
+
# config/initializers/cloudtasker.rb
|
386
|
+
|
387
|
+
Cloudtasker::WorkerLogger.log_context_processor = lambda { |worker|
|
388
|
+
worker.to_h.slice(:worker, :job_id, :job_meta).merge(app: 'my-app')
|
389
|
+
}
|
390
|
+
```
|
391
|
+
|
392
|
+
You could also decide to log all available context (including arguments passed to perform) for specific workers only:
|
393
|
+
```ruby
|
394
|
+
# app/workers/full_context_worker.rb
|
395
|
+
|
396
|
+
class FullContextWorker
|
397
|
+
include Cloudtasker::Worker
|
398
|
+
|
399
|
+
cloudtasker_options log_context_processor: ->(worker) { worker.to_h }
|
400
|
+
|
401
|
+
def perform(some_arg)
|
402
|
+
logger.info("This log entry will have full context!")
|
403
|
+
end
|
404
|
+
end
|
405
|
+
```
|
406
|
+
|
407
|
+
See the [Cloudtasker::Worker class](blob/master/lib/cloudtasker/worker.rb) for more information on attributes available to be logged in your `log_context_processor` proc.
|
408
|
+
|
409
|
+
## Error Handling
|
410
|
+
|
411
|
+
Jobs failing will automatically return an HTTP error to Cloud Task and trigger a retry at a later time. The number of retries Cloud Task will do depends on the configuration of your queue in Cloud Tasks.
|
412
|
+
|
413
|
+
### HTTP Error codes
|
414
|
+
|
415
|
+
Jobs failing will automatically return the following HTTP error code to Cloud Tasks, based on the actual reason:
|
416
|
+
|
417
|
+
| Code | Description |
|
418
|
+
|------|-------------|
|
419
|
+
| 205 | The job is dead and has been removed from the queue |
|
420
|
+
| 404 | The job has specified an incorrect worker class. |
|
421
|
+
| 422 | An error happened during the execution of the worker (`perform` method) |
|
422
|
+
|
423
|
+
### Error callbacks
|
424
|
+
|
425
|
+
Workers can implement the `on_error(error)` and `on_dead(error)` callbacks to do things when a job fails during its execution:
|
426
|
+
|
427
|
+
E.g.
|
428
|
+
```ruby
|
429
|
+
# app/workers/handle_error_worker.rb
|
430
|
+
|
431
|
+
class HandleErrorWorker
|
432
|
+
include Cloudtasker::Worker
|
433
|
+
|
434
|
+
def perform
|
435
|
+
raise(ArgumentError)
|
436
|
+
end
|
437
|
+
|
438
|
+
# The runtime error is passed as an argument.
|
439
|
+
def on_error(error)
|
440
|
+
logger.error("The following error happened: #{error}")
|
441
|
+
end
|
442
|
+
|
443
|
+
# The job has been retried too many times and will be removed
|
444
|
+
# from the queue.
|
445
|
+
def on_dead(error)
|
446
|
+
logger.error("The job died with the following error: #{error}")
|
447
|
+
end
|
448
|
+
end
|
449
|
+
```
|
450
|
+
|
451
|
+
### Max retries
|
452
|
+
|
453
|
+
By default jobs are retried 25 times - using an exponential backoff - before being declared dead. This number of retries can be customized locally on workers and/or globally via the Cloudtasker initializer.
|
454
|
+
|
455
|
+
Note that the number of retries set on your Cloud Task queue should be many times higher than the number of retries configured in Cloudtasker because Cloud Task also includes failures to connect to your application. Ideally set the number of retries to `unlimited` in Cloud Tasks.
|
456
|
+
|
457
|
+
E.g. Set max number of retries globally via the cloudtasker initializer.
|
458
|
+
```ruby
|
459
|
+
# config/initializers/cloudtasker.rb
|
460
|
+
|
461
|
+
Cloudtasker.configure do |config|
|
462
|
+
#
|
463
|
+
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
464
|
+
# connectivity error that would be due to the application being down or unreachable.
|
465
|
+
#
|
466
|
+
# Default: 25
|
467
|
+
#
|
468
|
+
config.max_retries = 10
|
469
|
+
end
|
470
|
+
```
|
471
|
+
|
472
|
+
E.g. Set max number of retries to 3 on a given worker
|
473
|
+
|
474
|
+
E.g.
|
475
|
+
```ruby
|
476
|
+
# app/workers/some_error_worker.rb
|
477
|
+
|
478
|
+
class SomeErrorWorker
|
479
|
+
include Cloudtasker::Worker
|
480
|
+
|
481
|
+
# This will override the global setting
|
482
|
+
cloudtasker_options max_retries: 3
|
483
|
+
|
484
|
+
def perform()
|
485
|
+
raise(ArgumentError)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
```
|
489
|
+
|
490
|
+
|
491
|
+
|
492
|
+
## Best practices building workers
|
493
|
+
|
494
|
+
Below are recommendations and notes about creating workers.
|
495
|
+
|
496
|
+
### Use primitive arguments
|
497
|
+
Pushing a job via `MyWorker.perform_async(arg1, arg2)` will serialize all arguments as JSON. Cloudtasker does not do any magic marshalling and therefore passing user-defined class instance as arguments is likely to make your jobs fail because of JSON serialization/deserialization.
|
498
|
+
|
499
|
+
When defining your worker `perform` method, use primitive arguments (integers, strings, hashes).
|
500
|
+
|
501
|
+
Don't do that:
|
502
|
+
```ruby
|
503
|
+
# app/workers/user_email_worker.rb
|
504
|
+
|
505
|
+
class UserEmailWorker
|
506
|
+
include Cloudtasker::Worker
|
507
|
+
|
508
|
+
def perform(user)
|
509
|
+
user.reload.send_email
|
510
|
+
end
|
511
|
+
end
|
512
|
+
```
|
513
|
+
|
514
|
+
Do that:
|
515
|
+
```ruby
|
516
|
+
# app/workers/user_email_worker.rb
|
517
|
+
|
518
|
+
class UserEmailWorker
|
519
|
+
include Cloudtasker::Worker
|
520
|
+
|
521
|
+
def perform(user_id)
|
522
|
+
User.find_by(id: user_id)&.send_email
|
523
|
+
end
|
524
|
+
end
|
525
|
+
```
|
526
|
+
|
527
|
+
### Assume hash arguments are stringified
|
528
|
+
Because of JSON serialization/deserialization hashes passed to `perform_*` methods will eventually be passed as stringified hashes to the worker `perform` method.
|
529
|
+
|
530
|
+
```ruby
|
531
|
+
# Enqueuing a job with:
|
532
|
+
MyWorker.perform_async({ foo: 'bar', 'baz' => { key: 'value' } })
|
533
|
+
|
534
|
+
# will be processed as
|
535
|
+
MyWorker.new.perform({ 'foo' => 'bar', 'baz' => { 'key' => 'value' } })
|
536
|
+
```
|
537
|
+
|
538
|
+
### Be careful with default arguments
|
539
|
+
Default arguments passed to the `perform` method are not actually considered as job arguments. Default arguments will therefore be ignored in contextual logging and by extensions relying on arguments such as the `unique-job` extension.
|
540
|
+
|
541
|
+
Consider the following worker:
|
542
|
+
```ruby
|
543
|
+
# app/workers/user_email_worker.rb
|
544
|
+
|
545
|
+
class UserEmailWorker
|
546
|
+
include Cloudtasker::Worker
|
547
|
+
|
548
|
+
cloudtasker_options lock: :until_executed
|
549
|
+
|
550
|
+
def perform(user_id, time_at = Time.now.iso8601)
|
551
|
+
User.find_by(id: user_id)&.send_email(Time.parse(time_at))
|
552
|
+
end
|
553
|
+
end
|
554
|
+
```
|
555
|
+
|
556
|
+
If you enqueue this worker by omitting the second argument `MyWorker.perform_async(123)` then:
|
557
|
+
- The `time_at` argument will not be included in contextual logging
|
558
|
+
- The `time_at` argument will be ignored by the `unique-job` extension, meaning that job uniqueness will be only based on the `user_id` argument.
|
559
|
+
|
560
|
+
### Handling big job payloads
|
561
|
+
Keep in mind that jobs are pushed to Google Cloud Tasks via API and then delivered to your application via API as well. Therefore any excessive job payload will slow down the enqueuing of jobs and create additional processing when receiving the job.
|
562
|
+
|
563
|
+
If you feel that a job payload is going to get big, prefer to store the payload using a datastore (e.g. Redis) and pass a reference to the job to retrieve the payload inside your job `perform` method.
|
564
|
+
|
565
|
+
E.g. Define a job like this
|
566
|
+
```ruby
|
567
|
+
# app/workers/big_payload_worker.rb
|
568
|
+
|
569
|
+
class BigPayloadWorker
|
570
|
+
include Cloudtasker::Worker
|
571
|
+
|
572
|
+
def perform(payload_id)
|
573
|
+
data = Rails.cache.fetch(payload_id)
|
574
|
+
# ...do some processing...
|
575
|
+
end
|
576
|
+
end
|
577
|
+
```
|
578
|
+
|
579
|
+
Then enqueue your job like this:
|
580
|
+
```ruby
|
581
|
+
# Fetch and store the payload
|
582
|
+
data = ApiClient.fetch_thousands_of_records
|
583
|
+
payload_id = SecureRandom.uuid
|
584
|
+
Rails.cache.write(payload_id, data)
|
585
|
+
|
586
|
+
# Enqueue the processing job
|
587
|
+
BigPayloadWorker.perform_async(payload_id)
|
588
|
+
```
|
26
589
|
|
27
590
|
## Development
|
28
591
|
|
@@ -32,7 +595,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
595
|
|
33
596
|
## Contributing
|
34
597
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
598
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/keypup-io/cloudtasker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
599
|
|
37
600
|
## License
|
38
601
|
|
@@ -40,4 +603,8 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
40
603
|
|
41
604
|
## Code of Conduct
|
42
605
|
|
43
|
-
Everyone interacting in the Cloudtasker project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
606
|
+
Everyone interacting in the Cloudtasker project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/keypup-io/cloudtasker/blob/master/CODE_OF_CONDUCT.md).
|
607
|
+
|
608
|
+
## Author
|
609
|
+
|
610
|
+
Provided with :heart: by [keypup.io](https://keypup.io/)
|