gitlab-mail_room 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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