gitlab-mail_room 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 367b6c3327d94d056144113b001df4d6255ab16c6aedcfdb400b413eadd3e07e
4
- data.tar.gz: 31ca4ff3025b9fd3ee67b1bb72203d5da46c3367830623f991bcf7b064043857
3
+ metadata.gz: 046d032389d041dfea1d880c47ff567ad6ccd97439ba05db04f49fe8f6fd8596
4
+ data.tar.gz: c1eda45ae5d2cbab863eabb843adfb89694ea563f73101bbf268fba18c25d175
5
5
  SHA512:
6
- metadata.gz: 55b4dce8b77c87a550e088344593490190975fffd13a7c6aed28316cd4524054fd209b476eae29d235413f33bbcc0b0abba1ff6eb3b1c2ba9c008dcc611ba20e
7
- data.tar.gz: d8245f80b571b7c9a6c42c9280fd5d99db4705b4b067d6baeef6cd9dd623de69b44a75d9504ddf0fab9eb5bd566e11e72caefe9674ffff33b6e673149ed8458c
6
+ metadata.gz: 58986de036f80e17d25440c25d4d6bbdb54a0340f865b455847f7a7bf4d2206ae4fefccb91edbb3b56990cf468505501ad1fa30f5223f589483a1e291a59c49a
7
+ data.tar.gz: a085cd90036662bf26516bd5574a64d921cfff6b0728ec3350cac8eba80f75faef08dc0a69bf4909adb6e31a1f62ff9e4f2d62560eff5226e493af777eaa5810
data/.gitlab-ci.yml CHANGED
@@ -10,7 +10,7 @@ services:
10
10
  variables:
11
11
  REDIS_URL: redis://redis:6379
12
12
  script:
13
- - bundle exec rspec spec
13
+ - bundle exec rspec spec
14
14
  before_script:
15
15
  - apt update && apt install -y libicu-dev
16
16
  - ruby -v # Print out ruby version for debugging
@@ -30,3 +30,11 @@ rspec-2.5:
30
30
  rspec-2.6:
31
31
  image: "ruby:2.6"
32
32
  <<: *test
33
+
34
+ rspec-2.7:
35
+ image: "ruby:2.7"
36
+ <<: *test
37
+
38
+ rspec-3.0:
39
+ image: "ruby:3.0"
40
+ <<: *test
data/README.md CHANGED
@@ -55,6 +55,9 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
55
55
 
56
56
  ```yaml
57
57
  ---
58
+ :health_check:
59
+ - :address: "127.0.0.1"
60
+ :port: 8080
58
61
  :mailboxes:
59
62
  -
60
63
  :email: "user1@gmail.com"
@@ -119,6 +122,18 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
119
122
  **Note:** If using `delete_after_delivery`, you also probably want to use
120
123
  `expunge_deleted` unless you really know what you're doing.
121
124
 
125
+ ## health_check ##
126
+
127
+ Requires `webrick` gem to be installed.
128
+
129
+ This option enables an HTTP server that listens to a bind address
130
+ defined by `address` and `port`. The following endpoints are supported:
131
+
132
+ * `/liveness`: This returns a 200 status code with `OK` as the body if
133
+ the server is running. Otherwise, it returns a 500 status code.
134
+
135
+ This feature is not included in upstream `mail_room` and is specific to GitLab.
136
+
122
137
  ## delivery_method ##
123
138
 
124
139
  ### postback ###
data/lib/mail_room.rb CHANGED
@@ -7,8 +7,10 @@ end
7
7
 
8
8
  require "mail_room/version"
9
9
  require "mail_room/configuration"
10
+ require "mail_room/health_check"
10
11
  require "mail_room/mailbox"
11
12
  require "mail_room/mailbox_watcher"
13
+ require "mail_room/message"
12
14
  require "mail_room/connection"
13
15
  require "mail_room/coordinator"
14
16
  require "mail_room/cli"
data/lib/mail_room/cli.rb CHANGED
@@ -42,7 +42,7 @@ module MailRoom
42
42
  end.parse!(args)
43
43
 
44
44
  self.configuration = Configuration.new(options)
45
- self.coordinator = Coordinator.new(configuration.mailboxes)
45
+ self.coordinator = Coordinator.new(configuration.mailboxes, configuration.health_check)
46
46
  end
47
47
 
48
48
  # Start the coordinator running, sets up signal traps
@@ -4,7 +4,7 @@ module MailRoom
4
4
  # Wraps configuration for a set of individual mailboxes with global config
5
5
  # @author Tony Pitale
6
6
  class Configuration
7
- attr_accessor :mailboxes, :log_path, :quiet
7
+ attr_accessor :mailboxes, :log_path, :quiet, :health_check
8
8
 
9
9
  # Initialize a new configuration of mailboxes
10
10
  def initialize(options={})
@@ -18,6 +18,7 @@ module MailRoom
18
18
  config_file = YAML.load(erb.result)
19
19
 
20
20
  set_mailboxes(config_file[:mailboxes])
21
+ set_health_check(config_file[:health_check])
21
22
  rescue => e
22
23
  raise e unless quiet
23
24
  end
@@ -32,5 +33,14 @@ module MailRoom
32
33
  self.mailboxes << Mailbox.new(attributes)
33
34
  end
34
35
  end
36
+
37
+ # Builds the health checker from YAML configuration
38
+ #
39
+ # @param health_check_config nil or a Hash containing :address and :port
40
+ def set_health_check(health_check_config)
41
+ return unless health_check_config
42
+
43
+ self.health_check = HealthCheck.new(health_check_config)
44
+ end
35
45
  end
36
46
  end
@@ -1,197 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MailRoom
2
4
  class Connection
5
+ attr_reader :mailbox, :new_message_handler
6
+
3
7
  def initialize(mailbox)
4
8
  @mailbox = mailbox
5
-
6
- # log in and set the mailbox
7
- reset
8
- setup
9
9
  end
10
10
 
11
11
  def on_new_message(&block)
12
12
  @new_message_handler = block
13
13
  end
14
14
 
15
- # is the connection logged in?
16
- # @return [Boolean]
17
- def logged_in?
18
- @logged_in
19
- end
20
-
21
- # is the connection blocked idling?
22
- # @return [Boolean]
23
- def idling?
24
- @idling
25
- end
26
-
27
- # is the imap connection closed?
28
- # @return [Boolean]
29
- def disconnected?
30
- imap.disconnected?
31
- end
32
-
33
- # is the connection ready to idle?
34
- # @return [Boolean]
35
- def ready_to_idle?
36
- logged_in? && !idling?
37
- end
38
-
39
- def quit
40
- stop_idling
41
- reset
42
- end
43
-
44
15
  def wait
45
- begin
46
- # in case we missed any between idles
47
- process_mailbox
48
-
49
- idle
50
-
51
- process_mailbox
52
- rescue Net::IMAP::Error, IOError => e
53
- @mailbox.logger.warn({ context: @mailbox.context, action: "Disconnected. Resetting...", error: e.message })
54
- reset
55
- setup
56
- end
57
- end
58
-
59
- private
60
-
61
- def reset
62
- @imap = nil
63
- @logged_in = false
64
- @idling = false
65
- end
66
-
67
- def setup
68
- @mailbox.logger.info({ context: @mailbox.context, action: "Starting TLS session" })
69
- start_tls
70
-
71
- @mailbox.logger.info({ context: @mailbox.context, action: "Logging into mailbox" })
72
- log_in
73
-
74
- @mailbox.logger.info({ context: @mailbox.context, action: "Setting mailbox" })
75
- set_mailbox
16
+ raise NotImplementedError
76
17
  end
77
18
 
78
- # build a net/imap connection to google imap
79
- def imap
80
- @imap ||= Net::IMAP.new(@mailbox.host, :port => @mailbox.port, :ssl => @mailbox.ssl_options)
81
- end
82
-
83
- # start a TLS session
84
- def start_tls
85
- imap.starttls if @mailbox.start_tls
86
- end
87
-
88
- # send the imap login command to google
89
- def log_in
90
- imap.login(@mailbox.email, @mailbox.password)
91
- @logged_in = true
92
- end
93
-
94
- # select the mailbox name we want to use
95
- def set_mailbox
96
- imap.select(@mailbox.name) if logged_in?
97
- end
98
-
99
- # is the response for a new message?
100
- # @param response [Net::IMAP::TaggedResponse] the imap response from idle
101
- # @return [Boolean]
102
- def message_exists?(response)
103
- response.respond_to?(:name) && response.name == 'EXISTS'
104
- end
105
-
106
- # @private
107
- def idle_handler
108
- lambda {|response| imap.idle_done if message_exists?(response)}
109
- end
110
-
111
- # maintain an imap idle connection
112
- def idle
113
- return unless ready_to_idle?
114
-
115
- @mailbox.logger.info({ context: @mailbox.context, action: "Idling" })
116
- @idling = true
117
-
118
- imap.idle(@mailbox.idle_timeout, &idle_handler)
119
- ensure
120
- @idling = false
121
- end
122
-
123
- # trigger the idle to finish and wait for the thread to finish
124
- def stop_idling
125
- return unless idling?
126
-
127
- imap.idle_done
128
-
129
- # idling_thread.join
130
- # self.idling_thread = nil
131
- end
132
-
133
- def process_mailbox
134
- return unless @new_message_handler
135
- @mailbox.logger.info({ context: @mailbox.context, action: "Processing started" })
136
-
137
- msgs = new_messages
138
-
139
- any_deletions = msgs.
140
- # deliver each new message, collect success
141
- map(&@new_message_handler).
142
- # include messages with success
143
- zip(msgs).
144
- # filter failed deliveries, collect message
145
- select(&:first).map(&:last).
146
- # scrub delivered messages
147
- map { |message| scrub(message) }.
148
- any?
149
-
150
- imap.expunge if @mailbox.expunge_deleted && any_deletions
151
- end
152
-
153
- def scrub(message)
154
- if @mailbox.delete_after_delivery
155
- imap.store(message.seqno, "+FLAGS", [Net::IMAP::DELETED])
156
- true
157
- end
158
- end
159
-
160
- # @private
161
- # fetch all messages for the new message ids
162
- def new_messages
163
- # Both of these calls may results in
164
- # imap raising an EOFError, we handle
165
- # this exception in the watcher
166
- messages_for_ids(new_message_ids)
167
- end
168
-
169
- # TODO: label messages?
170
- # @imap.store(id, "+X-GM-LABELS", [label])
171
-
172
- # @private
173
- # search for all new (unseen) message ids
174
- # @return [Array<Integer>] message ids
175
- def new_message_ids
176
- # uid_search still leaves messages UNSEEN
177
- all_unread = imap.uid_search(@mailbox.search_command)
178
-
179
- all_unread = all_unread.slice(0, @mailbox.limit_max_unread) unless @mailbox.limit_max_unread.nil? || @mailbox.limit_max_unread == 0
180
-
181
- to_deliver = all_unread.select { |uid| @mailbox.deliver?(uid) }
182
- @mailbox.logger.info({ context: @mailbox.context, action: "Getting new messages", unread: {count: all_unread.count, ids: all_unread}, to_be_delivered: { count: to_deliver.count, ids: to_deliver } })
183
- to_deliver
184
- end
185
-
186
- # @private
187
- # fetch the email for all given ids in RFC822 format
188
- # @param ids [Array<Integer>] list of message ids
189
- # @return [Array<Net::IMAP::FetchData>] the net/imap messages for the given ids
190
- def messages_for_ids(uids)
191
- return [] if uids.empty?
192
-
193
- # uid_fetch marks as SEEN, will not be re-fetched for UNSEEN
194
- imap.uid_fetch(uids, "RFC822")
19
+ def quit
20
+ raise NotImplementedError
195
21
  end
196
22
  end
197
23
  end
@@ -2,13 +2,15 @@ module MailRoom
2
2
  # Coordinate the mailbox watchers
3
3
  # @author Tony Pitale
4
4
  class Coordinator
5
- attr_accessor :watchers, :running
5
+ attr_accessor :watchers, :running, :health_check
6
6
 
7
7
  # build watchers for a set of mailboxes
8
8
  # @params mailboxes [Array<MailRoom::Mailbox>] mailboxes to be watched
9
- def initialize(mailboxes)
9
+ # @params health_check <MailRoom::HealthCheck> health checker to run
10
+ def initialize(mailboxes, health_check = nil)
10
11
  self.watchers = []
11
12
 
13
+ @health_check = health_check
12
14
  mailboxes.each {|box| self.watchers << MailboxWatcher.new(box)}
13
15
  end
14
16
 
@@ -16,10 +18,11 @@ module MailRoom
16
18
 
17
19
  # start each of the watchers to running
18
20
  def run
21
+ health_check&.run
19
22
  watchers.each(&:run)
20
-
23
+
21
24
  self.running = true
22
-
25
+
23
26
  sleep_while_running
24
27
  ensure
25
28
  quit
@@ -27,6 +30,7 @@ module MailRoom
27
30
 
28
31
  # quit each of the watchers when we're done running
29
32
  def quit
33
+ health_check&.quit
30
34
  watchers.each(&:quit)
31
35
  end
32
36
 
@@ -1,7 +1,6 @@
1
1
 
2
2
  module MailRoom
3
3
  class CrashHandler
4
-
5
4
  SUPPORTED_FORMATS = %w[json none]
6
5
 
7
6
  def initialize(stream=STDOUT)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailRoom
4
+ class HealthCheck
5
+ attr_reader :address, :port, :running
6
+
7
+ def initialize(attributes = {})
8
+ @address = attributes[:address]
9
+ @port = attributes[:port]
10
+
11
+ validate!
12
+ end
13
+
14
+ def run
15
+ @server = create_server
16
+
17
+ @thread = Thread.new do
18
+ @server.start
19
+ end
20
+
21
+ @thread.abort_on_exception = true
22
+ @running = true
23
+ end
24
+
25
+ def quit
26
+ @running = false
27
+ @server&.shutdown
28
+ @thread&.join(60)
29
+ end
30
+
31
+ private
32
+
33
+ def validate!
34
+ raise 'No health check address specified' unless address
35
+ raise "Health check port #{@port.to_i} is invalid" unless port.to_i.positive?
36
+ end
37
+
38
+ def create_server
39
+ require 'webrick'
40
+
41
+ server = ::WEBrick::HTTPServer.new(Port: port, BindAddress: address, AccessLog: [])
42
+
43
+ server.mount_proc '/liveness' do |_req, res|
44
+ handle_liveness(res)
45
+ end
46
+
47
+ server
48
+ end
49
+
50
+ def handle_liveness(res)
51
+ if @running
52
+ res.status = 200
53
+ res.body = "OK\n"
54
+ else
55
+ res.status = 500
56
+ res.body = "Not running\n"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailRoom
4
+ module IMAP
5
+ autoload :Connection, 'mail_room/imap/connection'
6
+ autoload :Message, 'mail_room/imap/message'
7
+ end
8
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailRoom
4
+ module IMAP
5
+ class Connection < MailRoom::Connection
6
+ def initialize(mailbox)
7
+ super
8
+
9
+ # log in and set the mailbox
10
+ reset
11
+ setup
12
+ end
13
+
14
+ # is the connection logged in?
15
+ # @return [Boolean]
16
+ def logged_in?
17
+ @logged_in
18
+ end
19
+
20
+ # is the connection blocked idling?
21
+ # @return [Boolean]
22
+ def idling?
23
+ @idling
24
+ end
25
+
26
+ # is the imap connection closed?
27
+ # @return [Boolean]
28
+ def disconnected?
29
+ imap.disconnected?
30
+ end
31
+
32
+ # is the connection ready to idle?
33
+ # @return [Boolean]
34
+ def ready_to_idle?
35
+ logged_in? && !idling?
36
+ end
37
+
38
+ def quit
39
+ stop_idling
40
+ reset
41
+ end
42
+
43
+ def wait
44
+ # in case we missed any between idles
45
+ process_mailbox
46
+
47
+ idle
48
+
49
+ process_mailbox
50
+ rescue Net::IMAP::Error, IOError => e
51
+ @mailbox.logger.warn({ context: @mailbox.context, action: 'Disconnected. Resetting...', error: e.message })
52
+ reset
53
+ setup
54
+ end
55
+
56
+ private
57
+
58
+ def reset
59
+ @imap = nil
60
+ @logged_in = false
61
+ @idling = false
62
+ end
63
+
64
+ def setup
65
+ @mailbox.logger.info({ context: @mailbox.context, action: 'Starting TLS session' })
66
+ start_tls
67
+
68
+ @mailbox.logger.info({ context: @mailbox.context, action: 'Logging into mailbox' })
69
+ log_in
70
+
71
+ @mailbox.logger.info({ context: @mailbox.context, action: 'Setting mailbox' })
72
+ set_mailbox
73
+ end
74
+
75
+ # build a net/imap connection to google imap
76
+ def imap
77
+ @imap ||= Net::IMAP.new(@mailbox.host, port: @mailbox.port, ssl: @mailbox.ssl_options)
78
+ end
79
+
80
+ # start a TLS session
81
+ def start_tls
82
+ imap.starttls if @mailbox.start_tls
83
+ end
84
+
85
+ # send the imap login command to google
86
+ def log_in
87
+ imap.login(@mailbox.email, @mailbox.password)
88
+ @logged_in = true
89
+ end
90
+
91
+ # select the mailbox name we want to use
92
+ def set_mailbox
93
+ imap.select(@mailbox.name) if logged_in?
94
+ end
95
+
96
+ # is the response for a new message?
97
+ # @param response [Net::IMAP::TaggedResponse] the imap response from idle
98
+ # @return [Boolean]
99
+ def message_exists?(response)
100
+ response.respond_to?(:name) && response.name == 'EXISTS'
101
+ end
102
+
103
+ # @private
104
+ def idle_handler
105
+ ->(response) { imap.idle_done if message_exists?(response) }
106
+ end
107
+
108
+ # maintain an imap idle connection
109
+ def idle
110
+ return unless ready_to_idle?
111
+
112
+ @mailbox.logger.info({ context: @mailbox.context, action: 'Idling' })
113
+ @idling = true
114
+
115
+ imap.idle(@mailbox.idle_timeout, &idle_handler)
116
+ ensure
117
+ @idling = false
118
+ end
119
+
120
+ # trigger the idle to finish and wait for the thread to finish
121
+ def stop_idling
122
+ return unless idling?
123
+
124
+ imap.idle_done
125
+
126
+ # idling_thread.join
127
+ # self.idling_thread = nil
128
+ end
129
+
130
+ def process_mailbox
131
+ return unless @new_message_handler
132
+
133
+ @mailbox.logger.info({ context: @mailbox.context, action: 'Processing started' })
134
+
135
+ msgs = new_messages
136
+ any_deletions = msgs.
137
+ # deliver each new message, collect success
138
+ map(&@new_message_handler).
139
+ # include messages with success
140
+ zip(msgs).
141
+ # filter failed deliveries, collect message
142
+ select(&:first).map(&:last).
143
+ # scrub delivered messages
144
+ map { |message| scrub(message) }
145
+ .any?
146
+
147
+ imap.expunge if @mailbox.expunge_deleted && any_deletions
148
+ end
149
+
150
+ def scrub(message)
151
+ if @mailbox.delete_after_delivery
152
+ imap.store(message.seqno, '+FLAGS', [Net::IMAP::DELETED])
153
+ true
154
+ end
155
+ end
156
+
157
+ # @private
158
+ # fetch all messages for the new message ids
159
+ def new_messages
160
+ # Both of these calls may results in
161
+ # imap raising an EOFError, we handle
162
+ # this exception in the watcher
163
+ messages_for_ids(new_message_ids)
164
+ end
165
+
166
+ # TODO: label messages?
167
+ # @imap.store(id, "+X-GM-LABELS", [label])
168
+
169
+ # @private
170
+ # search for all new (unseen) message ids
171
+ # @return [Array<Integer>] message ids
172
+ def new_message_ids
173
+ # uid_search still leaves messages UNSEEN
174
+ all_unread = imap.uid_search(@mailbox.search_command)
175
+
176
+ all_unread = all_unread.slice(0, @mailbox.limit_max_unread) if @mailbox.limit_max_unread.to_i > 0
177
+
178
+ to_deliver = all_unread.select { |uid| @mailbox.deliver?(uid) }
179
+ @mailbox.logger.info({ context: @mailbox.context, action: 'Getting new messages',
180
+ unread: { count: all_unread.count, ids: all_unread }, to_be_delivered: { count: to_deliver.count, ids: to_deliver } })
181
+ to_deliver
182
+ end
183
+
184
+ # @private
185
+ # fetch the email for all given ids in RFC822 format
186
+ # @param ids [Array<Integer>] list of message ids
187
+ # @return [Array<MailRoom::IMAP::Message>] the net/imap messages for the given ids
188
+ def messages_for_ids(uids)
189
+ return [] if uids.empty?
190
+
191
+ # uid_fetch marks as SEEN, will not be re-fetched for UNSEEN
192
+ imap_messages = imap.uid_fetch(uids, 'RFC822')
193
+
194
+ imap_messages.each_with_object([]) do |msg, messages|
195
+ messages << ::MailRoom::IMAP::Message.new(uid: msg.attr['UID'], body: msg.attr['RFC822'], seqno: msg.seqno)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal:true
2
+
3
+ module MailRoom
4
+ module IMAP
5
+ class Message < MailRoom::Message
6
+ attr_reader :seqno
7
+
8
+ def initialize(uid:, body:, seqno:)
9
+ super(uid: uid, body: body)
10
+
11
+ @seqno = seqno
12
+ end
13
+
14
+ def ==(other)
15
+ super && seqno == other.seqno
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,6 @@
1
1
  require "mail_room/delivery"
2
2
  require "mail_room/arbitration"
3
+ require "mail_room/imap"
3
4
 
4
5
  module MailRoom
5
6
  # Mailbox Configuration fields
@@ -102,13 +103,13 @@ module MailRoom
102
103
  arbitrator.deliver?(uid)
103
104
  end
104
105
 
105
- # deliver the imap email message
106
- # @param message [Net::IMAP::FetchData]
106
+ # deliver the email message
107
+ # @param message [MailRoom::Message]
107
108
  def deliver(message)
108
- body = message.attr['RFC822']
109
+ body = message.body
109
110
  return true unless body
110
111
 
111
- logger.info({context: context, uid: message.attr['UID'], action: "sending to deliverer", deliverer: delivery.class.name, byte_size: body.bytesize})
112
+ logger.info({context: context, uid: message.uid, action: "sending to deliverer", deliverer: delivery.class.name, byte_size: body.bytesize})
112
113
  delivery.deliver(body)
113
114
  end
114
115
 
@@ -1,3 +1,5 @@
1
+ require "mail_room/connection"
2
+
1
3
  module MailRoom
2
4
  # TODO: split up between processing and idling?
3
5
 
@@ -61,7 +63,7 @@ module MailRoom
61
63
 
62
64
  private
63
65
  def connection
64
- @connection ||= Connection.new(@mailbox)
66
+ @connection ||= ::MailRoom::IMAP::Connection.new(@mailbox)
65
67
  end
66
68
  end
67
69
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailRoom
4
+ class Message
5
+ attr_reader :uid, :body
6
+
7
+ def initialize(uid:, body:)
8
+ @uid = uid
9
+ @body = body
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class && uid == other.uid && body == other.body
14
+ end
15
+ end
16
+ end
@@ -1,4 +1,4 @@
1
1
  module MailRoom
2
2
  # Current version of gitlab-mail_room gem
3
- VERSION = "0.0.8"
3
+ VERSION = "0.0.9"
4
4
  end
data/mail_room.gemspec CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |gem|
21
21
  gem.add_development_dependency "rspec", "~> 3.9"
22
22
  gem.add_development_dependency "mocha", "~> 1.11"
23
23
  gem.add_development_dependency "simplecov"
24
+ gem.add_development_dependency "webrick", "~> 1.6"
24
25
 
25
26
  # for testing delivery methods
26
27
  gem.add_development_dependency "faraday"
@@ -1,4 +1,7 @@
1
1
  ---
2
+ :health_check:
3
+ :address: "127.0.0.1"
4
+ :port: 8080
2
5
  :mailboxes:
3
6
  -
4
7
  :email: "user1@gmail.com"
data/spec/lib/cli_spec.rb CHANGED
@@ -5,14 +5,14 @@ describe MailRoom::CLI do
5
5
  let!(:configuration) {MailRoom::Configuration.new({:config_path => config_path})}
6
6
  let(:coordinator) {stub(:run => true, :quit => true)}
7
7
  let(:configuration_args) { anything }
8
- let(:coordinator_args) { anything }
8
+ let(:coordinator_args) { [anything, anything] }
9
9
 
10
10
  describe '.new' do
11
11
  let(:args) {["-c", "a path"]}
12
12
 
13
13
  before :each do
14
14
  MailRoom::Configuration.expects(:new).with(configuration_args).returns(configuration)
15
- MailRoom::Coordinator.stubs(:new).with(coordinator_args).returns(coordinator)
15
+ MailRoom::Coordinator.stubs(:new).with(*coordinator_args).returns(coordinator)
16
16
  end
17
17
 
18
18
  context 'with configuration args' do
@@ -27,7 +27,7 @@ describe MailRoom::CLI do
27
27
 
28
28
  context 'with coordinator args' do
29
29
  let(:coordinator_args) do
30
- configuration.mailboxes
30
+ [configuration.mailboxes, anything]
31
31
  end
32
32
 
33
33
  it 'creates a new coordinator with configuration' do
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe MailRoom::Configuration do
4
4
  let(:config_path) {File.expand_path('../fixtures/test_config.yml', File.dirname(__FILE__))}
5
5
 
6
- describe 'set_mailboxes' do
6
+ describe '#initalize' do
7
7
  context 'with config_path' do
8
8
  let(:configuration) { MailRoom::Configuration.new(:config_path => config_path) }
9
9
 
@@ -12,6 +12,10 @@ describe MailRoom::Configuration do
12
12
 
13
13
  expect(configuration.mailboxes).to eq(['mailbox1', 'mailbox2'])
14
14
  end
15
+
16
+ it 'parses health check' do
17
+ expect(configuration.health_check).to be_a(MailRoom::HealthCheck)
18
+ end
15
19
  end
16
20
 
17
21
  context 'without config_path' do
@@ -23,6 +27,10 @@ describe MailRoom::Configuration do
23
27
 
24
28
  expect(configuration.mailboxes).to eq([])
25
29
  end
30
+
31
+ it 'sets the health check to nil' do
32
+ expect(configuration.health_check).to be_nil
33
+ end
26
34
  end
27
35
  end
28
36
  end
@@ -15,6 +15,13 @@ describe MailRoom::Coordinator do
15
15
  coordinator = MailRoom::Coordinator.new([])
16
16
  expect(coordinator.watchers).to eq([])
17
17
  end
18
+
19
+ it 'sets the health check' do
20
+ health_check = MailRoom::HealthCheck.new({ address: '127.0.0.1', port: 8080})
21
+ coordinator = MailRoom::Coordinator.new([], health_check)
22
+
23
+ expect(coordinator.health_check).to eq(health_check)
24
+ end
18
25
  end
19
26
 
20
27
  describe '#run' do
@@ -22,15 +29,22 @@ describe MailRoom::Coordinator do
22
29
  watcher = stub
23
30
  watcher.stubs(:run)
24
31
  watcher.stubs(:quit)
32
+
33
+ health_check = stub
34
+ health_check.stubs(:run)
35
+ health_check.stubs(:quit)
36
+
25
37
  MailRoom::MailboxWatcher.stubs(:new).returns(watcher)
26
- coordinator = MailRoom::Coordinator.new(['mailbox1'])
38
+ coordinator = MailRoom::Coordinator.new(['mailbox1'], health_check)
27
39
  coordinator.stubs(:sleep_while_running)
28
40
  watcher.expects(:run)
29
41
  watcher.expects(:quit)
42
+ health_check.expects(:run)
43
+ health_check.expects(:quit)
30
44
 
31
45
  coordinator.run
32
46
  end
33
-
47
+
34
48
  it 'should go to sleep after running watchers' do
35
49
  coordinator = MailRoom::Coordinator.new([])
36
50
  coordinator.stubs(:running=)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe MailRoom::HealthCheck do
6
+ let(:address) { '127.0.0.1' }
7
+ let(:port) { 8000 }
8
+ let(:params) { { address: address, port: port } }
9
+ subject { described_class.new(params) }
10
+
11
+ describe '#initialize' do
12
+ context 'with valid parameters' do
13
+ it 'validates successfully' do
14
+ expect(subject).to be_a(described_class)
15
+ end
16
+ end
17
+
18
+ context 'with invalid address' do
19
+ let(:address) { nil }
20
+
21
+ it 'raises an error' do
22
+ expect { subject }.to raise_error('No health check address specified')
23
+ end
24
+ end
25
+
26
+ context 'with invalid port' do
27
+ let(:port) { nil }
28
+
29
+ it 'raises an error' do
30
+ expect { subject }.to raise_error('Health check port 0 is invalid')
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#run' do
36
+ it 'sets running to true' do
37
+ server = stub(start: true)
38
+ subject.stubs(:create_server).returns(server)
39
+
40
+ subject.run
41
+
42
+ expect(subject.running).to be true
43
+ end
44
+ end
45
+
46
+ describe '#quit' do
47
+ it 'sets running to false' do
48
+ server = stub(start: true, shutdown: true)
49
+ subject.stubs(:create_server).returns(server)
50
+
51
+ subject.run
52
+ subject.quit
53
+
54
+ expect(subject.running).to be false
55
+ end
56
+ end
57
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MailRoom::Connection do
3
+ describe MailRoom::IMAP::Connection do
4
4
  let(:imap) {stub}
5
5
  let(:mailbox) {build_mailbox(delete_after_delivery: true, expunge_deleted: true)}
6
6
 
@@ -9,7 +9,9 @@ describe MailRoom::Connection do
9
9
  end
10
10
 
11
11
  context "with imap set up" do
12
- let(:connection) {MailRoom::Connection.new(mailbox)}
12
+ let(:connection) {MailRoom::IMAP::Connection.new(mailbox)}
13
+ let(:uid) { 1 }
14
+ let(:seqno) { 8 }
13
15
 
14
16
  before :each do
15
17
  imap.stubs(:starttls)
@@ -36,19 +38,21 @@ describe MailRoom::Connection do
36
38
  end
37
39
 
38
40
  it "waits for a message to process" do
39
- new_message = 'a message'
40
- new_message.stubs(:seqno).returns(8)
41
+ new_message = MailRoom::IMAP::Message.new(uid: uid, body: 'a message', seqno: seqno)
41
42
 
42
43
  connection.on_new_message do |message|
43
44
  expect(message).to eq(new_message)
44
45
  true
45
46
  end
46
47
 
48
+ attr = { 'UID' => uid, 'RFC822' => new_message.body }
49
+ fetch_data = Net::IMAP::FetchData.new(seqno, attr)
50
+
47
51
  imap.expects(:idle)
48
- imap.stubs(:uid_search).with(mailbox.search_command).returns([], [1])
49
- imap.expects(:uid_fetch).with([1], "RFC822").returns([new_message])
50
- mailbox.expects(:deliver?).with(1).returns(true)
51
- imap.expects(:store).with(8, "+FLAGS", [Net::IMAP::DELETED])
52
+ imap.stubs(:uid_search).with(mailbox.search_command).returns([], [uid])
53
+ imap.expects(:uid_fetch).with([uid], "RFC822").returns([fetch_data])
54
+ mailbox.expects(:deliver?).with(uid).returns(true)
55
+ imap.expects(:store).with(seqno, "+FLAGS", [Net::IMAP::DELETED])
52
56
  imap.expects(:expunge).once
53
57
 
54
58
  connection.wait
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal:true
2
+
3
+ require 'spec_helper'
4
+ require 'securerandom'
5
+
6
+ describe MailRoom::IMAP::Message do
7
+ let(:uid) { SecureRandom.hex }
8
+ let(:body) { 'hello world' }
9
+ let(:seqno) { 5 }
10
+
11
+ subject { described_class.new(uid: uid, body: body, seqno: seqno) }
12
+
13
+ describe '#initalize' do
14
+ it 'initializes with required parameters' do
15
+ subject
16
+
17
+ expect(subject.uid).to eq(uid)
18
+ expect(subject.body).to eq(body)
19
+ expect(subject.seqno).to eq(seqno)
20
+ end
21
+ end
22
+
23
+ describe '#==' do
24
+ let(:dup) { described_class.new(uid: uid, body: body, seqno: seqno) }
25
+ let(:base_msg) { MailRoom::Message.new(uid: uid, body: body) }
26
+
27
+ it 'matches an equivalent message' do
28
+ expect(dup == subject).to be true
29
+ end
30
+
31
+ it 'does not match a base message' do
32
+ expect(subject == base_msg).to be false
33
+ expect(base_msg == subject).to be false
34
+ end
35
+ end
36
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MailRoom::Mailbox do
4
- let(:sample_message) { {'RFC822' => 'a message', 'UID' => 123} }
4
+ let(:sample_message) { MailRoom::Message.new(uid: 123, body: 'a message') }
5
5
 
6
6
  describe "#deliver" do
7
7
  context "with arbitration_method of noop" do
@@ -36,9 +36,9 @@ describe MailRoom::Mailbox do
36
36
  noop = stub(:deliver)
37
37
  MailRoom::Delivery['noop'].stubs(:new => noop)
38
38
 
39
- noop.expects(:deliver).with('a message')
39
+ noop.expects(:deliver).with(sample_message.body)
40
40
 
41
- mailbox.deliver(stub(:attr => sample_message))
41
+ mailbox.deliver(sample_message)
42
42
  end
43
43
  end
44
44
 
@@ -48,9 +48,9 @@ describe MailRoom::Mailbox do
48
48
  logger = stub(:deliver)
49
49
  MailRoom::Delivery['logger'].stubs(:new => logger)
50
50
 
51
- logger.expects(:deliver).with('a message')
51
+ logger.expects(:deliver).with(sample_message.body)
52
52
 
53
- mailbox.deliver(stub(:attr => sample_message))
53
+ mailbox.deliver(sample_message)
54
54
  end
55
55
  end
56
56
 
@@ -60,9 +60,9 @@ describe MailRoom::Mailbox do
60
60
  postback = stub(:deliver)
61
61
  MailRoom::Delivery['postback'].stubs(:new => postback)
62
62
 
63
- postback.expects(:deliver).with('a message')
63
+ postback.expects(:deliver).with(sample_message.body)
64
64
 
65
- mailbox.deliver(stub(:attr => sample_message))
65
+ mailbox.deliver(sample_message)
66
66
  end
67
67
  end
68
68
 
@@ -72,9 +72,9 @@ describe MailRoom::Mailbox do
72
72
  letter_opener = stub(:deliver)
73
73
  MailRoom::Delivery['letter_opener'].stubs(:new => letter_opener)
74
74
 
75
- letter_opener.expects(:deliver).with('a message')
75
+ letter_opener.expects(:deliver).with(sample_message.body)
76
76
 
77
- mailbox.deliver(stub(:attr => sample_message))
77
+ mailbox.deliver(sample_message)
78
78
  end
79
79
  end
80
80
 
@@ -85,7 +85,7 @@ describe MailRoom::Mailbox do
85
85
  MailRoom::Delivery['noop'].stubs(:new => noop)
86
86
  noop.expects(:deliver).never
87
87
 
88
- mailbox.deliver(stub(:attr => {'FLAGS' => [:Seen, :Recent]}))
88
+ mailbox.deliver(MailRoom::Message.new(uid: 1234, body: nil))
89
89
  end
90
90
  end
91
91
 
@@ -19,9 +19,9 @@ describe MailRoom::MailboxWatcher do
19
19
  end
20
20
 
21
21
  it 'loops over wait while running' do
22
- connection = MailRoom::Connection.new(mailbox)
22
+ connection = MailRoom::IMAP::Connection.new(mailbox)
23
23
 
24
- MailRoom::Connection.stubs(:new).returns(connection)
24
+ MailRoom::IMAP::Connection.stubs(:new).returns(connection)
25
25
 
26
26
  watcher.expects(:running?).twice.returns(true, false)
27
27
  connection.expects(:wait).once
@@ -41,11 +41,11 @@ describe MailRoom::MailboxWatcher do
41
41
  end
42
42
 
43
43
  it 'closes and waits for the connection' do
44
- connection = MailRoom::Connection.new(mailbox)
44
+ connection = MailRoom::IMAP::Connection.new(mailbox)
45
45
  connection.stubs(:wait)
46
46
  connection.stubs(:quit)
47
47
 
48
- MailRoom::Connection.stubs(:new).returns(connection)
48
+ MailRoom::IMAP::Connection.stubs(:new).returns(connection)
49
49
 
50
50
  watcher.run
51
51
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal:true
2
+
3
+ require 'spec_helper'
4
+ require 'securerandom'
5
+
6
+ describe MailRoom::Message do
7
+ let(:uid) { SecureRandom.hex }
8
+ let(:body) { 'hello world' }
9
+
10
+ subject { described_class.new(uid: uid, body: body) }
11
+
12
+ describe '#initalize' do
13
+ it 'initializes with required parameters' do
14
+ subject
15
+
16
+ expect(subject.uid).to eq(uid)
17
+ expect(subject.body).to eq(body)
18
+ end
19
+ end
20
+
21
+ describe '#==' do
22
+ let(:dup) { described_class.new(uid: uid, body: body) }
23
+
24
+ it 'matches an equivalent message' do
25
+ expect(dup == subject).to be true
26
+ end
27
+
28
+ it 'does not match a message with a different UID' do
29
+ msg = described_class.new(uid: '12345', body: body)
30
+
31
+ expect(subject == msg).to be false
32
+ expect(msg == subject).to be false
33
+ end
34
+ end
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-mail_room
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Pitale
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-11 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webrick
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.6'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: faraday
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -200,9 +214,14 @@ files:
200
214
  - lib/mail_room/delivery/postback.rb
201
215
  - lib/mail_room/delivery/que.rb
202
216
  - lib/mail_room/delivery/sidekiq.rb
217
+ - lib/mail_room/health_check.rb
218
+ - lib/mail_room/imap.rb
219
+ - lib/mail_room/imap/connection.rb
220
+ - lib/mail_room/imap/message.rb
203
221
  - lib/mail_room/logger/structured.rb
204
222
  - lib/mail_room/mailbox.rb
205
223
  - lib/mail_room/mailbox_watcher.rb
224
+ - lib/mail_room/message.rb
206
225
  - lib/mail_room/version.rb
207
226
  - logfile.log
208
227
  - mail_room.gemspec
@@ -210,7 +229,6 @@ files:
210
229
  - spec/lib/arbitration/redis_spec.rb
211
230
  - spec/lib/cli_spec.rb
212
231
  - spec/lib/configuration_spec.rb
213
- - spec/lib/connection_spec.rb
214
232
  - spec/lib/coordinator_spec.rb
215
233
  - spec/lib/crash_handler_spec.rb
216
234
  - spec/lib/delivery/letter_opener_spec.rb
@@ -218,9 +236,13 @@ files:
218
236
  - spec/lib/delivery/postback_spec.rb
219
237
  - spec/lib/delivery/que_spec.rb
220
238
  - spec/lib/delivery/sidekiq_spec.rb
239
+ - spec/lib/health_check_spec.rb
240
+ - spec/lib/imap/connection_spec.rb
241
+ - spec/lib/imap/message_spec.rb
221
242
  - spec/lib/logger/structured_spec.rb
222
243
  - spec/lib/mailbox_spec.rb
223
244
  - spec/lib/mailbox_watcher_spec.rb
245
+ - spec/lib/message_spec.rb
224
246
  - spec/spec_helper.rb
225
247
  homepage: http://github.com/tpitale/mail_room
226
248
  licenses: []
@@ -250,7 +272,6 @@ test_files:
250
272
  - spec/lib/arbitration/redis_spec.rb
251
273
  - spec/lib/cli_spec.rb
252
274
  - spec/lib/configuration_spec.rb
253
- - spec/lib/connection_spec.rb
254
275
  - spec/lib/coordinator_spec.rb
255
276
  - spec/lib/crash_handler_spec.rb
256
277
  - spec/lib/delivery/letter_opener_spec.rb
@@ -258,7 +279,11 @@ test_files:
258
279
  - spec/lib/delivery/postback_spec.rb
259
280
  - spec/lib/delivery/que_spec.rb
260
281
  - spec/lib/delivery/sidekiq_spec.rb
282
+ - spec/lib/health_check_spec.rb
283
+ - spec/lib/imap/connection_spec.rb
284
+ - spec/lib/imap/message_spec.rb
261
285
  - spec/lib/logger/structured_spec.rb
262
286
  - spec/lib/mailbox_spec.rb
263
287
  - spec/lib/mailbox_watcher_spec.rb
288
+ - spec/lib/message_spec.rb
264
289
  - spec/spec_helper.rb