mail_room 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +31 -0
- data/CHANGELOG.md +6 -0
- data/README.md +60 -7
- data/lib/mail_room/arbitration/redis.rb +14 -9
- data/lib/mail_room/delivery/sidekiq.rb +17 -10
- data/lib/mail_room/version.rb +1 -1
- data/spec/lib/arbitration/redis_spec.rb +70 -6
- data/spec/lib/delivery/sidekiq_spec.rb +76 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee519fad16a2cfffc77987222062d15f59da6081
|
4
|
+
data.tar.gz: e403cac3cc18fe5a258d892dc69481535e01c4b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b001def60c902e4efab7b4708c655e6c07d2d1b4b94cd1d0a0703c91a987c05ba218834a9e711f377a2c38c524134de73a0b184d3e676b188cd077a1a3cf94c
|
7
|
+
data.tar.gz: 8df7ff1eb78ec3eb6f19d976c5b6fc3a884194daede0af990a4f2c74b2efcdd04b6c8dfd7f9dd2f8e7ee3fd156696ab5104062abfa6ea1f4d640d2cc2a9f7a53
|
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Cache gems in between builds
|
2
|
+
|
3
|
+
.test-template: &test
|
4
|
+
cache:
|
5
|
+
paths:
|
6
|
+
- vendor/ruby
|
7
|
+
script:
|
8
|
+
- bundle exec rspec spec
|
9
|
+
before_script:
|
10
|
+
- apt update && apt install -y libicu-dev
|
11
|
+
- ruby -v # Print out ruby version for debugging
|
12
|
+
# Uncomment next line if your rails app needs a JS runtime:
|
13
|
+
# - apt-get update -q && apt-get install nodejs -yqq
|
14
|
+
- gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image
|
15
|
+
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
|
16
|
+
|
17
|
+
rspec-2.0:
|
18
|
+
image: "ruby:2.0"
|
19
|
+
<<: *test
|
20
|
+
|
21
|
+
rspec-2.1:
|
22
|
+
image: "ruby:2.1"
|
23
|
+
<<: *test
|
24
|
+
|
25
|
+
rspec-2.2:
|
26
|
+
image: "ruby:2.2"
|
27
|
+
<<: *test
|
28
|
+
|
29
|
+
rspec-2.3:
|
30
|
+
image: "ruby:2.3"
|
31
|
+
<<: *test
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -44,7 +44,7 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
|
|
44
44
|
:delivery_options:
|
45
45
|
:delivery_url: "http://localhost:3000/inbox"
|
46
46
|
:delivery_token: "abcdefg"
|
47
|
-
|
47
|
+
|
48
48
|
-
|
49
49
|
:email: "user2@gmail.com"
|
50
50
|
:password: "password"
|
@@ -76,6 +76,20 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
|
|
76
76
|
:delivery_options:
|
77
77
|
:redis_url: redis://localhost:6379
|
78
78
|
:worker: EmailReceiverWorker
|
79
|
+
-
|
80
|
+
:email: "user6@gmail.com"
|
81
|
+
:password: "password"
|
82
|
+
:name: "inbox"
|
83
|
+
:delivery_method: sidekiq
|
84
|
+
:delivery_options:
|
85
|
+
# When pointing to sentinel, follow this sintax for redis URLs:
|
86
|
+
# redis://:<password>@<master-name>/
|
87
|
+
:redis_url: redis://:password@my-redis-sentinel/
|
88
|
+
:sentinels:
|
89
|
+
-
|
90
|
+
:host: 127.0.0.1
|
91
|
+
:port: 26379
|
92
|
+
:worker: EmailReceiverWorker
|
79
93
|
```
|
80
94
|
|
81
95
|
## delivery_method ##
|
@@ -86,12 +100,12 @@ Requires `faraday` gem be installed.
|
|
86
100
|
|
87
101
|
*NOTE:* If you're using Ruby `>= 2.0`, you'll need to use Faraday from `>= 0.8.9`. Versions before this seem to have some weird behavior with `mail_room`.
|
88
102
|
|
89
|
-
The default delivery method, requires `delivery_url` and `delivery_token` in
|
103
|
+
The default delivery method, requires `delivery_url` and `delivery_token` in
|
90
104
|
configuration.
|
91
105
|
|
92
|
-
As the postback is essentially using your app as if it were an API endpoint,
|
93
|
-
you may need to disable forgery protection as you would with a JSON API. In
|
94
|
-
our case, the postback is plaintext, but the protection will still need to be
|
106
|
+
As the postback is essentially using your app as if it were an API endpoint,
|
107
|
+
you may need to disable forgery protection as you would with a JSON API. In
|
108
|
+
our case, the postback is plaintext, but the protection will still need to be
|
95
109
|
disabled.
|
96
110
|
|
97
111
|
### sidekiq ###
|
@@ -105,6 +119,8 @@ Configured with `:delivery_method: sidekiq`.
|
|
105
119
|
Delivery options:
|
106
120
|
- **redis_url**: The Redis server to connect with. Use the same Redis URL that's used to configure Sidekiq.
|
107
121
|
Required, defaults to `redis://localhost:6379`.
|
122
|
+
- **sentinels**: A list of sentinels servers used to provide HA to Redis. (see [Sentinel Support](#sentinel-support))
|
123
|
+
Optional.
|
108
124
|
- **namespace**: The Redis namespace Sidekiq works under. Use the same Redis namespace that's used to configure Sidekiq.
|
109
125
|
Optional.
|
110
126
|
- **queue**: The Sidekiq queue the job is pushed onto. Make sure Sidekiq actually reads off this queue.
|
@@ -224,20 +240,57 @@ When running multiple instances of MailRoom against a single mailbox, to try to
|
|
224
240
|
:delivery_options:
|
225
241
|
:delivery_url: "http://localhost:3000/inbox"
|
226
242
|
:delivery_token: "abcdefg"
|
227
|
-
|
243
|
+
|
228
244
|
:arbitration_method: redis
|
229
245
|
:arbitration_options:
|
230
246
|
# The Redis server to connect with. Defaults to redis://localhost:6379.
|
231
247
|
:redis_url: redis://redis.example.com:6379
|
232
|
-
# The Redis namespace to house the Redis keys under. Optional.
|
248
|
+
# The Redis namespace to house the Redis keys under. Optional.
|
233
249
|
:namespace: mail_room
|
250
|
+
-
|
251
|
+
:email: "user2@gmail.com"
|
252
|
+
:password: "password"
|
253
|
+
:name: "inbox"
|
254
|
+
:delivery_method: postback
|
255
|
+
:delivery_options:
|
256
|
+
:delivery_url: "http://localhost:3000/inbox"
|
257
|
+
:delivery_token: "abcdefg"
|
234
258
|
|
259
|
+
:arbitration_method: redis
|
260
|
+
:arbitration_options:
|
261
|
+
# When pointing to sentinel, follow this sintax for redis URLs:
|
262
|
+
# redis://:<password>@<master-name>/
|
263
|
+
:redis_url: redis://:password@my-redis-sentinel/
|
264
|
+
:sentinels:
|
265
|
+
-
|
266
|
+
:host: 127.0.0.1
|
267
|
+
:port: 26379
|
268
|
+
# The Redis namespace to house the Redis keys under. Optional.
|
269
|
+
:namespace: mail_room
|
235
270
|
```
|
236
271
|
|
237
272
|
**Note:** This will likely never be a _perfect_ system for preventing multiple deliveries of the same message, so I would advise checking the unique `message_id` if you are running in this situation.
|
238
273
|
|
239
274
|
**Note:** There are other scenarios for preventing duplication of messages at scale that _may_ be more appropriate in your particular setup. One such example is using multiple inboxes in reply-by-email situations. Another is to use labels and configure a different `SEARCH` command for each instance of MailRoom.
|
240
275
|
|
276
|
+
## Sentinel Support
|
277
|
+
|
278
|
+
Redis Sentinel provides high availability for Redis. Please read their [documentation](http://redis.io/topics/sentinel)
|
279
|
+
first, before enabling it with mail_room.
|
280
|
+
|
281
|
+
To connect to a Sentinel, you need to setup authentication to both sentinels and redis daemons first, and make sure
|
282
|
+
both are binding to a reachable IP address.
|
283
|
+
|
284
|
+
In mail_room, when you are connecting to a Sentinel, you have to inform the `master-name` and the `password` through
|
285
|
+
`redis_url` param, following this syntax:
|
286
|
+
|
287
|
+
```
|
288
|
+
redis://:<password>@<master-name>/
|
289
|
+
```
|
290
|
+
|
291
|
+
You also have to inform at least one pair of `host` and `port` for a sentinel in your cluster.
|
292
|
+
To have a minimum reliable setup, you need at least `3` sentinel nodes and `3` redis servers (1 master, 2 slaves).
|
293
|
+
|
241
294
|
## Contributing ##
|
242
295
|
|
243
296
|
1. Fork it
|
@@ -3,12 +3,13 @@ require "redis"
|
|
3
3
|
module MailRoom
|
4
4
|
module Arbitration
|
5
5
|
class Redis
|
6
|
-
Options = Struct.new(:redis_url, :namespace) do
|
6
|
+
Options = Struct.new(:redis_url, :namespace, :sentinels) do
|
7
7
|
def initialize(mailbox)
|
8
8
|
redis_url = mailbox.arbitration_options[:redis_url] || "redis://localhost:6379"
|
9
9
|
namespace = mailbox.arbitration_options[:namespace]
|
10
|
+
sentinels = mailbox.arbitration_options[:sentinels]
|
10
11
|
|
11
|
-
super(redis_url, namespace)
|
12
|
+
super(redis_url, namespace, sentinels)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -25,12 +26,12 @@ module MailRoom
|
|
25
26
|
key = "delivered:#{uid}"
|
26
27
|
|
27
28
|
incr = nil
|
28
|
-
|
29
|
-
# At this point, `incr` is a future, which will get its value after
|
29
|
+
client.multi do |c|
|
30
|
+
# At this point, `incr` is a future, which will get its value after
|
30
31
|
# the MULTI command returns.
|
31
|
-
incr =
|
32
|
+
incr = c.incr(key)
|
32
33
|
|
33
|
-
|
34
|
+
c.expire(key, EXPIRATION)
|
34
35
|
end
|
35
36
|
|
36
37
|
# If INCR returns 1, that means the key didn't exist before, which means
|
@@ -42,9 +43,13 @@ module MailRoom
|
|
42
43
|
|
43
44
|
private
|
44
45
|
|
45
|
-
def
|
46
|
-
@
|
47
|
-
|
46
|
+
def client
|
47
|
+
@client ||= begin
|
48
|
+
sentinels = options.sentinels
|
49
|
+
redis_options = { url: options.redis_url }
|
50
|
+
redis_options[:sentinels] = sentinels if sentinels
|
51
|
+
|
52
|
+
redis = ::Redis.new(redis_options)
|
48
53
|
|
49
54
|
namespace = options.namespace
|
50
55
|
if namespace
|
@@ -8,14 +8,15 @@ module MailRoom
|
|
8
8
|
# Sidekiq Delivery method
|
9
9
|
# @author Douwe Maan
|
10
10
|
class Sidekiq
|
11
|
-
Options = Struct.new(:redis_url, :namespace, :queue, :worker) do
|
11
|
+
Options = Struct.new(:redis_url, :namespace, :sentinels, :queue, :worker) do
|
12
12
|
def initialize(mailbox)
|
13
13
|
redis_url = mailbox.delivery_options[:redis_url] || "redis://localhost:6379"
|
14
14
|
namespace = mailbox.delivery_options[:namespace]
|
15
|
+
sentinels = mailbox.delivery_options[:sentinels]
|
15
16
|
queue = mailbox.delivery_options[:queue] || "default"
|
16
17
|
worker = mailbox.delivery_options[:worker]
|
17
18
|
|
18
|
-
super(redis_url, namespace, queue, worker)
|
19
|
+
super(redis_url, namespace, sentinels, queue, worker)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
@@ -40,14 +41,20 @@ module MailRoom
|
|
40
41
|
private
|
41
42
|
|
42
43
|
def client
|
43
|
-
client
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Redis
|
49
|
-
|
50
|
-
|
44
|
+
@client ||= begin
|
45
|
+
sentinels = options.sentinels
|
46
|
+
redis_options = { url: options.redis_url }
|
47
|
+
redis_options[:sentinels] = sentinels if sentinels
|
48
|
+
|
49
|
+
redis = ::Redis.new(redis_options)
|
50
|
+
|
51
|
+
namespace = options.namespace
|
52
|
+
if namespace
|
53
|
+
require 'redis/namespace'
|
54
|
+
Redis::Namespace.new(namespace, redis: redis)
|
55
|
+
else
|
56
|
+
redis
|
57
|
+
end
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
data/lib/mail_room/version.rb
CHANGED
@@ -2,18 +2,18 @@ require 'spec_helper'
|
|
2
2
|
require 'mail_room/arbitration/redis'
|
3
3
|
|
4
4
|
describe MailRoom::Arbitration::Redis do
|
5
|
-
let(:mailbox) {
|
5
|
+
let(:mailbox) {
|
6
6
|
MailRoom::Mailbox.new(
|
7
7
|
arbitration_options: {
|
8
8
|
namespace: "mail_room"
|
9
9
|
}
|
10
|
-
)
|
10
|
+
)
|
11
11
|
}
|
12
12
|
let(:options) { described_class::Options.new(mailbox) }
|
13
13
|
subject { described_class.new(options) }
|
14
14
|
|
15
15
|
# Private, but we don't care.
|
16
|
-
let(:
|
16
|
+
let(:client) { subject.send(:client) }
|
17
17
|
|
18
18
|
describe '#deliver?' do
|
19
19
|
context "when called the first time" do
|
@@ -24,13 +24,13 @@ describe MailRoom::Arbitration::Redis do
|
|
24
24
|
it "increments the delivered flag" do
|
25
25
|
subject.deliver?(123)
|
26
26
|
|
27
|
-
expect(
|
27
|
+
expect(client.get("delivered:123")).to eq("1")
|
28
28
|
end
|
29
29
|
|
30
30
|
it "sets an expiration on the delivered flag" do
|
31
31
|
subject.deliver?(123)
|
32
32
|
|
33
|
-
expect(
|
33
|
+
expect(client.ttl("delivered:123")).to be > 0
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -46,7 +46,7 @@ describe MailRoom::Arbitration::Redis do
|
|
46
46
|
it "increments the delivered flag" do
|
47
47
|
subject.deliver?(123)
|
48
48
|
|
49
|
-
expect(
|
49
|
+
expect(client.get("delivered:123")).to eq("2")
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -60,4 +60,68 @@ describe MailRoom::Arbitration::Redis do
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
63
|
+
|
64
|
+
context 'redis client connection params' do
|
65
|
+
context 'when only url is present' do
|
66
|
+
let(:redis_url) { "redis://redis.example.com:8888" }
|
67
|
+
let(:mailbox) {
|
68
|
+
MailRoom::Mailbox.new(
|
69
|
+
arbitration_options: {
|
70
|
+
redis_url: redis_url
|
71
|
+
}
|
72
|
+
)
|
73
|
+
}
|
74
|
+
|
75
|
+
it 'client has same specified url' do
|
76
|
+
subject.deliver?(123)
|
77
|
+
|
78
|
+
expect(client.options[:url]).to eq redis_url
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'client is a instance of Redis class' do
|
82
|
+
expect(client).to be_a Redis
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when namespace is present' do
|
87
|
+
let(:namespace) { 'mail_room' }
|
88
|
+
let(:mailbox) {
|
89
|
+
MailRoom::Mailbox.new(
|
90
|
+
arbitration_options: {
|
91
|
+
namespace: namespace
|
92
|
+
}
|
93
|
+
)
|
94
|
+
}
|
95
|
+
|
96
|
+
it 'client has same specified namespace' do
|
97
|
+
expect(client.namespace).to eq(namespace)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'client is a instance of RedisNamespace class' do
|
101
|
+
expect(client).to be_a ::Redis::Namespace
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when sentinel is present' do
|
106
|
+
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
|
107
|
+
let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
|
108
|
+
let(:mailbox) {
|
109
|
+
MailRoom::Mailbox.new(
|
110
|
+
arbitration_options: {
|
111
|
+
redis_url: redis_url,
|
112
|
+
sentinels: sentinels
|
113
|
+
}
|
114
|
+
)
|
115
|
+
}
|
116
|
+
|
117
|
+
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
118
|
+
|
119
|
+
it 'client has same specified sentinel params' do
|
120
|
+
expect(client.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
121
|
+
expect(client.client.options[:host]).to eq('sentinel-master')
|
122
|
+
expect(client.client.options[:password]).to eq('mypassword')
|
123
|
+
expect(client.client.options[:sentinels]).to eq(sentinels)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
63
127
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mail_room/delivery/sidekiq'
|
3
|
+
|
4
|
+
describe MailRoom::Delivery::Sidekiq do
|
5
|
+
subject { described_class.new(options) }
|
6
|
+
let(:redis) { subject.send(:client) }
|
7
|
+
let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
|
8
|
+
|
9
|
+
describe '#options' do
|
10
|
+
let(:redis_url) { 'redis://redis.example.com' }
|
11
|
+
|
12
|
+
context 'when only redis_url is specified' do
|
13
|
+
let(:mailbox) {
|
14
|
+
MailRoom::Mailbox.new(
|
15
|
+
delivery_method: :sidekiq,
|
16
|
+
delivery_options: {
|
17
|
+
redis_url: redis_url
|
18
|
+
}
|
19
|
+
)
|
20
|
+
}
|
21
|
+
|
22
|
+
it 'client has same specified redis_url' do
|
23
|
+
expect(redis.options[:url]).to eq(redis_url)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'client is a instance of RedisNamespace class' do
|
27
|
+
expect(redis).to be_a ::Redis
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when namespace is specified' do
|
32
|
+
let(:namespace) { 'sidekiq_mailman' }
|
33
|
+
let(:mailbox) {
|
34
|
+
MailRoom::Mailbox.new(
|
35
|
+
delivery_method: :sidekiq,
|
36
|
+
delivery_options: {
|
37
|
+
redis_url: redis_url,
|
38
|
+
namespace: namespace
|
39
|
+
}
|
40
|
+
)
|
41
|
+
}
|
42
|
+
|
43
|
+
it 'client has same specified namespace' do
|
44
|
+
expect(redis.namespace).to eq(namespace)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'client is a instance of RedisNamespace class' do
|
48
|
+
expect(redis).to be_a ::Redis::Namespace
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when sentinel is specified' do
|
53
|
+
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
|
54
|
+
let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
|
55
|
+
let(:mailbox) {
|
56
|
+
MailRoom::Mailbox.new(
|
57
|
+
delivery_method: :sidekiq,
|
58
|
+
delivery_options: {
|
59
|
+
redis_url: redis_url,
|
60
|
+
sentinels: sentinels
|
61
|
+
}
|
62
|
+
)
|
63
|
+
}
|
64
|
+
|
65
|
+
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
66
|
+
|
67
|
+
it 'client has same specified sentinel params' do
|
68
|
+
expect(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
69
|
+
expect(redis.client.options[:host]).to eq('sentinel-master')
|
70
|
+
expect(redis.client.options[:password]).to eq('mypassword')
|
71
|
+
expect(redis.client.options[:sentinels]).to eq(sentinels)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mail_room
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Pitale
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -187,6 +187,7 @@ extensions: []
|
|
187
187
|
extra_rdoc_files: []
|
188
188
|
files:
|
189
189
|
- ".gitignore"
|
190
|
+
- ".gitlab-ci.yml"
|
190
191
|
- ".ruby-version"
|
191
192
|
- ".travis.yml"
|
192
193
|
- CHANGELOG.md
|
@@ -226,6 +227,7 @@ files:
|
|
226
227
|
- spec/lib/delivery/logger_spec.rb
|
227
228
|
- spec/lib/delivery/postback_spec.rb
|
228
229
|
- spec/lib/delivery/que_spec.rb
|
230
|
+
- spec/lib/delivery/sidekiq_spec.rb
|
229
231
|
- spec/lib/mailbox_spec.rb
|
230
232
|
- spec/lib/mailbox_watcher_spec.rb
|
231
233
|
- spec/spec_helper.rb
|
@@ -264,6 +266,7 @@ test_files:
|
|
264
266
|
- spec/lib/delivery/logger_spec.rb
|
265
267
|
- spec/lib/delivery/postback_spec.rb
|
266
268
|
- spec/lib/delivery/que_spec.rb
|
269
|
+
- spec/lib/delivery/sidekiq_spec.rb
|
267
270
|
- spec/lib/mailbox_spec.rb
|
268
271
|
- spec/lib/mailbox_watcher_spec.rb
|
269
272
|
- spec/spec_helper.rb
|