gitlab-mail_room 0.0.3 → 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: 219f2185b18471c72198ed5c0a55a1428139f7b944f90d1f2428bbc9961e95e9
4
- data.tar.gz: f3fadcc3962ac8325c3c461cda65a81e6c7df40a1b575a303a9073b1773252e9
3
+ metadata.gz: 046d032389d041dfea1d880c47ff567ad6ccd97439ba05db04f49fe8f6fd8596
4
+ data.tar.gz: c1eda45ae5d2cbab863eabb843adfb89694ea563f73101bbf268fba18c25d175
5
5
  SHA512:
6
- metadata.gz: e279af4924d7a87fabedbae769785ed2c5f258091651c2f3089225a0bacb1a2ab54b23931a925304e6b0f9cfaeb88f4d93d97763c663774b27bc0def8fbf1bac
7
- data.tar.gz: 470d47c934a55ccd789c19da2188e889c6d953e5ebb5ff0be9d70be98db3ab770a3646646faf9596f1296660974d29b1ef02ba7c13fe1b6a1cfb36bc363330d6
6
+ metadata.gz: 58986de036f80e17d25440c25d4d6bbdb54a0340f865b455847f7a7bf4d2206ae4fefccb91edbb3b56990cf468505501ad1fa30f5223f589483a1e291a59c49a
7
+ data.tar.gz: a085cd90036662bf26516bd5574a64d921cfff6b0728ec3350cac8eba80f75faef08dc0a69bf4909adb6e31a1f62ff9e4f2d62560eff5226e493af777eaa5810
data/.gitlab-ci.yml CHANGED
@@ -1,11 +1,16 @@
1
1
  # Cache gems in between builds
2
2
 
3
+ services:
4
+ - redis:latest
5
+
3
6
  .test-template: &test
4
7
  cache:
5
8
  paths:
6
9
  - vendor/ruby
10
+ variables:
11
+ REDIS_URL: redis://redis:6379
7
12
  script:
8
- - bundle exec rspec spec
13
+ - bundle exec rspec spec
9
14
  before_script:
10
15
  - apt update && apt install -y libicu-dev
11
16
  - ruby -v # Print out ruby version for debugging
@@ -25,3 +30,11 @@ rspec-2.5:
25
30
  rspec-2.6:
26
31
  image: "ruby:2.6"
27
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
@@ -0,0 +1,7 @@
1
+ # GitLab mail_room release checklist
2
+
3
+ - [ ] create tag in https://gitlab.com/gitlab-org/gitlab-mail_room/
4
+ - [ ] publish gem from this tag to rubygems.org
5
+ - [ ] update https://gitlab.com/gitlab-org/gitlab/-/blob/master/Gemfile to use the new gem version
6
+ - [ ] update gitlab-org/build/CNG to build container images from the new gem (example: https://gitlab.com/gitlab-org/build/CNG/-/merge_requests/451/diffs)
7
+ - [ ] to deploy the new version to gitlab.com, update gitlab-com/gl-infra/k8s-workloads/gitlab-com to pin the new mailroom container image version and assign it the [release managers](https://about.gitlab.com/community/release-managers/) (example: https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/merge_requests/236/diffs)
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.0
1
+ 2.7.2
data/README.md CHANGED
@@ -2,7 +2,21 @@
2
2
 
3
3
  ## Fork notice
4
4
 
5
- mail_room contains some merged functionality that GitLab requires, so this mirror fork is to help us release custom functionality.
5
+ `mail_room` contains some merged functionality that GitLab requires, so this mirror fork is to help us release custom functionality.
6
+
7
+ It needs to be more or less kept up to date with the original, so please feel free to incorporate changes to the upstream repo if you see them.
8
+
9
+ ### Rationale
10
+
11
+ This fork is required to reduce dependency on the upstream releases.
12
+
13
+ The [original JSON structured logging PR](https://github.com/tpitale/mail_room/pull/88) was [released](https://github.com/tpitale/mail_room/commit/deb8fe63bab21c5c3003346961a815d137ff6d2d) and we [bumped the version](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/3719) to incorporate it into [omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab).
14
+ It turned out that when Mailroom crashed out (which it's designed to do), the crash log [wasn't being pulled into elastic in a very useful way](https://github.com/tpitale/mail_room/commits/master) (that is, every line of the stack trace was a new event) so [another PR](https://github.com/tpitale/mail_room/pull/103) was raised.
15
+
16
+ Rather than wait for the author (or bugging him more than once), we [opted for bias for action](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19186#note_290758986) and made a fork of the gem.
17
+ Here it is [in omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/3960).
18
+
19
+ The fork is useful as we can post quick fixes to our own fork and release fixes quickly, and still contribute those fixes upstream to help others.
6
20
 
7
21
  ## README
8
22
 
@@ -41,6 +55,9 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
41
55
 
42
56
  ```yaml
43
57
  ---
58
+ :health_check:
59
+ - :address: "127.0.0.1"
60
+ :port: 8080
44
61
  :mailboxes:
45
62
  -
46
63
  :email: "user1@gmail.com"
@@ -105,6 +122,18 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
105
122
  **Note:** If using `delete_after_delivery`, you also probably want to use
106
123
  `expunge_deleted` unless you really know what you're doing.
107
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
+
108
137
  ## delivery_method ##
109
138
 
110
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
@@ -57,7 +57,7 @@ module MailRoom
57
57
 
58
58
  coordinator.run
59
59
  rescue Exception => e # not just Errors, but includes lower-level Exceptions
60
- CrashHandler.new(error: e, format: @options[:exit_error_format]).handle
60
+ CrashHandler.new.handle(e, @options[:exit_error_format])
61
61
  exit
62
62
  end
63
63
  end
@@ -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,195 +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
53
- @mailbox.logger.warn({ context: @mailbox.context, action: "Disconnected. Resetting..." })
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
76
- end
77
-
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
16
+ raise NotImplementedError
92
17
  end
93
18
 
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
- to_deliver = all_unread.select { |uid| @mailbox.deliver?(uid) }
180
- @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: all_unread } })
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<Net::IMAP::FetchData>] 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.uid_fetch(uids, "RFC822")
19
+ def quit
20
+ raise NotImplementedError
193
21
  end
194
22
  end
195
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,19 +1,15 @@
1
1
 
2
2
  module MailRoom
3
3
  class CrashHandler
4
+ SUPPORTED_FORMATS = %w[json none]
4
5
 
5
- attr_reader :error, :format
6
-
7
- SUPPORTED_FORMATS = %w[json plain]
8
-
9
- def initialize(error:, format:)
10
- @error = error
11
- @format = format
6
+ def initialize(stream=STDOUT)
7
+ @stream = stream
12
8
  end
13
9
 
14
- def handle
10
+ def handle(error, format)
15
11
  if format == 'json'
16
- puts json
12
+ @stream.puts json(error)
17
13
  return
18
14
  end
19
15
 
@@ -22,7 +18,7 @@ module MailRoom
22
18
 
23
19
  private
24
20
 
25
- def json
21
+ def json(error)
26
22
  { time: Time.now, severity: :fatal, message: error.message, backtrace: error.backtrace }.to_json
27
23
  end
28
24
  end