gitlab-mail-receiver 0.0.1 → 0.0.2

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
  SHA1:
3
- metadata.gz: 4ff6d380ad5fe4c2cd3ff4066cfa4c371907a20c
4
- data.tar.gz: f3018919650a3ac10e1bc1bbe30a4810430edee8
3
+ metadata.gz: 685533be718d29db08f1ba52b4b3118a166bc7d8
4
+ data.tar.gz: 85d33b4ce3c0ddbaaf1118ba95a7a82e211e6210
5
5
  SHA512:
6
- metadata.gz: 45eaca00bbd532de05c158f898cf3b9e2cfb05d84ad9069b2e18ef661f44b04c884e4d52e02030b9d90356033f751639f765f937e92e59b49bbf554bd3767b93
7
- data.tar.gz: 3c186bb6c06e452f6b1c3d2929f145d3053da9ab311ce1440af0460a7e9c8e7644ffe9743a31a5fba1f693438a8030cf441307cb6a6614ad9dd8bf4bb61d9f04
6
+ metadata.gz: 29173d1720d26e9a9ca378e67c5b6fff0825b51e223093533b56eb56f5298602246ef9f3ecf6d336d9706c787ec17fab90275736ce05d678283e969ce4050390
7
+ data.tar.gz: a4583f133dd241987ec5cd66b108c567a306c433321d3e027843a4ac497a5e71f4e2bb2e312662ca7b4afe7fb6fde549a7b6ac7beca9774c38610c96a092135e
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # GitLab Mail Receiver
2
2
 
3
- This gem can allow your GitLab to receive emails to create Issue comments like GitHub.
3
+ [![Gem Version](https://badge.fury.io/rb/gitlab-mail-receiver.svg)](http://badge.fury.io/rb/gitlab-mail-receiver) [![CI Status](https://travis-ci.org/huacnlee/gitlab-mail-receiver.svg)](https://travis-ci.org/huacnlee/gitlab-mail-receiver)
4
+
5
+ The way of allow your GitLab support Email receive and parse the email content, and find Issue/MergeRequest to create reply.
4
6
 
5
7
  [中文介绍](https://ruby-china.org/topics/27143)
6
8
 
@@ -44,27 +46,52 @@ MailReceiver.configure do
44
46
  self.sender = 'xxx@your-mail-host.com'
45
47
  self.poll_interval = 5
46
48
  self.imap = {
47
- server: 'imap.your-mail-host.com'
48
- port: 993
49
- ssl: true
50
- username: 'xxx@your-mail-host.com'
49
+ server: 'imap.your-mail-host.com',
50
+ port: 993,
51
+ ssl: true,
52
+ username: 'xxx@your-mail-host.com',
51
53
  password: 'your-password'
52
54
  }
53
55
  end
54
56
  ```
55
57
 
56
- ## Run
58
+ ## Run commands
57
59
 
58
60
  ```
59
61
  $ cd gitlab
60
- $ bundle exec gitlab-mail-receiver
62
+ $ bundle exec gitlab-mail-receiver -h
63
+ Commands:
64
+ gitlab-mail-receiver help [COMMAND] # Describe available commands or one specific command
65
+ gitlab-mail-receiver restart # Restart Daemon
66
+ gitlab-mail-receiver start # Start Daemon
67
+ gitlab-mail-receiver stop # Stop Daemon
68
+ gitlab-mail-receiver version # Show version
69
+
70
+ Options:
71
+ [--root=ROOT]
72
+ # Default: ./
73
+ $ bundle exec gitlab-mail-receiver start
74
+ Started gitlab-mail-receiver on pid: 59386
75
+ I, [2015-09-01T13:36:50.813124 #59387] INFO -- : Celluloid 0.17.1.2 is running in BACKPORTED mode. [ http://git.io/vJf3J ]
76
+ ...
61
77
  ```
62
78
 
63
- > NOTE: The daemon log will write to `$rails_root/log/gitlab-mail-receiver.log`
64
-
65
79
  ## Run in production
66
80
 
67
81
  ```
68
82
  $ cd gitlab
69
- $ RAILS_ENV=production nohup bundle exec gitlab-mail-receiver &
83
+ $ RAILS_ENV=production bundle exec gitlab-mail-receiver start -d
84
+ pid_file: ./tmp/pids/gitlab-mail-receiver.pid
85
+ log_file: ./log/gitlab-mail-receiver.log
86
+ Started gitlab-mail-receiver on pid: 58861
87
+ ```
88
+
89
+ > NOTE: The daemon log will write to `$rails_root/log/gitlab-mail-receiver.log`
90
+
91
+ Stop daemon
92
+
93
+ ```bash
94
+ $ bundle exec gitlab-mail-receiver stop
95
+ Stoping gitlab-mail-receiver... [OK]
96
+ ```
70
97
  ```
@@ -1,26 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'bundler/setup'
4
- require_relative '../lib/gitlab-mail-receiver'
2
+ require_relative '../lib/mail-receiver/cli'
5
3
 
6
- app_root = ENV['GITLAB_ROOT'] || "."
7
-
8
- begin
9
- rails_env = ::File.expand_path('./config/environment', app_root)
10
- require rails_env
11
- rescue => e
12
- puts "You need run this command under GitLab root."
13
- return
14
- end
15
-
16
- logger = Logger.new(File.join(app_root, 'log/gitlab-mail-receiver.log'))
17
- Mailman.config.logger = logger
18
- Mailman.config.rails_root = app_root
19
-
20
- logger.info "Starting Mailman ..."
21
- Mailman::Application.run do
22
- to '%user%+%suffix%@%host%' do
23
- @receiver = MailReceiver::Receiver.new(message, logger: logger)
24
- @receiver.process!
25
- end
26
- end
4
+ MailReceiver::CLI.start(ARGV)
@@ -1,6 +1,8 @@
1
+ require_relative './mail-receiver/encoder'
1
2
  require_relative './mail-receiver/body_parser'
2
3
  require_relative './mail-receiver/receiver'
3
4
  require_relative './mail-receiver/reply_to'
5
+
4
6
  require "mailman"
5
7
  require 'active_support/core_ext'
6
8
 
@@ -15,6 +17,10 @@ end
15
17
  Mailman::Configuration.send(:include, MailmanConfig)
16
18
 
17
19
  module MailReceiver
20
+ def self.config
21
+ Mailman.config
22
+ end
23
+
18
24
  def self.configure(&block)
19
25
  Mailman.config.instance_exec(&block)
20
26
  end
@@ -0,0 +1,55 @@
1
+ require_relative './daemon'
2
+ require 'thor'
3
+
4
+ module MailReceiver
5
+ class CLI < Thor
6
+ include Thor::Actions
7
+
8
+ map '-v' => :version
9
+ map "s" => :start
10
+ class_option :root, type: :string, default: './'
11
+
12
+ option :daemon, type: :boolean, aliases: ['d'], default: false
13
+ desc "start", "Start Daemon"
14
+ def start
15
+ MailReceiver::Daemon.init(options) do
16
+ begin
17
+ rails_env = ::File.expand_path('./config/environment', options[:root])
18
+ require rails_env
19
+ rescue => e
20
+ puts "You need run this command under GitLab root."
21
+ return
22
+ end
23
+
24
+ Mailman.config.logger = Logger.new($stdout)
25
+ Mailman.config.rails_root = options[:root]
26
+
27
+ Mailman.config.logger.info "Starting gitlab-mail-receiver..."
28
+ Mailman::Application.run do
29
+ to '%user%+%suffix%@%host%' do
30
+ @receiver = MailReceiver::Receiver.new(message, logger: Mailman.config.logger)
31
+ @receiver.process!
32
+ end
33
+ end
34
+ end
35
+ MailReceiver::Daemon.start_process
36
+ end
37
+
38
+ desc "stop", "Stop Daemon"
39
+ def stop
40
+ MailReceiver::Daemon.init(options)
41
+ MailReceiver::Daemon.stop_process
42
+ end
43
+
44
+ desc "restart", "Restart Daemon"
45
+ def restart
46
+ MailReceiver::Daemon.init(options)
47
+ MailReceiver::Daemon.restart_process
48
+ end
49
+
50
+ desc "version", "Show version"
51
+ def version
52
+ puts "gitlab-mail-receiver #{MailReceiver.version}"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,162 @@
1
+ # Daemon code from: https://github.com/huacnlee/sails
2
+ module MailReceiver
3
+ class Daemon
4
+ class << self
5
+ attr_accessor :options, :daemon, :mode, :app_name, :pid_file, :log_file, :runblock
6
+
7
+ def init(opts = {}, &block)
8
+ self.app_name = 'gitlab-mail-receiver'
9
+ self.pid_file = File.join(opts[:root], "tmp/pids/gitlab-mail-receiver.pid")
10
+ self.log_file = File.join(opts[:root], 'log/gitlab-mail-receiver.log')
11
+ self.daemon = opts[:daemon]
12
+ self.options = opts
13
+ self.runblock = block
14
+ end
15
+
16
+ def read_pid
17
+ if !File.exist?(pid_file)
18
+ return nil
19
+ end
20
+
21
+ pid = File.open(pid_file).read.to_i
22
+ begin
23
+ Process.getpgid(pid)
24
+ rescue
25
+ pid = nil
26
+ end
27
+ pid
28
+ end
29
+
30
+ def start_process
31
+ old_pid = read_pid
32
+ if !old_pid.nil?
33
+ puts colorize("Current have #{app_name} process in running on pid #{old_pid}", :red)
34
+ return
35
+ end
36
+
37
+ # start master process
38
+ @master_pid = fork_master_process!
39
+ File.open(pid_file, "w+") do |f|
40
+ f.puts @master_pid
41
+ end
42
+
43
+ if self.daemon
44
+ puts "pid_file: #{self.pid_file}"
45
+ puts "log_file: #{self.log_file}"
46
+ end
47
+ puts "Started #{app_name} on pid: #{@master_pid}"
48
+ # puts "in init: #{Sails.service.object_id}"
49
+
50
+ if not self.daemon
51
+ Process.waitpid(@master_pid)
52
+ else
53
+ exit
54
+ end
55
+ end
56
+
57
+ def restart_process(options = {})
58
+ old_pid = read_pid
59
+ if old_pid == nil
60
+ puts colorize("#{app_name} process not found on pid #{old_pid}", :red)
61
+ return
62
+ end
63
+
64
+ print "Restarting #{app_name}..."
65
+ Process.kill("USR2", old_pid)
66
+ puts colorize(" [OK]", :green)
67
+ end
68
+
69
+ def fork_master_process!
70
+ fork do
71
+ # WARN: DO NOT CALL Sails IN THIS BLOCK!
72
+ $PROGRAM_NAME = self.app_name + " [master]"
73
+ @child_pid = fork_child_process!
74
+
75
+ Signal.trap("QUIT") do
76
+ Process.kill("QUIT", @child_pid)
77
+ exit
78
+ end
79
+
80
+ Signal.trap("USR2") do
81
+ Process.kill("USR2", @child_pid)
82
+ end
83
+
84
+ loop do
85
+ sleep 1
86
+ begin
87
+ Process.getpgid(@child_pid)
88
+ rescue Errno::ESRCH
89
+ @child_pid = fork_child_process!
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def fork_child_process!
96
+ pid = fork do
97
+ $PROGRAM_NAME = self.app_name + " [worker]"
98
+ Signal.trap("QUIT") do
99
+ exit
100
+ end
101
+
102
+ Signal.trap("USR2") do
103
+ # TODO: reload Sails in current process
104
+ exit
105
+ end
106
+
107
+ if self.daemon == true
108
+ redirect_stdout
109
+ end
110
+
111
+ # puts "in child: #{Sails.service.object_id}"
112
+ self.runblock.call
113
+ end
114
+ # http://ruby-doc.org/core-1.9.3/Process.html#detach-method
115
+ Process.detach(pid)
116
+ pid
117
+ end
118
+
119
+ def stop_process
120
+ pid = read_pid
121
+ if pid.nil?
122
+ puts colorize("#{app_name} process not found, pid #{pid}", :red)
123
+ return
124
+ end
125
+
126
+ print "Stoping #{app_name}..."
127
+ begin
128
+ Process.kill("QUIT", pid)
129
+ ensure
130
+ File.delete(pid_file)
131
+ end
132
+ puts colorize(" [OK]", :green)
133
+ end
134
+
135
+ private
136
+ # Redirect stdout, stderr to log file,
137
+ # If we not do this, stdout will block sails daemon, for example `puts`.
138
+ def redirect_stdout
139
+ redirect_io($stdout, self.log_file)
140
+ redirect_io($stderr, self.log_file)
141
+ end
142
+
143
+ def redirect_io(io, path)
144
+ File.open(path, 'ab') { |fp| io.reopen(fp) } if path
145
+ io.sync = true
146
+ end
147
+
148
+ def colorize(text, c)
149
+ case c
150
+ when :red
151
+ return ["\033[31m",text,"\033[0m"].join("")
152
+ when :green
153
+ return ["\033[32m",text,"\033[0m"].join("")
154
+ when :blue
155
+ return ["\033[34m",text,"\033[0m"].join("")
156
+ else
157
+ return text
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/core_ext/hash'
2
+ require 'rack'
3
+
4
+ module MailReceiver
5
+ module Encoder
6
+ class << self
7
+ def encode(hash)
8
+ hash.to_query
9
+ end
10
+
11
+ def decode(query)
12
+ Rack::Utils.parse_query(query).deep_symbolize_keys
13
+ end
14
+ end
15
+ end
16
+ end
@@ -12,15 +12,19 @@ module MailReceiver
12
12
  end
13
13
 
14
14
  def project_slug
15
- @project_slug ||= id_prefix.split(/!|#/).first
15
+ hash_data[:p]
16
16
  end
17
17
 
18
18
  def issue_id
19
- @issue_id ||= id_prefix.split(/!|#/).last
19
+ hash_data[:id]
20
+ end
21
+
22
+ def target_id
23
+ hash_data[:n]
20
24
  end
21
25
 
22
26
  def merge_request?
23
- @merge_request ||= id_prefix.match('!')
27
+ @merge_request ||= hash_data[:t].downcase == 'm'
24
28
  end
25
29
 
26
30
  def body
@@ -42,14 +46,23 @@ module MailReceiver
42
46
  @prefix ||= to.split('@').first
43
47
  end
44
48
 
49
+ # foo+p=chair/chair&id=123 => { p: chair/chair, id: 123 }
50
+ def hash_data
51
+ return @hash_data if defined?(@hash_data)
52
+ return {} if not prefix.include?('+')
53
+ @hash_data = Encoder.decode(prefix.split('+').last)
54
+ return @hash_data
55
+ end
56
+
45
57
  def inspect
46
- { project_slug: project_slug, issue_id: issue_id, merge_request: merge_request?, to: to, body: body}
58
+ { project_slug: project_slug, issue_id: issue_id, target_id: target_id, merge_request: merge_request?, to: to, body: body}
47
59
  end
48
60
 
49
61
  def project
50
62
  @project ||= Project.find_with_namespace(project_slug)
51
63
  rescue => e
52
64
  logger.warn "Project: #{project_slug} record not found."
65
+ nil
53
66
  end
54
67
 
55
68
  def process!
@@ -67,6 +80,16 @@ module MailReceiver
67
80
  return if note_params.blank?
68
81
 
69
82
  note_params[:project_id] = project.id
83
+
84
+ # relation to target Note
85
+ if target_id
86
+ target_note = project.notes.find_by_id(target_id)
87
+ if target_note
88
+ note_params[:commit_id] = target_note.commit_id
89
+ note_params[:line_code] = target_note.line_code
90
+ end
91
+ end
92
+
70
93
  note_params[:note] = body
71
94
 
72
95
  @note = Notes::CreateService.new(project, current_user, note_params).execute
@@ -99,17 +122,8 @@ module MailReceiver
99
122
  @current_user ||= User.find_by_any_email(from)
100
123
  end
101
124
 
102
- private
103
-
104
125
  def logger
105
126
  @logger ||= Logger.new($stdout)
106
127
  end
107
-
108
- # foo+chair/chair!123 => chair/chair!123
109
- def id_prefix
110
- return @id_prefix if defined?(@id_prefix)
111
- @id_prefix = prefix.split('+').last
112
- return @id_prefix
113
- end
114
128
  end
115
129
  end
@@ -1,11 +1,9 @@
1
1
  require 'mailman'
2
+ require 'json'
2
3
 
3
4
  module MailReceiver
4
5
  module ReplyTo
5
6
  def mail_new_thread(model, headers = {}, &block)
6
- headers['Message-ID'] = message_id(model)
7
- headers['X-GitLab-Project'] = "#{@project.name} | " if @project
8
-
9
7
  # Mail receiver
10
8
  headers[:reply_to] = reply_to_address(model)
11
9
 
@@ -13,10 +11,6 @@ module MailReceiver
13
11
  end
14
12
 
15
13
  def mail_answer_thread(model, headers = {}, &block)
16
- headers['In-Reply-To'] = message_id(model)
17
- headers['References'] = message_id(model)
18
- headers['X-GitLab-Project'] = "#{@project.name} | " if @project
19
-
20
14
  if headers[:subject]
21
15
  headers[:subject].prepend('Re: ')
22
16
  end
@@ -29,39 +23,37 @@ module MailReceiver
29
23
 
30
24
  protected
31
25
  def reply_to_address(model)
32
- able_path = convert_able_path(model)
33
- return default_email_reply_to if able_path.blank?
26
+ hash = convert_able(model)
27
+ return default_email_reply_to if hash.blank?
34
28
  return default_email_reply_to if @project.blank?
35
29
 
36
- slug = "#{@project.path_with_namespace}#{able_path}"
37
30
 
38
- Mailman.config.sender.sub('@', "+#{slug}@")
39
- end
31
+ hash.merge!({ p: @project.path_with_namespace })
40
32
 
33
+ suffix = Encoder.encode(hash)
41
34
 
35
+ Mailman.config.sender.sub('@', "+#{suffix}@")
36
+ end
42
37
 
43
38
  def default_email_reply_to
44
39
  Gitlab.config.gitlab.email_reply_to
45
40
  end
46
41
 
47
- def convert_able_path(model)
48
- if model.class.name == 'Issue'
49
- return "##{model.iid}"
42
+ def convert_able(model)
43
+ res = { id: model.iid }
44
+ if defined?(@note)
45
+ # gitlab/app/mailers/emails/notes.rb 里面会声明 @note
46
+ res.merge!({ n: @note.id })
50
47
  end
51
48
 
52
- if model.class.name == 'MergeRequest'
53
- return "!#{model.iid}"
49
+ if model.class.name == 'Issue'
50
+ res.merge({ t: 'i' })
51
+ return res
54
52
  end
55
53
 
56
-
57
- if model.class.name == 'Note'
58
- if model.noteable_type == 'Issue' && model.noteable
59
- return "##{model.noteable.iid}"
60
- end
61
-
62
- if model.noteable_type == 'MergeRequest' && model.noteable
63
- return "!#{model.noteable.iid}"
64
- end
54
+ if model.class.name == 'MergeRequest'
55
+ res.merge({ t: 't' })
56
+ return res
65
57
  end
66
58
 
67
59
  return nil
@@ -0,0 +1,7 @@
1
+ module MailReceiver
2
+ class << self
3
+ def version
4
+ '0.0.2'
5
+ end
6
+ end
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-mail-receiver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-31 00:00:00.000000000 Z
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mailman
@@ -38,7 +38,36 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '4.0'
41
- description: Allow your GitLab to receive mails to create Issue comment.
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.17.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.17.0
69
+ description: The way of allow your GitLab support Email receive and parse the email
70
+ content, and find Issue/MergeRequest to create reply.
42
71
  email:
43
72
  - huacnlee@gmail.com
44
73
  executables:
@@ -50,10 +79,13 @@ files:
50
79
  - README.md
51
80
  - bin/gitlab-mail-receiver
52
81
  - lib/gitlab-mail-receiver.rb
53
- - lib/gitlab-mail-receiver/railtie.rb
54
82
  - lib/mail-receiver/body_parser.rb
83
+ - lib/mail-receiver/cli.rb
84
+ - lib/mail-receiver/daemon.rb
85
+ - lib/mail-receiver/encoder.rb
55
86
  - lib/mail-receiver/receiver.rb
56
87
  - lib/mail-receiver/reply_to.rb
88
+ - lib/mail-receiver/version.rb
57
89
  homepage: http://github.com/huacnlee/gitlab-mail-receiver
58
90
  licenses:
59
91
  - MIT
@@ -77,6 +109,6 @@ rubyforge_project:
77
109
  rubygems_version: 2.4.7
78
110
  signing_key:
79
111
  specification_version: 4
80
- summary: Allow your GitLab to receive mails to create Issue comment
112
+ summary: Allow your GitLab receive mails to create Issue comment
81
113
  test_files: []
82
114
  has_rdoc:
@@ -1,7 +0,0 @@
1
- module GitLabMailReceiver
2
- module Rails
3
- class Railtie < ::Rails::Railtie
4
-
5
- end
6
- end
7
- end