##// END OF EJS Templates
Updates rdm-mailhandler.rb help....
Jean-Philippe Lang -
r14306:54952a2b2518
parent child
Show More
@@ -1,183 +1,196
1 #!/usr/bin/env ruby
1 #!/usr/bin/env ruby
2 # Redmine - project management software
2 # Redmine - project management software
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require 'net/http'
19 require 'net/http'
20 require 'net/https'
20 require 'net/https'
21 require 'uri'
21 require 'uri'
22 require 'optparse'
22 require 'optparse'
23
23
24 module Net
24 module Net
25 class HTTPS < HTTP
25 class HTTPS < HTTP
26 def self.post_form(url, params, headers, options={})
26 def self.post_form(url, params, headers, options={})
27 request = Post.new(url.path)
27 request = Post.new(url.path)
28 request.form_data = params
28 request.form_data = params
29 request.initialize_http_header(headers)
29 request.initialize_http_header(headers)
30 request.basic_auth url.user, url.password if url.user
30 request.basic_auth url.user, url.password if url.user
31 http = new(url.host, url.port)
31 http = new(url.host, url.port)
32 http.use_ssl = (url.scheme == 'https')
32 http.use_ssl = (url.scheme == 'https')
33 if options[:certificate_bundle]
33 if options[:certificate_bundle]
34 http.ca_file = options[:certificate_bundle]
34 http.ca_file = options[:certificate_bundle]
35 end
35 end
36 if options[:no_check_certificate]
36 if options[:no_check_certificate]
37 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
37 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
38 end
38 end
39 http.start {|h| h.request(request) }
39 http.start {|h| h.request(request) }
40 end
40 end
41 end
41 end
42 end
42 end
43
43
44 class RedmineMailHandler
44 class RedmineMailHandler
45 VERSION = '0.2.3'
45 VERSION = '0.2.3'
46
46
47 attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check,
47 attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check,
48 :url, :key, :no_check_certificate, :certificate_bundle, :no_account_notice, :no_notification
48 :url, :key, :no_check_certificate, :certificate_bundle, :no_account_notice, :no_notification
49
49
50 def initialize
50 def initialize
51 self.issue_attributes = {}
51 self.issue_attributes = {}
52
52
53 optparse = OptionParser.new do |opts|
53 optparse = OptionParser.new do |opts|
54 opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
54 opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
55 opts.separator("")
55 opts.separator("")
56 opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.")
56 opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.")
57 opts.separator("")
57 opts.separator("")
58 opts.separator("Required arguments:")
58 opts.separator("Required arguments:")
59 opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v}
59 opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v}
60 opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v}
60 opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v}
61 opts.separator("")
61 opts.separator("")
62 opts.separator("General options:")
62 opts.separator("General options:")
63 opts.on("--no-permission-check", "disable permission checking when receiving",
64 "the email") {self.no_permission_check = '1'}
65 opts.on("--key-file FILE", "full path to a file that contains your Redmine",
63 opts.on("--key-file FILE", "full path to a file that contains your Redmine",
66 "API key (use this option instead of --key if",
64 "API key (use this option instead of --key if",
67 "you don't want the key to appear in the command",
65 "you don't want the key to appear in the command",
68 "line)") {|v| read_key_from_file(v)}
66 "line)") {|v| read_key_from_file(v)}
69 opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true}
67 opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true}
70 opts.on("--certificate-bundle FILE", "certificate bundle to use") {|v| self.certificate_bundle = v}
68 opts.on("--certificate-bundle FILE", "certificate bundle to use") {|v| self.certificate_bundle = v}
71 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
69 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
72 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
70 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
73 opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit}
71 opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit}
74 opts.separator("")
72 opts.separator("")
75 opts.separator("User creation options:")
73 opts.separator("User and permissions options:")
76 opts.on("--unknown-user ACTION", "how to handle emails from an unknown user",
74 opts.on("--unknown-user ACTION", "how to handle emails from an unknown user",
77 "ACTION can be one of the following values:",
75 "ACTION can be one of the following values:",
78 "* ignore: email is ignored (default)",
76 "* ignore: email is ignored (default)",
79 "* accept: accept as anonymous user",
77 "* accept: accept as anonymous user",
80 "* create: create a user account") {|v| self.unknown_user = v}
78 "* create: create a user account") {|v| self.unknown_user = v}
79 opts.on("--no-permission-check", "disable permission checking when receiving",
80 "the email") {self.no_permission_check = '1'}
81 opts.on("--default-group GROUP", "add created user to GROUP (none by default)",
81 opts.on("--default-group GROUP", "add created user to GROUP (none by default)",
82 "GROUP can be a comma separated list of groups") { |v| self.default_group = v}
82 "GROUP can be a comma separated list of groups") { |v| self.default_group = v}
83 opts.on("--no-account-notice", "don't send account information to the newly",
83 opts.on("--no-account-notice", "don't send account information to the newly",
84 "created user") { |v| self.no_account_notice = '1'}
84 "created user") { |v| self.no_account_notice = '1'}
85 opts.on("--no-notification", "disable email notifications for the created",
85 opts.on("--no-notification", "disable email notifications for the created",
86 "user") { |v| self.no_notification = '1'}
86 "user") { |v| self.no_notification = '1'}
87 opts.separator("")
87 opts.separator("")
88 opts.separator("Issue attributes control options:")
88 opts.separator("Issue attributes control options:")
89 opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v}
89 opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v}
90 opts.on("-s", "--status STATUS", "name of the target status") {|v| self.issue_attributes['status'] = v}
90 opts.on("-s", "--status STATUS", "name of the target status") {|v| self.issue_attributes['status'] = v}
91 opts.on("-t", "--tracker TRACKER", "name of the target tracker") {|v| self.issue_attributes['tracker'] = v}
91 opts.on("-t", "--tracker TRACKER", "name of the target tracker") {|v| self.issue_attributes['tracker'] = v}
92 opts.on( "--category CATEGORY", "name of the target category") {|v| self.issue_attributes['category'] = v}
92 opts.on( "--category CATEGORY", "name of the target category") {|v| self.issue_attributes['category'] = v}
93 opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v}
93 opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v}
94 opts.on( "--private", "create new issues as private") {|v| self.issue_attributes['is_private'] = '1'}
94 opts.on( "--private", "create new issues as private") {|v| self.issue_attributes['is_private'] = '1'}
95 opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
95 opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
96 "specified by previous options",
96 "specified by previous options",
97 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
97 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
98 opts.separator("")
98 opts.separator("")
99 opts.separator("Overrides:")
100 opts.separator(" ATTRS is a comma separated list of attributes among:")
101 opts.separator(" * project, tracker, status, priority, category, assigned_to, fixed_version,")
102 opts.separator(" start_date, due_date, estimated_hours, done_ratio")
103 opts.separator(" * custom fields names with underscores instead of spaces (case insensitive)")
104 opts.separator("")
105 opts.separator(" Example: --allow_override=project,priority,my_custom_field")
106 opts.separator("")
107 opts.separator(" If the --project option is not set, project is overridable by default for")
108 opts.separator(" emails that create new issues.")
109 opts.separator("")
110 opts.separator(" You can use --allow_override=all to allow all attributes to be overridable.")
111 opts.separator("")
99 opts.separator("Examples:")
112 opts.separator("Examples:")
100 opts.separator("No project specified, emails MUST contain the 'Project' keyword:")
113 opts.separator(" No project specified, emails MUST contain the 'Project' keyword:")
101 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
114 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
102 opts.separator("")
115 opts.separator("")
103 opts.separator("Fixed project and default tracker specified, but emails can override")
116 opts.separator(" Fixed project and default tracker specified, but emails can override")
104 opts.separator("both tracker and priority attributes using keywords:")
117 opts.separator(" both tracker and priority attributes using keywords:")
105 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
118 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
106 opts.separator(" --project foo \\")
119 opts.separator(" --project foo \\")
107 opts.separator(" --tracker bug \\")
120 opts.separator(" --tracker bug \\")
108 opts.separator(" --allow-override tracker,priority")
121 opts.separator(" --allow-override tracker,priority")
109
122
110 opts.summary_width = 27
123 opts.summary_width = 27
111 end
124 end
112 optparse.parse!
125 optparse.parse!
113
126
114 unless url && key
127 unless url && key
115 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
128 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
116 exit 1
129 exit 1
117 end
130 end
118 end
131 end
119
132
120 def submit(email)
133 def submit(email)
121 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
134 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
122
135
123 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
136 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
124
137
125 data = { 'key' => key, 'email' => email,
138 data = { 'key' => key, 'email' => email,
126 'allow_override' => allow_override,
139 'allow_override' => allow_override,
127 'unknown_user' => unknown_user,
140 'unknown_user' => unknown_user,
128 'default_group' => default_group,
141 'default_group' => default_group,
129 'no_account_notice' => no_account_notice,
142 'no_account_notice' => no_account_notice,
130 'no_notification' => no_notification,
143 'no_notification' => no_notification,
131 'no_permission_check' => no_permission_check}
144 'no_permission_check' => no_permission_check}
132 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
145 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
133
146
134 debug "Posting to #{uri}..."
147 debug "Posting to #{uri}..."
135 begin
148 begin
136 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate, :certificate_bundle => certificate_bundle)
149 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate, :certificate_bundle => certificate_bundle)
137 rescue SystemCallError, IOError => e # connection refused, etc.
150 rescue SystemCallError, IOError => e # connection refused, etc.
138 warn "An error occured while contacting your Redmine server: #{e.message}"
151 warn "An error occured while contacting your Redmine server: #{e.message}"
139 return 75 # temporary failure
152 return 75 # temporary failure
140 end
153 end
141 debug "Response received: #{response.code}"
154 debug "Response received: #{response.code}"
142
155
143 case response.code.to_i
156 case response.code.to_i
144 when 403
157 when 403
145 warn "Request was denied by your Redmine server. " +
158 warn "Request was denied by your Redmine server. " +
146 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
159 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
147 return 77
160 return 77
148 when 422
161 when 422
149 warn "Request was denied by your Redmine server. " +
162 warn "Request was denied by your Redmine server. " +
150 "Possible reasons: email is sent from an invalid email address or is missing some information."
163 "Possible reasons: email is sent from an invalid email address or is missing some information."
151 return 77
164 return 77
152 when 400..499
165 when 400..499
153 warn "Request was denied by your Redmine server (#{response.code})."
166 warn "Request was denied by your Redmine server (#{response.code})."
154 return 77
167 return 77
155 when 500..599
168 when 500..599
156 warn "Failed to contact your Redmine server (#{response.code})."
169 warn "Failed to contact your Redmine server (#{response.code})."
157 return 75
170 return 75
158 when 201
171 when 201
159 debug "Proccessed successfully"
172 debug "Proccessed successfully"
160 return 0
173 return 0
161 else
174 else
162 return 1
175 return 1
163 end
176 end
164 end
177 end
165
178
166 private
179 private
167
180
168 def debug(msg)
181 def debug(msg)
169 puts msg if verbose
182 puts msg if verbose
170 end
183 end
171
184
172 def read_key_from_file(filename)
185 def read_key_from_file(filename)
173 begin
186 begin
174 self.key = File.read(filename).strip
187 self.key = File.read(filename).strip
175 rescue Exception => e
188 rescue Exception => e
176 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
189 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
177 exit 1
190 exit 1
178 end
191 end
179 end
192 end
180 end
193 end
181
194
182 handler = RedmineMailHandler.new
195 handler = RedmineMailHandler.new
183 exit(handler.submit(STDIN.read))
196 exit(handler.submit(STDIN.read))
General Comments 0
You need to be logged in to leave comments. Login now