gitlab-mail_room 0.0.3 → 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: 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