cloudtasker 0.10.rc7 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +6 -6
- data/Appraisals +16 -0
- data/CHANGELOG.md +57 -4
- data/README.md +54 -33
- data/app/controllers/cloudtasker/worker_controller.rb +4 -1
- data/cloudtasker.gemspec +2 -1
- data/gemfiles/google_cloud_tasks_1.0.gemfile.lock +202 -148
- data/gemfiles/google_cloud_tasks_1.1.gemfile.lock +202 -148
- data/gemfiles/google_cloud_tasks_1.2.gemfile.lock +202 -148
- data/gemfiles/google_cloud_tasks_1.3.gemfile.lock +202 -148
- data/gemfiles/rails_5.2.gemfile.lock +139 -84
- data/gemfiles/rails_6.0.gemfile.lock +140 -85
- data/gemfiles/semantic_logger_3.4.gemfile +7 -0
- data/gemfiles/semantic_logger_4.6.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.gemfile +7 -0
- data/lib/cloudtasker/backend/memory_task.rb +3 -0
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +15 -1
- data/lib/cloudtasker/worker_logger.rb +1 -1
- data/lib/cloudtasker.rb +1 -0
- metadata +30 -12
- data/app/controllers/cloudtasker/application_controller.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7c9396bf11e753118e256f596c41ec5977cc30c49f6f3e8a85e8448bb58d11e
|
4
|
+
data.tar.gz: b9fd7674bf747d7b837d2b229fc59697cf93804796cd2c239b793dcb7c070d00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5dcd9eaf3db496d437d9d802650abaed4f056309bb0e0d22d977e50051317da2553efe3b112d9bf138e7f4a0fb22af865d7359b7921cdfc49be778d9860b1c8f
|
7
|
+
data.tar.gz: aceabb668792364c0124f188002a590c4b76b33e694c5eeec9e6ddcd5dcda58bf703cd6f1cb3d0936fb8720bb3de6248c39e7fc1aa4795117ce351b6b3a7d6a3
|
data/.github/workflows/test.yml
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
name: Test
|
2
2
|
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [ master ]
|
6
|
-
pull_request:
|
7
|
-
branches: [ master ]
|
3
|
+
on: [push, pull_request]
|
8
4
|
|
9
5
|
jobs:
|
10
6
|
build:
|
@@ -21,6 +17,10 @@ jobs:
|
|
21
17
|
- 'google-cloud-tasks-1.3'
|
22
18
|
- 'rails-5.2'
|
23
19
|
- 'rails-6.0'
|
20
|
+
- 'semantic_logger-3.4'
|
21
|
+
- 'semantic_logger-4.6'
|
22
|
+
- 'semantic_logger-4.7.0'
|
23
|
+
- 'semantic_logger-4.7.2'
|
24
24
|
steps:
|
25
25
|
- name: Setup System
|
26
26
|
run: sudo apt-get install libsqlite3-dev
|
@@ -38,4 +38,4 @@ jobs:
|
|
38
38
|
bundle install --jobs 4 --retry 3
|
39
39
|
bundle exec rubocop
|
40
40
|
bundle exec appraisal ${APPRAISAL_CONTEXT} bundle
|
41
|
-
bundle exec appraisal ${APPRAISAL_CONTEXT} rspec
|
41
|
+
bundle exec appraisal ${APPRAISAL_CONTEXT} rspec
|
data/Appraisals
CHANGED
@@ -23,3 +23,19 @@ end
|
|
23
23
|
appraise 'rails-6.0' do
|
24
24
|
gem 'rails', '6.0'
|
25
25
|
end
|
26
|
+
|
27
|
+
appraise 'semantic_logger-3.4' do
|
28
|
+
gem 'semantic_logger', '3.4.1'
|
29
|
+
end
|
30
|
+
|
31
|
+
appraise 'semantic_logger-4.6' do
|
32
|
+
gem 'semantic_logger', '4.6.1'
|
33
|
+
end
|
34
|
+
|
35
|
+
appraise 'semantic_logger-4.7.0' do
|
36
|
+
gem 'semantic_logger', '4.7.0'
|
37
|
+
end
|
38
|
+
|
39
|
+
appraise 'semantic_logger-4.7.2' do
|
40
|
+
gem 'semantic_logger', '4.7.2'
|
41
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,62 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.10.2](https://github.com/keypup-io/cloudtasker/tree/v0.10.2) (2021-08-25)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.10.1...v0.10.2)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
- Dependencies: Require `try` from `activesupport`. This was preventing non-Rails projects from properly running Cloudtasker jobs.
|
9
|
+
- WorkerController: remove useless inheritance from local ApplicationController. The parent controller was not always loaded on Rails 5 which in turn created issues with authenticity token. Fixes [#40](https://github.com/keypup-io/cloudtasker/issues/40)
|
10
|
+
|
11
|
+
## [v0.9.5](https://github.com/keypup-io/cloudtasker/tree/v0.9.5) (2021-08-25)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.4...v0.9.5)
|
14
|
+
|
15
|
+
**Fixed bugs:**
|
16
|
+
- Dependencies: Require `try` from `activesupport`. This was preventing non-Rails projects from properly running Cloudtasker jobs.
|
17
|
+
- WorkerController: remove useless inheritance from local ApplicationController. The parent controller was not always loaded on Rails 5 which in turn created issues with authenticity token. Fixes [#40](https://github.com/keypup-io/cloudtasker/issues/40)
|
18
|
+
|
19
|
+
## [v0.10.1](https://github.com/keypup-io/cloudtasker/tree/v0.10.1) (2020-10-05)
|
20
|
+
|
21
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.10.0...v0.10.1)
|
22
|
+
|
23
|
+
**Fixed bugs:**
|
24
|
+
- Local server: delete dead task from local server queue
|
25
|
+
- Logging: fix log processing with `semantic_logger` `v4.7.2`. Accept any args on block passed to the logger.
|
26
|
+
- Worker: fix configuration of `max_retries` at worker level
|
27
|
+
|
28
|
+
## [v0.10.0](https://github.com/keypup-io/cloudtasker/tree/v0.10.0) (2020-09-02)
|
29
|
+
|
30
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.10.0)
|
31
|
+
|
32
|
+
**Improvements:**
|
33
|
+
- Logging: Add worker name in log messages
|
34
|
+
- Logging: Add job duration in log messages
|
35
|
+
- Logging: Add Cloud Cloud Task ID in log messages
|
36
|
+
- Unique Job: Support TTL for lock keys. This feature prevents queues from being dead-locked when a critical crash occurs while processing a unique job.
|
37
|
+
- Worker: support payload storage in Redis instead of sending the payload to Google Cloud Tasks. This is useful when job arguments are expected to exceed 100kb, which is the limit set by Google Cloud Tasks
|
38
|
+
|
39
|
+
**Fixed bugs:**
|
40
|
+
- Local processing error: improve error handling and retries around network interruptions
|
41
|
+
- Redis client: prevent deadlocks in high concurrency scenario by slowing down poll time and enforcing lock expiration
|
42
|
+
- Redis client: use connecion pool with Redis to prevent race conditions
|
43
|
+
- Google API: improve error handling on job creation
|
44
|
+
- Google API: use the `X-CloudTasks-TaskRetryCount` instead of `X-CloudTasks-TaskExecutionCount` to detect how many retries Google Cloud Tasks has performed. Using `X-CloudTasks-TaskRetryCount` is theoretically less accurate than using `X-CloudTasks-TaskExecutionCount` because it includes the number of "app unreachable" retries but `X-CloudTasks-TaskExecutionCount` is currently bugged and remains at zero all the time. See [this issue](https://github.com/keypup-io/cloudtasker/issues/6)
|
45
|
+
|
46
|
+
## [v0.9.4](https://github.com/keypup-io/cloudtasker/tree/v0.9.4) (2020-10-05)
|
47
|
+
|
48
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.9.4)
|
49
|
+
|
50
|
+
**Fixed bugs:**
|
51
|
+
- Logging: fix log processing with `semantic_logger` `v4.7.2`. Accept any args on block passed to the logger.
|
52
|
+
|
53
|
+
## [v0.9.3](https://github.com/keypup-io/cloudtasker/tree/v0.9.3) (2020-06-25)
|
54
|
+
|
55
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.2...v0.9.3)
|
56
|
+
|
57
|
+
**Fixed bugs:**
|
58
|
+
- Google Cloud Tasks: lock version to `~> 1.0` (Google recently released a v2 which changes its bindings completely). An [issue](https://github.com/keypup-io/cloudtasker/issues/11) has been raised to upgrade Cloudtasker to `google-cloud-tasks` `v2`.
|
59
|
+
|
3
60
|
## [v0.9.2](https://github.com/keypup-io/cloudtasker/tree/v0.9.2) (2020-03-04)
|
4
61
|
|
5
62
|
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.1...v0.9.2)
|
@@ -71,7 +128,3 @@ For Sinatra applications please update your Cloudtasker controller according to
|
|
71
128
|
## [v0.1.0](https://github.com/keypup-io/cloudtasker/tree/v0.1.0) (2019-11-17)
|
72
129
|
|
73
130
|
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/c137feb1ceaaaa4e2fecac0d1f0b4c73151ae002...v0.1.0)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/README.md
CHANGED
@@ -75,7 +75,7 @@ Cloudtasker.configure do |config|
|
|
75
75
|
# Adapt the server port to be the one used by your Rails web process
|
76
76
|
#
|
77
77
|
config.processor_host = 'http://localhost:3000'
|
78
|
-
|
78
|
+
|
79
79
|
#
|
80
80
|
# If you do not have any Rails secret_key_base defined, uncomment the following
|
81
81
|
# This secret is used to authenticate jobs sent to the processing endpoint
|
@@ -158,14 +158,14 @@ The gem can be configured through an initializer. See below all the available co
|
|
158
158
|
Cloudtasker.configure do |config|
|
159
159
|
#
|
160
160
|
# If you do not have any Rails secret_key_base defined, uncomment the following.
|
161
|
-
# This secret is used to authenticate jobs sent to the processing endpoint
|
161
|
+
# This secret is used to authenticate jobs sent to the processing endpoint
|
162
162
|
# of your application.
|
163
163
|
#
|
164
164
|
# Default with Rails: Rails.application.credentials.secret_key_base
|
165
165
|
#
|
166
166
|
# config.secret = 'some-long-token'
|
167
167
|
|
168
|
-
#
|
168
|
+
#
|
169
169
|
# Specify the details of your Google Cloud Task location.
|
170
170
|
#
|
171
171
|
# This not required in development using the Cloudtasker local server.
|
@@ -189,21 +189,21 @@ Cloudtasker.configure do |config|
|
|
189
189
|
#
|
190
190
|
# Specific queues can be created in Cloud Tasks using the gcloud SDK or
|
191
191
|
# via the `rake cloudtasker:setup_queue name=<queue_name>` task.
|
192
|
-
#
|
192
|
+
#
|
193
193
|
config.gcp_queue_prefix = 'my-app'
|
194
194
|
|
195
|
-
#
|
195
|
+
#
|
196
196
|
# Specify the publicly accessible host for your application
|
197
197
|
#
|
198
198
|
# > E.g. in development, using the cloudtasker local server
|
199
199
|
# config.processor_host = 'http://localhost:3000'
|
200
|
-
#
|
200
|
+
#
|
201
201
|
# > E.g. in development, using `config.mode = :production` and ngrok
|
202
202
|
# config.processor_host = 'https://111111.ngrok.io'
|
203
203
|
#
|
204
204
|
config.processor_host = 'https://app.mydomain.com'
|
205
205
|
|
206
|
-
#
|
206
|
+
#
|
207
207
|
# Specify the mode of operation:
|
208
208
|
# - :development => jobs will be pushed to Redis and picked up by the Cloudtasker local server
|
209
209
|
# - :production => jobs will be pushed to Google Cloud Tasks. Requires a publicly accessible domain.
|
@@ -212,20 +212,20 @@ Cloudtasker.configure do |config|
|
|
212
212
|
#
|
213
213
|
# config.mode = Rails.env.production? || Rails.env.my_other_env? ? :production : :development
|
214
214
|
|
215
|
-
#
|
215
|
+
#
|
216
216
|
# Specify the logger to use
|
217
|
-
#
|
217
|
+
#
|
218
218
|
# Default with Rails: Rails.logger
|
219
219
|
# Default without Rails: Logger.new(STDOUT)
|
220
|
-
#
|
220
|
+
#
|
221
221
|
# config.logger = MyLogger.new(STDOUT)
|
222
222
|
|
223
|
-
#
|
223
|
+
#
|
224
224
|
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
225
225
|
# connectivity error due to the application being down or unreachable.
|
226
|
-
#
|
226
|
+
#
|
227
227
|
# Default: 25
|
228
|
-
#
|
228
|
+
#
|
229
229
|
# config.max_retries = 10
|
230
230
|
|
231
231
|
#
|
@@ -250,7 +250,7 @@ Cloudtasker.configure do |config|
|
|
250
250
|
# You can set this configuration parameter to a KB value if you want to store jobs
|
251
251
|
# args in redis only if the JSONified arguments payload exceeds that threshold.
|
252
252
|
#
|
253
|
-
# Supported since: v0.10.
|
253
|
+
# Supported since: v0.10.0
|
254
254
|
#
|
255
255
|
# Default: false
|
256
256
|
#
|
@@ -262,7 +262,7 @@ Cloudtasker.configure do |config|
|
|
262
262
|
end
|
263
263
|
```
|
264
264
|
|
265
|
-
If the default queue `<gcp_queue_prefix>-default` does not exist in Cloud Tasks you should [create it using the gcloud sdk](https://cloud.google.com/tasks/docs/creating-queues).
|
265
|
+
If the default queue `<gcp_queue_prefix>-default` does not exist in Cloud Tasks you should [create it using the gcloud sdk](https://cloud.google.com/tasks/docs/creating-queues).
|
266
266
|
|
267
267
|
Alternatively with Rails you can simply run the following rake task if you have queue admin permissions (`cloudtasks.queues.get` and `cloudtasks.queues.create`).
|
268
268
|
```bash
|
@@ -364,7 +364,7 @@ CriticalWorker.schedule(args: [1], queue: :important)
|
|
364
364
|
Cloudtasker comes with three optional features:
|
365
365
|
- Cron Jobs [[docs](docs/CRON_JOBS.md)]: Run jobs at fixed intervals.
|
366
366
|
- Batch Jobs [[docs](docs/BATCH_JOBS.md)]: Run jobs in jobs and track completion of the overall batch.
|
367
|
-
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
367
|
+
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
368
368
|
|
369
369
|
## Working locally
|
370
370
|
|
@@ -381,9 +381,9 @@ You can configure your application to use the Cloudtasker local server using the
|
|
381
381
|
|
382
382
|
Cloudtasker.configure do |config|
|
383
383
|
# ... other options
|
384
|
-
|
384
|
+
|
385
385
|
# Push jobs to redis and let the Cloudtasker local server collect them
|
386
|
-
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
386
|
+
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
387
387
|
# to a non-development environment
|
388
388
|
config.mode = :development
|
389
389
|
end
|
@@ -427,7 +427,7 @@ Cloudtasker.configure do |config|
|
|
427
427
|
|
428
428
|
# Use your ngrok domain as the processor host
|
429
429
|
config.processor_host = 'https://your-tunnel-id.ngrok.io'
|
430
|
-
|
430
|
+
|
431
431
|
# Force Cloudtasker to use Google Cloud Tasks in development
|
432
432
|
config.mode = :production
|
433
433
|
end
|
@@ -508,7 +508,7 @@ end
|
|
508
508
|
See the [Cloudtasker::Worker class](lib/cloudtasker/worker.rb) for more information on attributes available to be logged in your `log_context_processor` proc.
|
509
509
|
|
510
510
|
### Searching logs: Job ID vs Task ID
|
511
|
-
**Note**: `task_id` field is available in logs starting with `0.10.
|
511
|
+
**Note**: `task_id` field is available in logs starting with `0.10.0`
|
512
512
|
|
513
513
|
Job instances are assigned two different different IDs for tracking and logging purpose: `job_id` and `task_id`. These IDs are found in each log entry to facilitate search.
|
514
514
|
|
@@ -571,26 +571,24 @@ By default jobs are retried 25 times - using an exponential backoff - before bei
|
|
571
571
|
|
572
572
|
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.
|
573
573
|
|
574
|
-
**Note**: The `X-CloudTasks-TaskExecutionCount` header sent by Google Cloud Tasks and providing the number of retries outside of `HTTP 503` (instance not reachable) is currently bugged and remains at `0` all the time. Starting with `v0.10.
|
574
|
+
**Note**: The `X-CloudTasks-TaskExecutionCount` header sent by Google Cloud Tasks and providing the number of retries outside of `HTTP 503` (instance not reachable) is currently bugged and remains at `0` all the time. Starting with `v0.10.0` Cloudtasker uses the `X-CloudTasks-TaskRetryCount` header to detect the number of retries. This header includes `HTTP 503` errors which means that if your application is down at some point, jobs will fail and these failures will be counted toward the maximum number of retries. A [bug report](https://issuetracker.google.com/issues/154532072) has been raised with GCP to address this issue. Once fixed we will revert to using `X-CloudTasks-TaskExecutionCount` to avoid counting `HTTP 503` as job failures.
|
575
575
|
|
576
576
|
E.g. Set max number of retries globally via the cloudtasker initializer.
|
577
577
|
```ruby
|
578
578
|
# config/initializers/cloudtasker.rb
|
579
579
|
|
580
580
|
Cloudtasker.configure do |config|
|
581
|
-
#
|
581
|
+
#
|
582
582
|
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
583
583
|
# connectivity error that would be due to the application being down or unreachable.
|
584
|
-
#
|
584
|
+
#
|
585
585
|
# Default: 25
|
586
|
-
#
|
586
|
+
#
|
587
587
|
config.max_retries = 10
|
588
588
|
end
|
589
589
|
```
|
590
590
|
|
591
591
|
E.g. Set max number of retries to 3 on a given worker
|
592
|
-
|
593
|
-
E.g.
|
594
592
|
```ruby
|
595
593
|
# app/workers/some_error_worker.rb
|
596
594
|
|
@@ -600,7 +598,30 @@ class SomeErrorWorker
|
|
600
598
|
# This will override the global setting
|
601
599
|
cloudtasker_options max_retries: 3
|
602
600
|
|
603
|
-
def perform
|
601
|
+
def perform
|
602
|
+
raise(ArgumentError)
|
603
|
+
end
|
604
|
+
end
|
605
|
+
```
|
606
|
+
|
607
|
+
E.g. Evaluate the number of max retries at runtime (Supported since: v0.10.1)
|
608
|
+
```ruby
|
609
|
+
# app/workers/some_error_worker.rb
|
610
|
+
|
611
|
+
class SomeErrorWorker
|
612
|
+
include Cloudtasker::Worker
|
613
|
+
|
614
|
+
# Return the number of max retries based on
|
615
|
+
# worker arguments.
|
616
|
+
#
|
617
|
+
# If this method returns nil then max_retries
|
618
|
+
# will delegate to the class `max_retries` setting or Cloudtasker
|
619
|
+
# `max_retries` configuration otion.
|
620
|
+
def max_retries(arg1, arg2)
|
621
|
+
arg1 == 'foo' ? 13 : nil
|
622
|
+
end
|
623
|
+
|
624
|
+
def perform(arg1, arg2)
|
604
625
|
raise(ArgumentError)
|
605
626
|
end
|
606
627
|
end
|
@@ -618,7 +639,7 @@ require 'cloudtasker/testing'
|
|
618
639
|
# Mode 1 (default): Push jobs to Google Cloud Tasks (env != development) or Redis (env == development)
|
619
640
|
Cloudtasker::Testing.enable!
|
620
641
|
|
621
|
-
# Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
|
642
|
+
# Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
|
622
643
|
# Cloudtasker::Worker.drain_all (process all jobs) or MyWorker.drain (process jobs for specific worker)
|
623
644
|
Cloudtasker::Testing.fake!
|
624
645
|
|
@@ -682,9 +703,9 @@ Below are examples of rspec tests. It is assumed that `Cloudtasker::Testing.fake
|
|
682
703
|
**Example 1**: Testing that a job is scheduled
|
683
704
|
```ruby
|
684
705
|
describe 'worker scheduling'
|
685
|
-
subject(:enqueue_job) { MyWorker.perform_async(1,2) }
|
686
|
-
|
687
|
-
it { expect { enqueue_job }.to change(MyWorker.jobs, :size).by(1) }
|
706
|
+
subject(:enqueue_job) { MyWorker.perform_async(1,2) }
|
707
|
+
|
708
|
+
it { expect { enqueue_job }.to change(MyWorker.jobs, :size).by(1) }
|
688
709
|
end
|
689
710
|
```
|
690
711
|
|
@@ -692,7 +713,7 @@ end
|
|
692
713
|
```ruby
|
693
714
|
describe 'worker calls api'
|
694
715
|
subject { Cloudtasker::Testing.inline! { MyApiWorker.perform_async(1,2) } }
|
695
|
-
|
716
|
+
|
696
717
|
before { expect(MyApi).to receive(:fetch).and_return([]) }
|
697
718
|
it { is_expected.to be_truthy }
|
698
719
|
end
|
@@ -772,7 +793,7 @@ Google Cloud Tasks enforces a limit of 100 KB for job payloads. Taking into acco
|
|
772
793
|
Any excessive job payload (> 100 KB) will raise a `Cloudtasker::MaxTaskSizeExceededError`, both in production and development mode.
|
773
794
|
|
774
795
|
#### Option 1: Use Cloudtasker optional support for payload storage in Redis
|
775
|
-
**Supported since**: `0.10.
|
796
|
+
**Supported since**: `0.10.0`
|
776
797
|
|
777
798
|
Cloudtasker provides optional support for storing argument payloads in Redis instead of sending them to Google Cloud Tasks.
|
778
799
|
|
@@ -2,7 +2,10 @@
|
|
2
2
|
|
3
3
|
module Cloudtasker
|
4
4
|
# Handle execution of workers
|
5
|
-
class WorkerController <
|
5
|
+
class WorkerController < ActionController::Base
|
6
|
+
# No need for CSRF verification on API endpoints
|
7
|
+
skip_before_action :verify_authenticity_token
|
8
|
+
|
6
9
|
# Authenticate all requests.
|
7
10
|
before_action :authenticate!
|
8
11
|
|
data/cloudtasker.gemspec
CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_dependency 'activesupport'
|
32
32
|
spec.add_dependency 'connection_pool'
|
33
33
|
spec.add_dependency 'fugit'
|
34
|
-
spec.add_dependency 'google-cloud-tasks'
|
34
|
+
spec.add_dependency 'google-cloud-tasks', '~> 1.0'
|
35
35
|
spec.add_dependency 'jwt'
|
36
36
|
spec.add_dependency 'redis'
|
37
37
|
spec.add_dependency 'retriable'
|
@@ -43,6 +43,7 @@ Gem::Specification.new do |spec|
|
|
43
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
44
|
spec.add_development_dependency 'rubocop', '0.76.0'
|
45
45
|
spec.add_development_dependency 'rubocop-rspec', '1.37.0'
|
46
|
+
spec.add_development_dependency 'semantic_logger'
|
46
47
|
spec.add_development_dependency 'timecop'
|
47
48
|
spec.add_development_dependency 'webmock'
|
48
49
|
|
@@ -1,11 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
cloudtasker (0.10.
|
4
|
+
cloudtasker (0.10.2)
|
5
5
|
activesupport
|
6
6
|
connection_pool
|
7
7
|
fugit
|
8
|
-
google-cloud-tasks
|
8
|
+
google-cloud-tasks (~> 1.0)
|
9
9
|
jwt
|
10
10
|
redis
|
11
11
|
retriable
|
@@ -13,95 +13,138 @@ PATH
|
|
13
13
|
GEM
|
14
14
|
remote: https://rubygems.org/
|
15
15
|
specs:
|
16
|
-
actioncable (6.
|
17
|
-
actionpack (= 6.
|
16
|
+
actioncable (6.1.4.1)
|
17
|
+
actionpack (= 6.1.4.1)
|
18
|
+
activesupport (= 6.1.4.1)
|
18
19
|
nio4r (~> 2.0)
|
19
20
|
websocket-driver (>= 0.6.1)
|
20
|
-
actionmailbox (6.
|
21
|
-
actionpack (= 6.
|
22
|
-
activejob (= 6.
|
23
|
-
activerecord (= 6.
|
24
|
-
activestorage (= 6.
|
25
|
-
activesupport (= 6.
|
21
|
+
actionmailbox (6.1.4.1)
|
22
|
+
actionpack (= 6.1.4.1)
|
23
|
+
activejob (= 6.1.4.1)
|
24
|
+
activerecord (= 6.1.4.1)
|
25
|
+
activestorage (= 6.1.4.1)
|
26
|
+
activesupport (= 6.1.4.1)
|
26
27
|
mail (>= 2.7.1)
|
27
|
-
actionmailer (6.
|
28
|
-
actionpack (= 6.
|
29
|
-
actionview (= 6.
|
30
|
-
activejob (= 6.
|
28
|
+
actionmailer (6.1.4.1)
|
29
|
+
actionpack (= 6.1.4.1)
|
30
|
+
actionview (= 6.1.4.1)
|
31
|
+
activejob (= 6.1.4.1)
|
32
|
+
activesupport (= 6.1.4.1)
|
31
33
|
mail (~> 2.5, >= 2.5.4)
|
32
34
|
rails-dom-testing (~> 2.0)
|
33
|
-
actionpack (6.
|
34
|
-
actionview (= 6.
|
35
|
-
activesupport (= 6.
|
36
|
-
rack (~> 2.0, >= 2.0.
|
35
|
+
actionpack (6.1.4.1)
|
36
|
+
actionview (= 6.1.4.1)
|
37
|
+
activesupport (= 6.1.4.1)
|
38
|
+
rack (~> 2.0, >= 2.0.9)
|
37
39
|
rack-test (>= 0.6.3)
|
38
40
|
rails-dom-testing (~> 2.0)
|
39
41
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
40
|
-
actiontext (6.
|
41
|
-
actionpack (= 6.
|
42
|
-
activerecord (= 6.
|
43
|
-
activestorage (= 6.
|
44
|
-
activesupport (= 6.
|
42
|
+
actiontext (6.1.4.1)
|
43
|
+
actionpack (= 6.1.4.1)
|
44
|
+
activerecord (= 6.1.4.1)
|
45
|
+
activestorage (= 6.1.4.1)
|
46
|
+
activesupport (= 6.1.4.1)
|
45
47
|
nokogiri (>= 1.8.5)
|
46
|
-
actionview (6.
|
47
|
-
activesupport (= 6.
|
48
|
+
actionview (6.1.4.1)
|
49
|
+
activesupport (= 6.1.4.1)
|
48
50
|
builder (~> 3.1)
|
49
51
|
erubi (~> 1.4)
|
50
52
|
rails-dom-testing (~> 2.0)
|
51
53
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
52
|
-
activejob (6.
|
53
|
-
activesupport (= 6.
|
54
|
+
activejob (6.1.4.1)
|
55
|
+
activesupport (= 6.1.4.1)
|
54
56
|
globalid (>= 0.3.6)
|
55
|
-
activemodel (6.
|
56
|
-
activesupport (= 6.
|
57
|
-
activerecord (6.
|
58
|
-
activemodel (= 6.
|
59
|
-
activesupport (= 6.
|
60
|
-
activestorage (6.
|
61
|
-
actionpack (= 6.
|
62
|
-
activejob (= 6.
|
63
|
-
activerecord (= 6.
|
64
|
-
|
65
|
-
|
57
|
+
activemodel (6.1.4.1)
|
58
|
+
activesupport (= 6.1.4.1)
|
59
|
+
activerecord (6.1.4.1)
|
60
|
+
activemodel (= 6.1.4.1)
|
61
|
+
activesupport (= 6.1.4.1)
|
62
|
+
activestorage (6.1.4.1)
|
63
|
+
actionpack (= 6.1.4.1)
|
64
|
+
activejob (= 6.1.4.1)
|
65
|
+
activerecord (= 6.1.4.1)
|
66
|
+
activesupport (= 6.1.4.1)
|
67
|
+
marcel (~> 1.0.0)
|
68
|
+
mini_mime (>= 1.1.0)
|
69
|
+
activesupport (6.1.4.1)
|
66
70
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
67
|
-
i18n (>=
|
68
|
-
minitest (
|
69
|
-
tzinfo (~>
|
70
|
-
zeitwerk (~> 2.
|
71
|
-
addressable (2.
|
71
|
+
i18n (>= 1.6, < 2)
|
72
|
+
minitest (>= 5.1)
|
73
|
+
tzinfo (~> 2.0)
|
74
|
+
zeitwerk (~> 2.3)
|
75
|
+
addressable (2.8.0)
|
72
76
|
public_suffix (>= 2.0.2, < 5.0)
|
73
|
-
appraisal (2.
|
77
|
+
appraisal (2.4.1)
|
74
78
|
bundler
|
75
79
|
rake
|
76
80
|
thor (>= 0.14.0)
|
77
|
-
ast (2.4.
|
81
|
+
ast (2.4.2)
|
82
|
+
async (1.30.1)
|
83
|
+
console (~> 1.10)
|
84
|
+
nio4r (~> 2.3)
|
85
|
+
timers (~> 4.1)
|
86
|
+
async-http (0.56.5)
|
87
|
+
async (>= 1.25)
|
88
|
+
async-io (>= 1.28)
|
89
|
+
async-pool (>= 0.2)
|
90
|
+
protocol-http (~> 0.22.0)
|
91
|
+
protocol-http1 (~> 0.14.0)
|
92
|
+
protocol-http2 (~> 0.14.0)
|
93
|
+
async-http-faraday (0.11.0)
|
94
|
+
async-http (~> 0.42)
|
95
|
+
faraday
|
96
|
+
async-io (1.32.2)
|
97
|
+
async
|
98
|
+
async-pool (0.3.8)
|
99
|
+
async (>= 1.25)
|
78
100
|
builder (3.2.4)
|
79
|
-
concurrent-ruby (1.1.
|
80
|
-
connection_pool (2.2.
|
81
|
-
|
82
|
-
|
101
|
+
concurrent-ruby (1.1.9)
|
102
|
+
connection_pool (2.2.5)
|
103
|
+
console (1.13.1)
|
104
|
+
fiber-local
|
105
|
+
crack (0.4.5)
|
106
|
+
rexml
|
83
107
|
crass (1.0.6)
|
84
|
-
diff-lcs (1.
|
85
|
-
erubi (1.
|
108
|
+
diff-lcs (1.4.4)
|
109
|
+
erubi (1.10.0)
|
86
110
|
et-orbi (1.2.4)
|
87
111
|
tzinfo
|
88
|
-
faraday (1.0
|
112
|
+
faraday (1.7.0)
|
113
|
+
faraday-em_http (~> 1.0)
|
114
|
+
faraday-em_synchrony (~> 1.0)
|
115
|
+
faraday-excon (~> 1.1)
|
116
|
+
faraday-httpclient (~> 1.0.1)
|
117
|
+
faraday-net_http (~> 1.0)
|
118
|
+
faraday-net_http_persistent (~> 1.1)
|
119
|
+
faraday-patron (~> 1.0)
|
120
|
+
faraday-rack (~> 1.0)
|
89
121
|
multipart-post (>= 1.2, < 3)
|
122
|
+
ruby2_keywords (>= 0.0.4)
|
123
|
+
faraday-em_http (1.0.0)
|
124
|
+
faraday-em_synchrony (1.0.0)
|
125
|
+
faraday-excon (1.1.0)
|
90
126
|
faraday-http-cache (2.2.0)
|
91
127
|
faraday (>= 0.8)
|
92
|
-
|
128
|
+
faraday-httpclient (1.0.1)
|
129
|
+
faraday-net_http (1.0.1)
|
130
|
+
faraday-net_http_persistent (1.2.0)
|
131
|
+
faraday-patron (1.0.0)
|
132
|
+
faraday-rack (1.0.0)
|
133
|
+
fiber-local (1.0.0)
|
134
|
+
fugit (1.5.1)
|
93
135
|
et-orbi (~> 1.1, >= 1.1.8)
|
94
|
-
raabro (~> 1.
|
95
|
-
github_changelog_generator (1.
|
136
|
+
raabro (~> 1.4)
|
137
|
+
github_changelog_generator (1.16.4)
|
96
138
|
activesupport
|
139
|
+
async (>= 1.25.0)
|
140
|
+
async-http-faraday
|
97
141
|
faraday-http-cache
|
98
142
|
multi_json
|
99
143
|
octokit (~> 4.6)
|
100
144
|
rainbow (>= 2.2.1)
|
101
145
|
rake (>= 10.0)
|
102
|
-
|
103
|
-
|
104
|
-
activesupport (>= 4.2.0)
|
146
|
+
globalid (0.5.2)
|
147
|
+
activesupport (>= 5.0)
|
105
148
|
google-cloud-tasks (1.0.0)
|
106
149
|
google-gax (~> 1.3)
|
107
150
|
googleapis-common-protos (>= 1.3.9, < 2.0)
|
@@ -112,114 +155,122 @@ GEM
|
|
112
155
|
googleauth (~> 0.9)
|
113
156
|
grpc (~> 1.24)
|
114
157
|
rly (~> 0.2.3)
|
115
|
-
google-protobuf (3.
|
116
|
-
googleapis-common-protos (1.3.
|
117
|
-
google-protobuf (~> 3.
|
118
|
-
googleapis-common-protos-types (>= 1.0.
|
158
|
+
google-protobuf (3.17.3)
|
159
|
+
googleapis-common-protos (1.3.11)
|
160
|
+
google-protobuf (~> 3.14)
|
161
|
+
googleapis-common-protos-types (>= 1.0.6, < 2.0)
|
119
162
|
grpc (~> 1.27)
|
120
|
-
googleapis-common-protos-types (1.0
|
121
|
-
google-protobuf (~> 3.
|
122
|
-
googleauth (0.
|
163
|
+
googleapis-common-protos-types (1.1.0)
|
164
|
+
google-protobuf (~> 3.14)
|
165
|
+
googleauth (0.17.0)
|
123
166
|
faraday (>= 0.17.3, < 2.0)
|
124
167
|
jwt (>= 1.4, < 3.0)
|
125
168
|
memoist (~> 0.16)
|
126
169
|
multi_json (~> 1.11)
|
127
170
|
os (>= 0.9, < 2.0)
|
128
171
|
signet (~> 0.14)
|
129
|
-
grpc (1.
|
130
|
-
google-protobuf (~> 3.
|
172
|
+
grpc (1.38.0)
|
173
|
+
google-protobuf (~> 3.15)
|
131
174
|
googleapis-common-protos-types (~> 1.0)
|
132
|
-
grpc-google-iam-v1 (0.6.
|
133
|
-
google-protobuf (~> 3.
|
134
|
-
googleapis-common-protos (>= 1.3.
|
175
|
+
grpc-google-iam-v1 (0.6.11)
|
176
|
+
google-protobuf (~> 3.14)
|
177
|
+
googleapis-common-protos (>= 1.3.11, < 2.0)
|
135
178
|
grpc (~> 1.27)
|
136
179
|
hashdiff (1.0.1)
|
137
|
-
i18n (1.8.
|
180
|
+
i18n (1.8.10)
|
138
181
|
concurrent-ruby (~> 1.0)
|
139
182
|
jaro_winkler (1.5.4)
|
140
|
-
jwt (2.2.
|
141
|
-
loofah (2.
|
183
|
+
jwt (2.2.3)
|
184
|
+
loofah (2.12.0)
|
142
185
|
crass (~> 1.0.2)
|
143
186
|
nokogiri (>= 1.5.9)
|
144
187
|
mail (2.7.1)
|
145
188
|
mini_mime (>= 0.1.1)
|
146
|
-
marcel (0.
|
147
|
-
mimemagic (~> 0.3.2)
|
189
|
+
marcel (1.0.1)
|
148
190
|
memoist (0.16.2)
|
149
191
|
method_source (1.0.0)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
multi_json (1.14.1)
|
192
|
+
mini_mime (1.1.1)
|
193
|
+
mini_portile2 (2.6.1)
|
194
|
+
minitest (5.14.4)
|
195
|
+
multi_json (1.15.0)
|
155
196
|
multipart-post (2.1.1)
|
156
|
-
nio4r (2.5.
|
157
|
-
nokogiri (1.
|
158
|
-
mini_portile2 (~> 2.
|
159
|
-
|
197
|
+
nio4r (2.5.8)
|
198
|
+
nokogiri (1.12.3)
|
199
|
+
mini_portile2 (~> 2.6.1)
|
200
|
+
racc (~> 1.4)
|
201
|
+
octokit (4.21.0)
|
160
202
|
faraday (>= 0.9)
|
161
203
|
sawyer (~> 0.8.0, >= 0.5.3)
|
162
|
-
os (1.1.
|
163
|
-
parallel (1.
|
164
|
-
parser (
|
165
|
-
ast (~> 2.4.
|
166
|
-
|
167
|
-
|
168
|
-
|
204
|
+
os (1.1.1)
|
205
|
+
parallel (1.20.1)
|
206
|
+
parser (3.0.2.0)
|
207
|
+
ast (~> 2.4.1)
|
208
|
+
protocol-hpack (1.4.2)
|
209
|
+
protocol-http (0.22.5)
|
210
|
+
protocol-http1 (0.14.2)
|
211
|
+
protocol-http (~> 0.22)
|
212
|
+
protocol-http2 (0.14.2)
|
213
|
+
protocol-hpack (~> 1.4)
|
214
|
+
protocol-http (~> 0.18)
|
215
|
+
public_suffix (4.0.6)
|
216
|
+
raabro (1.4.0)
|
217
|
+
racc (1.5.2)
|
218
|
+
rack (2.2.3)
|
169
219
|
rack-test (1.1.0)
|
170
220
|
rack (>= 1.0, < 3)
|
171
|
-
rails (6.
|
172
|
-
actioncable (= 6.
|
173
|
-
actionmailbox (= 6.
|
174
|
-
actionmailer (= 6.
|
175
|
-
actionpack (= 6.
|
176
|
-
actiontext (= 6.
|
177
|
-
actionview (= 6.
|
178
|
-
activejob (= 6.
|
179
|
-
activemodel (= 6.
|
180
|
-
activerecord (= 6.
|
181
|
-
activestorage (= 6.
|
182
|
-
activesupport (= 6.
|
183
|
-
bundler (>= 1.
|
184
|
-
railties (= 6.
|
221
|
+
rails (6.1.4.1)
|
222
|
+
actioncable (= 6.1.4.1)
|
223
|
+
actionmailbox (= 6.1.4.1)
|
224
|
+
actionmailer (= 6.1.4.1)
|
225
|
+
actionpack (= 6.1.4.1)
|
226
|
+
actiontext (= 6.1.4.1)
|
227
|
+
actionview (= 6.1.4.1)
|
228
|
+
activejob (= 6.1.4.1)
|
229
|
+
activemodel (= 6.1.4.1)
|
230
|
+
activerecord (= 6.1.4.1)
|
231
|
+
activestorage (= 6.1.4.1)
|
232
|
+
activesupport (= 6.1.4.1)
|
233
|
+
bundler (>= 1.15.0)
|
234
|
+
railties (= 6.1.4.1)
|
185
235
|
sprockets-rails (>= 2.0.0)
|
186
236
|
rails-dom-testing (2.0.3)
|
187
237
|
activesupport (>= 4.2.0)
|
188
238
|
nokogiri (>= 1.6)
|
189
|
-
rails-html-sanitizer (1.
|
239
|
+
rails-html-sanitizer (1.4.2)
|
190
240
|
loofah (~> 2.3)
|
191
|
-
railties (6.
|
192
|
-
actionpack (= 6.
|
193
|
-
activesupport (= 6.
|
241
|
+
railties (6.1.4.1)
|
242
|
+
actionpack (= 6.1.4.1)
|
243
|
+
activesupport (= 6.1.4.1)
|
194
244
|
method_source
|
195
|
-
rake (>= 0.
|
196
|
-
thor (
|
245
|
+
rake (>= 0.13)
|
246
|
+
thor (~> 1.0)
|
197
247
|
rainbow (3.0.0)
|
198
|
-
rake (13.0.
|
199
|
-
redis (4.
|
248
|
+
rake (13.0.6)
|
249
|
+
redis (4.4.0)
|
200
250
|
retriable (3.1.2)
|
251
|
+
rexml (3.2.5)
|
201
252
|
rly (0.2.3)
|
202
|
-
rspec (3.
|
203
|
-
rspec-core (~> 3.
|
204
|
-
rspec-expectations (~> 3.
|
205
|
-
rspec-mocks (~> 3.
|
206
|
-
rspec-core (3.
|
207
|
-
rspec-support (~> 3.
|
208
|
-
rspec-expectations (3.
|
253
|
+
rspec (3.10.0)
|
254
|
+
rspec-core (~> 3.10.0)
|
255
|
+
rspec-expectations (~> 3.10.0)
|
256
|
+
rspec-mocks (~> 3.10.0)
|
257
|
+
rspec-core (3.10.1)
|
258
|
+
rspec-support (~> 3.10.0)
|
259
|
+
rspec-expectations (3.10.1)
|
209
260
|
diff-lcs (>= 1.2.0, < 2.0)
|
210
|
-
rspec-support (~> 3.
|
211
|
-
rspec-mocks (3.
|
261
|
+
rspec-support (~> 3.10.0)
|
262
|
+
rspec-mocks (3.10.2)
|
212
263
|
diff-lcs (>= 1.2.0, < 2.0)
|
213
|
-
rspec-support (~> 3.
|
214
|
-
rspec-rails (
|
215
|
-
actionpack (>=
|
216
|
-
activesupport (>=
|
217
|
-
railties (>=
|
218
|
-
rspec-core (~> 3.
|
219
|
-
rspec-expectations (~> 3.
|
220
|
-
rspec-mocks (~> 3.
|
221
|
-
rspec-support (~> 3.
|
222
|
-
rspec-support (3.
|
264
|
+
rspec-support (~> 3.10.0)
|
265
|
+
rspec-rails (5.0.2)
|
266
|
+
actionpack (>= 5.2)
|
267
|
+
activesupport (>= 5.2)
|
268
|
+
railties (>= 5.2)
|
269
|
+
rspec-core (~> 3.10)
|
270
|
+
rspec-expectations (~> 3.10)
|
271
|
+
rspec-mocks (~> 3.10)
|
272
|
+
rspec-support (~> 3.10)
|
273
|
+
rspec-support (3.10.2)
|
223
274
|
rubocop (0.76.0)
|
224
275
|
jaro_winkler (~> 1.5.1)
|
225
276
|
parallel (~> 1.10)
|
@@ -229,38 +280,40 @@ GEM
|
|
229
280
|
unicode-display_width (>= 1.4.0, < 1.7)
|
230
281
|
rubocop-rspec (1.37.0)
|
231
282
|
rubocop (>= 0.68.1)
|
232
|
-
ruby-progressbar (1.
|
233
|
-
|
283
|
+
ruby-progressbar (1.11.0)
|
284
|
+
ruby2_keywords (0.0.5)
|
234
285
|
sawyer (0.8.2)
|
235
286
|
addressable (>= 2.3.5)
|
236
287
|
faraday (> 0.8, < 2.0)
|
237
|
-
|
288
|
+
semantic_logger (4.8.2)
|
289
|
+
concurrent-ruby (~> 1.0)
|
290
|
+
signet (0.15.0)
|
238
291
|
addressable (~> 2.3)
|
239
292
|
faraday (>= 0.17.3, < 2.0)
|
240
293
|
jwt (>= 1.5, < 3.0)
|
241
294
|
multi_json (~> 1.10)
|
242
|
-
sprockets (4.0.
|
295
|
+
sprockets (4.0.2)
|
243
296
|
concurrent-ruby (~> 1.0)
|
244
297
|
rack (> 1, < 3)
|
245
|
-
sprockets-rails (3.2.
|
298
|
+
sprockets-rails (3.2.2)
|
246
299
|
actionpack (>= 4.0)
|
247
300
|
activesupport (>= 4.0)
|
248
301
|
sprockets (>= 3.0.0)
|
249
302
|
sqlite3 (1.4.2)
|
250
|
-
thor (1.0
|
251
|
-
|
252
|
-
|
253
|
-
tzinfo (
|
254
|
-
|
303
|
+
thor (1.1.0)
|
304
|
+
timecop (0.9.4)
|
305
|
+
timers (4.3.3)
|
306
|
+
tzinfo (2.0.4)
|
307
|
+
concurrent-ruby (~> 1.0)
|
255
308
|
unicode-display_width (1.6.1)
|
256
|
-
webmock (3.
|
257
|
-
addressable (>= 2.
|
309
|
+
webmock (3.14.0)
|
310
|
+
addressable (>= 2.8.0)
|
258
311
|
crack (>= 0.3.2)
|
259
312
|
hashdiff (>= 0.4.0, < 2.0.0)
|
260
|
-
websocket-driver (0.7.
|
313
|
+
websocket-driver (0.7.5)
|
261
314
|
websocket-extensions (>= 0.1.0)
|
262
|
-
websocket-extensions (0.1.
|
263
|
-
zeitwerk (2.
|
315
|
+
websocket-extensions (0.1.5)
|
316
|
+
zeitwerk (2.4.2)
|
264
317
|
|
265
318
|
PLATFORMS
|
266
319
|
ruby
|
@@ -277,9 +330,10 @@ DEPENDENCIES
|
|
277
330
|
rspec-rails
|
278
331
|
rubocop (= 0.76.0)
|
279
332
|
rubocop-rspec (= 1.37.0)
|
333
|
+
semantic_logger
|
280
334
|
sqlite3
|
281
335
|
timecop
|
282
336
|
webmock
|
283
337
|
|
284
338
|
BUNDLED WITH
|
285
|
-
2.
|
339
|
+
2.2.9
|