##// END OF EJS Templates
Updates rdm-mailhandler.rb help....
Jean-Philippe Lang -
r14307:1079089e7549
parent child
Show More
@@ -1,196 +1,200
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("--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",
64 "API key (use this option instead of --key if",
64 "API key (use this option instead of --key if",
65 "you don't want the key to appear in the command",
65 "you don't want the key to appear in the command",
66 "line)") {|v| read_key_from_file(v)}
66 "line)") {|v| read_key_from_file(v)}
67 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}
68 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}
69 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
69 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
70 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
70 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
71 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}
72 opts.separator("")
72 opts.separator("")
73 opts.separator("User and permissions options:")
73 opts.separator("User and permissions options:")
74 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",
75 "ACTION can be one of the following values:",
75 "ACTION can be one of the following values:",
76 "* ignore: email is ignored (default)",
76 "* ignore: email is ignored (default)",
77 "* accept: accept as anonymous user",
77 "* accept: accept as anonymous user",
78 "* 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",
79 opts.on("--no-permission-check", "disable permission checking when receiving",
80 "the email") {self.no_permission_check = '1'}
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 set attributes values",
96 "specified by previous options",
96 "ATTRS is a comma separated list of attributes",
97 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
97 "or 'all' to allow all attributes to be",
98 opts.separator("")
98 "overridable (see below for details)") {|v| self.allow_override = v}
99 opts.separator("Overrides:")
99
100 opts.separator(" ATTRS is a comma separated list of attributes among:")
100 opts.separator <<-END_DESC
101 opts.separator(" * project, tracker, status, priority, category, assigned_to, fixed_version,")
101
102 opts.separator(" start_date, due_date, estimated_hours, done_ratio")
102 Overrides:
103 opts.separator(" * custom fields names with underscores instead of spaces (case insensitive)")
103 ATTRS is a comma separated list of attributes among:
104 opts.separator("")
104 * project, tracker, status, priority, category, assigned_to, fixed_version,
105 opts.separator(" Example: --allow_override=project,priority,my_custom_field")
105 start_date, due_date, estimated_hours, done_ratio
106 opts.separator("")
106 * custom fields names with underscores instead of spaces (case insensitive)
107 opts.separator(" If the --project option is not set, project is overridable by default for")
107
108 opts.separator(" emails that create new issues.")
108 Example: --allow_override=project,priority,my_custom_field
109 opts.separator("")
109
110 opts.separator(" You can use --allow_override=all to allow all attributes to be overridable.")
110 If the --project option is not set, project is overridable by default for
111 opts.separator("")
111 emails that create new issues.
112 opts.separator("Examples:")
112
113 opts.separator(" No project specified, emails MUST contain the 'Project' keyword:")
113 You can use --allow_override=all to allow all attributes to be overridable.
114 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
114
115 opts.separator("")
115 Examples:
116 opts.separator(" Fixed project and default tracker specified, but emails can override")
116 No project specified, emails MUST contain the 'Project' keyword:
117 opts.separator(" both tracker and priority attributes using keywords:")
117 rdm-mailhandler.rb --url http://redmine.domain.foo --key secret
118 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
118
119 opts.separator(" --project foo \\")
119 Fixed project and default tracker specified, but emails can override
120 opts.separator(" --tracker bug \\")
120 both tracker and priority attributes using keywords:
121 opts.separator(" --allow-override tracker,priority")
121 rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\
122 --project foo \\
123 --tracker bug \\
124 --allow-override tracker,priority
125 END_DESC
122
126
123 opts.summary_width = 27
127 opts.summary_width = 27
124 end
128 end
125 optparse.parse!
129 optparse.parse!
126
130
127 unless url && key
131 unless url && key
128 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
132 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
129 exit 1
133 exit 1
130 end
134 end
131 end
135 end
132
136
133 def submit(email)
137 def submit(email)
134 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
138 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
135
139
136 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
140 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
137
141
138 data = { 'key' => key, 'email' => email,
142 data = { 'key' => key, 'email' => email,
139 'allow_override' => allow_override,
143 'allow_override' => allow_override,
140 'unknown_user' => unknown_user,
144 'unknown_user' => unknown_user,
141 'default_group' => default_group,
145 'default_group' => default_group,
142 'no_account_notice' => no_account_notice,
146 'no_account_notice' => no_account_notice,
143 'no_notification' => no_notification,
147 'no_notification' => no_notification,
144 'no_permission_check' => no_permission_check}
148 'no_permission_check' => no_permission_check}
145 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
149 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
146
150
147 debug "Posting to #{uri}..."
151 debug "Posting to #{uri}..."
148 begin
152 begin
149 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate, :certificate_bundle => certificate_bundle)
153 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate, :certificate_bundle => certificate_bundle)
150 rescue SystemCallError, IOError => e # connection refused, etc.
154 rescue SystemCallError, IOError => e # connection refused, etc.
151 warn "An error occured while contacting your Redmine server: #{e.message}"
155 warn "An error occured while contacting your Redmine server: #{e.message}"
152 return 75 # temporary failure
156 return 75 # temporary failure
153 end
157 end
154 debug "Response received: #{response.code}"
158 debug "Response received: #{response.code}"
155
159
156 case response.code.to_i
160 case response.code.to_i
157 when 403
161 when 403
158 warn "Request was denied by your Redmine server. " +
162 warn "Request was denied by your Redmine server. " +
159 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
163 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
160 return 77
164 return 77
161 when 422
165 when 422
162 warn "Request was denied by your Redmine server. " +
166 warn "Request was denied by your Redmine server. " +
163 "Possible reasons: email is sent from an invalid email address or is missing some information."
167 "Possible reasons: email is sent from an invalid email address or is missing some information."
164 return 77
168 return 77
165 when 400..499
169 when 400..499
166 warn "Request was denied by your Redmine server (#{response.code})."
170 warn "Request was denied by your Redmine server (#{response.code})."
167 return 77
171 return 77
168 when 500..599
172 when 500..599
169 warn "Failed to contact your Redmine server (#{response.code})."
173 warn "Failed to contact your Redmine server (#{response.code})."
170 return 75
174 return 75
171 when 201
175 when 201
172 debug "Proccessed successfully"
176 debug "Proccessed successfully"
173 return 0
177 return 0
174 else
178 else
175 return 1
179 return 1
176 end
180 end
177 end
181 end
178
182
179 private
183 private
180
184
181 def debug(msg)
185 def debug(msg)
182 puts msg if verbose
186 puts msg if verbose
183 end
187 end
184
188
185 def read_key_from_file(filename)
189 def read_key_from_file(filename)
186 begin
190 begin
187 self.key = File.read(filename).strip
191 self.key = File.read(filename).strip
188 rescue Exception => e
192 rescue Exception => e
189 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
193 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
190 exit 1
194 exit 1
191 end
195 end
192 end
196 end
193 end
197 end
194
198
195 handler = RedmineMailHandler.new
199 handler = RedmineMailHandler.new
196 exit(handler.submit(STDIN.read))
200 exit(handler.submit(STDIN.read))
General Comments 0
You need to be logged in to leave comments. Login now