Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 883 lines (777 sloc) 22.706 kb
da3e470 @technoweenie epic tomdoc
technoweenie authored
1 # Represents a single triggered Service call. Each Service tracks the event
2 # type, the configuration data, and the payload for the current call.
354032b @technoweenie PayloadHelpers => PushHelpers
technoweenie authored
3 class Service
b0fbdac @technoweenie convert irc response strings to valid utf-8
technoweenie authored
4 UTF8 = "UTF-8".freeze
cdd4b63 @technoweenie start tracking url/logo_url/maintainers/supporters
technoweenie authored
5 class Contributor < Struct.new(:value)
6 def self.contributor_types
7 @contributor_types ||= []
8 end
9
10 def self.inherited(contributor_type)
11 contributor_types << contributor_type
12 super
13 end
14
15 def self.create(type, keys)
16 klass = contributor_types.detect { |struct| struct.contributor_type == type }
17 if klass
18 Array(keys).map do |key|
19 klass.new(key)
20 end
21 else
22 raise ArgumentError, "Invalid Contributor type #{type.inspect}"
23 end
24 end
25
26 def to_contributor_hash(key)
27 {:type => self.class.contributor_type, key => value}
28 end
29 end
30
50b9bf5 @technoweenie wat
technoweenie authored
31 class EmailContributor < Contributor
cdd4b63 @technoweenie start tracking url/logo_url/maintainers/supporters
technoweenie authored
32 def self.contributor_type
33 :email
34 end
35
36 def to_hash
37 to_contributor_hash(:address)
38 end
39 end
40
41 class GitHubContributor < Contributor
42 def self.contributor_type
43 :github
44 end
45
46 def to_hash
47 to_contributor_hash(:login)
48 end
49 end
50
51 class TwitterContributor < Contributor
52 def self.contributor_type
53 :twitter
54 end
55
56 def to_hash
57 to_contributor_hash(:login)
58 end
59 end
60
61 class WebContributor < Contributor
62 def self.contributor_type
63 :web
64 end
65
66 def to_hash
67 to_contributor_hash(:url)
68 end
69 end
70
fc56e03 @technoweenie rearrange lib dir
technoweenie authored
71 dir = File.expand_path '../service', __FILE__
78802fa @technoweenie ok that wasnt bullshit. shut up
technoweenie authored
72 Dir["#{dir}/events/helpers/*.rb"].each do |helper|
73 require helper
74 end
a0ef374 @technoweenie event is set when the Service is initialized. determines which event…
technoweenie authored
75 Dir["#{dir}/events/*.rb"].each do |helper|
354032b @technoweenie PayloadHelpers => PushHelpers
technoweenie authored
76 require helper
9c2ffd3 @tmm1 split out messaging logic in campfire hook into helpers + tests
tmm1 authored
77 end
78
8c80e7b @technoweenie test existing schema options
technoweenie authored
79 ALL_EVENTS = %w[
80 commit_comment create delete download follow fork fork_apply gist gollum
81 issue_comment issues member public pull_request push team_add watch
ad96b97 @izuzak add deployment events
izuzak authored
82 pull_request_review_comment status release deployment deployment_status
8c80e7b @technoweenie test existing schema options
technoweenie authored
83 ].sort
84
baa597f @technoweenie add a real services class
technoweenie authored
85 class << self
227b0c1 @technoweenie every app should know its root and env
technoweenie authored
86 attr_accessor :root, :env, :host
87
88 %w(development test production staging fi).each do |m|
89 define_method "#{m}?" do
90 env == m
91 end
92 end
93
94 # The SHA1 of the commit that was HEAD when the process started. This is
95 # used in production to determine which version of the app is deployed.
96 #
97 # Returns the 40 char commit SHA1 string.
98 def current_sha
99 @current_sha ||=
100 `cd #{root}; git rev-parse HEAD 2>/dev/null || echo unknown`.
101 chomp.freeze
102 end
103
104 attr_writer :current_sha
105
c45fb90 @technoweenie get the logger ready
technoweenie authored
106 # Returns the Service instance if it responds to this event, or nil.
722c0af @technoweenie don't need the meta crap, it's already sent as part of the payload!
technoweenie authored
107 def receive(event, data, payload = nil)
45df8b3 @technoweenie backport tweaks
technoweenie authored
108 new(event, data, payload).receive
baa597f @technoweenie add a real services class
technoweenie authored
109 end
15566aa @technoweenie make Service#hook_name automatic
technoweenie authored
110
1ce8270 @technoweenie extract service loading to a method
technoweenie authored
111 def load_services
9bde803 @technoweenie load http_post first
technoweenie authored
112 require File.expand_path("../services/http_post", __FILE__)
14f4da0 @technoweenie move services to lib
technoweenie authored
113 path = File.expand_path("../services/**/*.rb", __FILE__)
1ce8270 @technoweenie extract service loading to a method
technoweenie authored
114 Dir[path].each { |lib| require(lib) }
115 end
116
843579e @technoweenie Service tracks all inherited services
technoweenie authored
117 # Tracks the defined services.
118 #
119 # Returns an Array of Service Classes.
120 def services
121 @services ||= []
122 end
123
e2470ba @technoweenie Services can now define what events should be registered when a Hook …
technoweenie authored
124 # Gets the default events that this Service will listen for. This defines
125 # the default event configuration when Hooks are created on GitHub. By
126 # default, GitHub Hooks will only send `push` events.
127 #
128 # Returns an Array of Strings (or Symbols).
129 def default_events(*events)
130 if events.empty?
131 @default_events ||= [:push]
132 else
a9bc40c @technoweenie let people pass arrays to #default_events
technoweenie authored
133 @default_events = events.flatten
e2470ba @technoweenie Services can now define what events should be registered when a Hook …
technoweenie authored
134 end
135 end
136
fe4211c @rkh add info about supported events to services.json
rkh authored
137 # Gets a list of events support by the service. Should be a superset of
138 # default_events.
139 def supported_events
140 return ALL_EVENTS.dup if method_defined? :receive_event
141 ALL_EVENTS.select { |event| method_defined? "receive_#{event}" }
142 end
143
7785065 @technoweenie let services specify their schemas in code.
technoweenie authored
144 # Gets the current schema for the data attributes that this Service
145 # expects. This schema is used to generate the GitHub repository admin
146 # interface. The attribute types loosely to HTML input elements.
147 #
148 # Example:
149 #
150 # class FooService < Service
151 # string :token
152 # end
153 #
154 # FooService.schema
155 # # => [[:string, :token]]
156 #
6530cb4 @technoweenie add list of white listed attributes for logging
technoweenie authored
157 # Returns an Array of [Symbol attribute type, Symbol attribute name] tuples.
7785065 @technoweenie let services specify their schemas in code.
technoweenie authored
158 def schema
159 @schema ||= []
160 end
161
162 # Public: Adds the given attributes as String attributes in the Service's
163 # schema.
164 #
165 # Example:
166 #
167 # class FooService < Service
168 # string :token
169 # end
170 #
171 # FooService.schema
172 # # => [[:string, :token]]
173 #
174 # *attrs - Array of Symbol attribute names.
175 #
176 # Returns nothing.
177 def string(*attrs)
178 add_to_schema :string, attrs
179 end
180
181 # Public: Adds the given attributes as Password attributes in the Service's
182 # schema.
183 #
184 # Example:
185 #
186 # class FooService < Service
187 # password :token
188 # end
189 #
190 # FooService.schema
191 # # => [[:password, :token]]
192 #
193 # *attrs - Array of Symbol attribute names.
194 #
195 # Returns nothing.
196 def password(*attrs)
197 add_to_schema :password, attrs
198 end
199
200 # Public: Adds the given attributes as Boolean attributes in the Service's
201 # schema.
202 #
203 # Example:
204 #
205 # class FooService < Service
206 # boolean :digest
207 # end
208 #
209 # FooService.schema
210 # # => [[:boolean, :digest]]
211 #
212 # *attrs - Array of Symbol attribute names.
213 #
214 # Returns nothing.
215 def boolean(*attrs)
216 add_to_schema :boolean, attrs
217 end
218
6530cb4 @technoweenie add list of white listed attributes for logging
technoweenie authored
219 # Public: get a list of attributes that are approved for logging. Don't
220 # add things like tokens or passwords here.
221 #
222 # Returns an Array of String attribute names.
223 def white_listed
224 @white_listed ||= []
225 end
226
227 def white_list(*attrs)
228 attrs.each do |attr|
229 white_listed << attr.to_s
230 end
231 end
232
233
7785065 @technoweenie let services specify their schemas in code.
technoweenie authored
234 # Adds the given attributes to the Service's data schema.
235 #
236 # type - A Symbol specifying the type: :string, :password, :boolean.
237 # attrs - Array of Symbol attribute names.
238 #
239 # Returns nothing.
240 def add_to_schema(type, attrs)
241 attrs.each do |attr|
242 schema << [type, attr.to_sym]
243 end
244 end
245
9b9019f @technoweenie add service titles
technoweenie authored
246 # Gets the official title of this Service. This is used in any
247 # user-facing documentation regarding the Service.
248 #
249 # Returns a String.
2742533 @technoweenie make title/hook_name more consistent 'schema' methods
technoweenie authored
250 def title(value = nil)
251 if value
1f7f12c @technoweenie mention how to set logo/url
technoweenie authored
252 @title = value
253 else
254 @title ||= begin
255 hook = name.dup
256 hook.sub! /.*:/, ''
257 hook
258 end
9b9019f @technoweenie add service titles
technoweenie authored
259 end
260 end
261
262 # Sets the official title of this Service.
263 #
264 # title - The String title.
265 #
266 # Returns nothing.
267 attr_writer :title
268
269 # Gets the name that identifies this Service type. This is a
270 # short string that is used to uniquely identify the service internally.
da3e470 @technoweenie epic tomdoc
technoweenie authored
271 #
272 # Returns a String.
2742533 @technoweenie make title/hook_name more consistent 'schema' methods
technoweenie authored
273 def hook_name(value = nil)
274 if value
1f7f12c @technoweenie mention how to set logo/url
technoweenie authored
275 @hook_name = value
276 else
277 @hook_name ||= begin
278 hook = name.dup
279 hook.downcase!
280 hook.sub! /.*:/, ''
281 hook
282 end
15566aa @technoweenie make Service#hook_name automatic
technoweenie authored
283 end
284 end
285
58c2d8f @technoweenie add hook_name=
technoweenie authored
286 # Sets the uniquely identifying name for this Service type.
287 #
288 # hook_name - The String name.
289 #
290 # Returns a String.
291 attr_writer :hook_name
292
1f7f12c @technoweenie mention how to set logo/url
technoweenie authored
293 attr_reader :url, :logo_url
294
295 def url(value = nil)
296 if value
297 @url = value
298 else
299 @url
300 end
301 end
302
303 def logo_url(value = nil)
304 if value
305 @logo_url = value
306 else
307 @logo_url
308 end
309 end
cdd4b63 @technoweenie start tracking url/logo_url/maintainers/supporters
technoweenie authored
310
311 def supporters
312 @supporters ||= []
313 end
314
315 def maintainers
316 @maintainers ||= []
317 end
318
319 def supported_by(values)
320 values.each do |contributor_type, value|
321 supporters.push(*Contributor.create(contributor_type, value))
322 end
323 end
324
325 def maintained_by(values)
326 values.each do |contributor_type, value|
327 maintainers.push(*Contributor.create(contributor_type, value))
328 end
329 end
330
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
331 # Public: Gets the Hash of secret configuration options. These are set on
332 # the GitHub servers and never committed to git.
333 #
334 # Returns a Hash.
335 def secrets
45df8b3 @technoweenie backport tweaks
technoweenie authored
336 @secrets ||= begin
337 jabber = ENV['SERVICES_JABBER'].to_s.split("::")
338 twitter = ENV['SERVICES_TWITTER'].to_s.split("::")
339
340 { 'jabber' => {'user' => jabber[0], 'password' => jabber[1] },
341 'boxcar' => {'apikey' => ENV['SERVICES_BOXCAR'].to_s},
342 'twitter' => {'key' => twitter[0], 'secret' => twitter[1]},
343 'bitly' => {'key' => ENV['SERVICES_BITLY'].to_s}
344 }
345 end
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
346 end
347
348 # Public: Gets the Hash of email configuration options. These are set on
349 # the GitHub servers and never committed to git.
350 #
351 # Returns a Hash.
352 def email_config
baa3790 @technoweenie prefer env vars over config files
technoweenie authored
353 @email_config ||= begin
354 hash = (File.exist?(email_config_file) && YAML.load_file(email_config_file)) || {}
355 EMAIL_KEYS.each do |key|
356 env_key = "EMAIL_SMTP_#{key.upcase}"
2e315f1 @sbryant Don't bother setting keys to empty values.
sbryant authored
357 value = ENV[env_key]
358 if value && value != ''
baa3790 @technoweenie prefer env vars over config files
technoweenie authored
359 hash[key] = value
360 end
361 end
362 hash
363 end
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
364 end
baa3790 @technoweenie prefer env vars over config files
technoweenie authored
365 EMAIL_KEYS = %w(address port domain authentication user_name password
366 enable_starttls_auto openssl_verify_mode enable_logging
367 noreply_address)
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
368
369 # Gets the path to the secret configuration file.
370 #
371 # Returns a String path.
372 def secret_file
373 @secret_file ||= File.expand_path("../../config/secrets.yml", __FILE__)
374 end
375
376 # Gets the path to the email configuration file.
377 #
378 # Returns a String path.
379 def email_config_file
380 @email_config_file ||= File.expand_path('../../config/email.yml', __FILE__)
381 end
382
a6bc3ff @technoweenie campfire service can listen for pull_request and issues events
technoweenie authored
383 def objectify(hash)
c51ccff @technoweenie #objectify shouldn't modify the original hash
technoweenie authored
384 struct = OpenStruct.new
a6bc3ff @technoweenie campfire service can listen for pull_request and issues events
technoweenie authored
385 hash.each do |key, value|
c51ccff @technoweenie #objectify shouldn't modify the original hash
technoweenie authored
386 struct.send("#{key}=", value.is_a?(Hash) ? objectify(value) : value)
a6bc3ff @technoweenie campfire service can listen for pull_request and issues events
technoweenie authored
387 end
c51ccff @technoweenie #objectify shouldn't modify the original hash
technoweenie authored
388 struct
a6bc3ff @technoweenie campfire service can listen for pull_request and issues events
technoweenie authored
389 end
390
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
391 # Sets the path to the secrets configuration file.
392 #
393 # secret_file - String path.
394 #
395 # Returns nothing.
396 attr_writer :secret_file
397
398 # Sets the default private configuration data for all Services.
399 #
400 # secrets - Configuration Hash.
401 #
402 # Returns nothing.
403 attr_writer :secrets
404
405 # Sets the path to the email configuration file.
406 #
407 # email_config_file - The String path.
408 #
409 # Returns nothing.
410 attr_writer :email_config_file
411
412 # Sets the default email configuration data for all Services.
413 #
414 # email_config - Email configuration Hash.
415 #
416 # Returns nothing.
417 attr_writer :email_config
418
da3e470 @technoweenie epic tomdoc
technoweenie authored
419 # Binds the current Service to the Sinatra App.
420 #
421 # Returns nothing.
15566aa @technoweenie make Service#hook_name automatic
technoweenie authored
422 def inherited(svc)
843579e @technoweenie Service tracks all inherited services
technoweenie authored
423 Service.services << svc
15566aa @technoweenie make Service#hook_name automatic
technoweenie authored
424 super
425 end
baa597f @technoweenie add a real services class
technoweenie authored
426 end
427
227b0c1 @technoweenie every app should know its root and env
technoweenie authored
428 # Determine #root from this file's location
429 self.root ||= File.expand_path('../..', __FILE__)
430 self.host ||= `hostname -s`.chomp
431
432 # Determine #env from the environment
433 self.env ||= ENV['RACK_ENV'] || ENV['GEM_STRICT'] ? 'production' : 'development'
434
da3e470 @technoweenie epic tomdoc
technoweenie authored
435 # Public: Gets the configuration data for this Service instance.
436 #
437 # Returns a Hash.
7ce5cad @technoweenie @data and @payload are ivars of Service
technoweenie authored
438 attr_reader :data
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
439
da3e470 @technoweenie epic tomdoc
technoweenie authored
440 # Public: Gets the unique payload data for this Service instance.
441 #
442 # Returns a Hash.
7ce5cad @technoweenie @data and @payload are ivars of Service
technoweenie authored
443 attr_reader :payload
baa597f @technoweenie add a real services class
technoweenie authored
444
da3e470 @technoweenie epic tomdoc
technoweenie authored
445 # Public: Gets the identifier for the Service's event.
446 #
447 # Returns a Symbol.
a0ef374 @technoweenie event is set when the Service is initialized. determines which event…
technoweenie authored
448 attr_reader :event
449
8da6bf3 @technoweenie add Service#delivery_guid
technoweenie authored
450 # Optional String unique identifier for this exact event.
451 attr_accessor :delivery_guid
452
da3e470 @technoweenie epic tomdoc
technoweenie authored
453 # Sets the Faraday::Connection for this Service instance.
454 #
455 # http - New Faraday::Connection instance.
456 #
457 # Returns a Faraday::Connection.
cc41dc3 @technoweenie Service#http now stores the faraday instance
technoweenie authored
458 attr_writer :http
da3e470 @technoweenie epic tomdoc
technoweenie authored
459
460 # Sets the private configuration data.
461 #
462 # secrets - Configuration Hash.
463 #
464 # Returns nothing.
98c6342 @technoweenie lazily read in secrets yml
technoweenie authored
465 attr_writer :secrets
da3e470 @technoweenie epic tomdoc
technoweenie authored
466
467 # Sets the email configuration data.
468 #
469 # email_config - Email configuration Hash.
470 #
471 # Returns nothing.
9d64149 @technoweenie port the email hook
technoweenie authored
472 attr_writer :email_config
da3e470 @technoweenie epic tomdoc
technoweenie authored
473
474 # Sets the path to the SSL Certificate Authority file.
475 #
476 # ca_file - String path.
477 #
478 # Returns nothing.
04f8c31 @technoweenie verify cacert
technoweenie authored
479 attr_writer :ca_file
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
480
45df8b3 @technoweenie backport tweaks
technoweenie authored
481 attr_reader :event_method
482
df29ac2 @technoweenie problems with the block callbacks when services raise exceptions
technoweenie authored
483 attr_reader :http_calls
484
485 attr_reader :remote_calls
486
722c0af @technoweenie don't need the meta crap, it's already sent as part of the payload!
technoweenie authored
487 def initialize(event = :push, data = {}, payload = nil)
a6bc3ff @technoweenie campfire service can listen for pull_request and issues events
technoweenie authored
488 helper_name = "#{event.to_s.classify}Helpers"
54c4db0 @technoweenie move the sample payload to Service::PushHelpers
technoweenie authored
489 if Service.const_defined?(helper_name)
490 @helper = Service.const_get(helper_name)
491 extend @helper
492 end
493
2a6ed53 @technoweenie add a Service::Meta object for tracking the core meta data for an event
technoweenie authored
494 @event = event.to_sym
932a827 @technoweenie ensure service data is initialized as {}
technoweenie authored
495 @data = data || {}
54c4db0 @technoweenie move the sample payload to Service::PushHelpers
technoweenie authored
496 @payload = payload || sample_payload
45df8b3 @technoweenie backport tweaks
technoweenie authored
497 @event_method = ["receive_#{event}", "receive_event"].detect do |method|
498 respond_to?(method)
499 end
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
500 @http = @secrets = @email_config = nil
df29ac2 @technoweenie problems with the block callbacks when services raise exceptions
technoweenie authored
501 @http_calls = []
502 @remote_calls = []
45df8b3 @technoweenie backport tweaks
technoweenie authored
503 end
504
8c35b49 @jdpace Add config_boolean_true? helper for dealing with boolean configs
jdpace authored
505 # Boolean fields as either nil, "0", or "1".
506 def config_boolean_true?(boolean_field)
507 data[boolean_field].to_i == 1
508 end
509
7ac6ee7 @jdpace Update talker to use config_boolean_false? helper
jdpace authored
510 def config_boolean_false?(boolean_field)
511 !config_boolean_true?(boolean_field)
512 end
513
45df8b3 @technoweenie backport tweaks
technoweenie authored
514 def respond_to_event?
515 !@event_method.nil?
baa597f @technoweenie add a real services class
technoweenie authored
516 end
517
ab30efd @sevein Update docs and code comments referring to bit.ly
sevein authored
518 # Public: Shortens the given URL with git.io.
da3e470 @technoweenie epic tomdoc
technoweenie authored
519 #
520 # url - String URL to be shortened.
521 #
ab30efd @sevein Update docs and code comments referring to bit.ly
sevein authored
522 # Returns the String URL response from git.io.
baa597f @technoweenie add a real services class
technoweenie authored
523 def shorten_url(url)
94341fc @technoweenie update the shorten tests
technoweenie authored
524 res = http_post("http://git.io", :url => url)
09a8581 @technoweenie bitly => git.io
technoweenie authored
525 if res.status == 201
526 res.headers['location']
527 else
528 url
baa597f @technoweenie add a real services class
technoweenie authored
529 end
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
530 rescue TimeoutError
baa597f @technoweenie add a real services class
technoweenie authored
531 url
532 end
533
da3e470 @technoweenie epic tomdoc
technoweenie authored
534 # Public: Makes an HTTP GET call.
535 #
536 # url - Optional String URL to request.
537 # params - Optional Hash of GET parameters to set.
538 # headers - Optional Hash of HTTP headers to set.
539 #
540 # Examples
541 #
542 # http_get("http://github.com")
543 # # => <Faraday::Response>
544 #
545 # # GET http://github.com?page=1
546 # http_get("http://github.com", :page => 1)
547 # # => <Faraday::Response>
548 #
549 # http_get("http://github.com", {:page => 1},
550 # 'Accept': 'application/json')
551 # # => <Faraday::Response>
552 #
553 # # Yield the Faraday::Response for more control.
554 # http_get "http://github.com" do |req|
555 # req.basic_auth("username", "password")
556 # req.params[:page] = 1
557 # req.headers['Accept'] = 'application/json'
558 # end
559 # # => <Faraday::Response>
560 #
561 # Yields a Faraday::Request instance.
562 # Returns a Faraday::Response instance.
39db84b @technoweenie port fogbugz hook
technoweenie authored
563 def http_get(url = nil, params = nil, headers = nil)
564 http.get do |req|
565 req.url(url) if url
566 req.params.update(params) if params
567 req.headers.update(headers) if headers
568 yield req if block_given?
569 end
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
570 end
571
da3e470 @technoweenie epic tomdoc
technoweenie authored
572 # Public: Makes an HTTP POST call.
573 #
574 # url - Optional String URL to request.
575 # body - Optional String Body of the POST request.
576 # headers - Optional Hash of HTTP headers to set.
577 #
578 # Examples
579 #
580 # http_post("http://github.com/create", "foobar")
581 # # => <Faraday::Response>
582 #
583 # http_post("http://github.com/create", "foobar",
584 # 'Accept': 'application/json')
585 # # => <Faraday::Response>
586 #
587 # # Yield the Faraday::Response for more control.
588 # http_post "http://github.com/create" do |req|
589 # req.basic_auth("username", "password")
590 # req.params[:page] = 1 # http://github.com/create?page=1
591 # req.headers['Content-Type'] = 'application/json'
d25952f @technoweenie use JSON.generate
technoweenie authored
592 # req.body = generate_json(:foo => :bar)
da3e470 @technoweenie epic tomdoc
technoweenie authored
593 # end
594 # # => <Faraday::Response>
595 #
596 # Yields a Faraday::Request instance.
597 # Returns a Faraday::Response instance.
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
598 def http_post(url = nil, body = nil, headers = nil)
6593dcc @technoweenie wrap ssl errors and return them as config errors
technoweenie authored
599 block = Proc.new if block_given?
b358475 @technoweenie dry
technoweenie authored
600 http_method :post, url, body, headers, &block
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
601 end
602
da3e470 @technoweenie epic tomdoc
technoweenie authored
603 # Public: Makes an HTTP call.
604 #
605 # method - Symbol of the HTTP method. Example: :put
606 # url - Optional String URL to request.
607 # body - Optional String Body of the POST request.
608 # headers - Optional Hash of HTTP headers to set.
609 #
610 # Examples
611 #
612 # http_method(:put, "http://github.com/create", "foobar")
613 # # => <Faraday::Response>
614 #
615 # http_method(:put, "http://github.com/create", "foobar",
616 # 'Accept': 'application/json')
617 # # => <Faraday::Response>
618 #
619 # # Yield the Faraday::Response for more control.
620 # http_method :put, "http://github.com/create" do |req|
621 # req.basic_auth("username", "password")
622 # req.params[:page] = 1 # http://github.com/create?page=1
623 # req.headers['Content-Type'] = 'application/json'
d25952f @technoweenie use JSON.generate
technoweenie authored
624 # req.body = generate_json(:foo => :bar)
da3e470 @technoweenie epic tomdoc
technoweenie authored
625 # end
626 # # => <Faraday::Response>
627 #
628 # Yields a Faraday::Request instance.
629 # Returns a Faraday::Response instance.
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
630 def http_method(method, url = nil, body = nil, headers = nil)
6593dcc @technoweenie wrap ssl errors and return them as config errors
technoweenie authored
631 block = Proc.new if block_given?
632
633 check_ssl do
634 http.send(method) do |req|
635 req.url(url) if url
636 req.headers.update(headers) if headers
637 req.body = body if body
638 block.call req if block
639 end
c70fa42 @technoweenie test shorten_url with faraday helpers
technoweenie authored
640 end
641 end
642
da3e470 @technoweenie epic tomdoc
technoweenie authored
643 # Public: Lazily loads the Faraday::Connection for the current Service
644 # instance.
645 #
646 # options - Optional Hash of Faraday::Connection options.
647 #
648 # Returns a Faraday::Connection instance.
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
649 def http(options = {})
650 @http ||= begin
d436712 @technoweenie remove excon as a dependency
technoweenie authored
651 config = self.class.default_http_options
652 config.each do |key, sub_options|
653 next if key == :adapter
45df8b3 @technoweenie backport tweaks
technoweenie authored
654 sub_hash = options[key] ||= {}
655 sub_options.each do |sub_key, sub_value|
656 sub_hash[sub_key] ||= sub_value
657 end
658 end
659 options[:ssl][:ca_file] ||= ca_file
15abe05 @technoweenie cant verify hipchat ssl
technoweenie authored
660
6104765 @atmos faraday connection construction for 0.9.x
atmos authored
661 adapter = Array(options.delete(:adapter) || config[:adapter])
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
662 Faraday.new(options) do |b|
d436712 @technoweenie remove excon as a dependency
technoweenie authored
663 b.request(:url_encoded)
6104765 @atmos faraday connection construction for 0.9.x
atmos authored
664 b.adapter *adapter
798f3ac @technoweenie put the http reporter at the end to get the full env
technoweenie authored
665 b.use(HttpReporter, self)
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
666 end
667 end
668 end
669
45df8b3 @technoweenie backport tweaks
technoweenie authored
670 def self.default_http_options
671 @@default_http_options ||= {
7e219a3 @atmos fixup aws opsworks related tests
atmos authored
672 :adapter => :net_http,
45df8b3 @technoweenie backport tweaks
technoweenie authored
673 :request => {:timeout => 10, :open_timeout => 5},
674 :ssl => {:verify_depth => 5},
675 :headers => {}
676 }
677 end
678
a02df2d @technoweenie add hooks for remote logging
technoweenie authored
679 # Passes HTTP response debug data to the HTTP callbacks.
45df8b3 @technoweenie backport tweaks
technoweenie authored
680 def receive_http(env)
df29ac2 @technoweenie problems with the block callbacks when services raise exceptions
technoweenie authored
681 @http_calls << env
a02df2d @technoweenie add hooks for remote logging
technoweenie authored
682 end
683
684 # Passes raw debug data to remote call callbacks.
685 def receive_remote_call(text)
df29ac2 @technoweenie problems with the block callbacks when services raise exceptions
technoweenie authored
686 @remote_calls << text
45df8b3 @technoweenie backport tweaks
technoweenie authored
687 end
688
55a1fb1 @technoweenie let us pass a custom timeout to run a hook
technoweenie authored
689 def receive(timeout = nil)
45df8b3 @technoweenie backport tweaks
technoweenie authored
690 return unless respond_to_event?
55a1fb1 @technoweenie let us pass a custom timeout to run a hook
technoweenie authored
691 timeout_sec = (timeout || 20).to_i
692 Service::Timeout.timeout(timeout_sec, TimeoutError) do
45df8b3 @technoweenie backport tweaks
technoweenie authored
693 send(event_method)
694 end
695
696 self
697 rescue Service::ConfigurationError, Errno::EHOSTUNREACH, Errno::ECONNRESET, SocketError, Net::ProtocolError => err
698 if !err.is_a?(Service::Error)
699 err = ConfigurationError.new(err)
700 end
701 raise err
702 end
703
d25952f @technoweenie use JSON.generate
technoweenie authored
704 def generate_json(body)
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
705 JSON.generate(clean_for_json(body))
706 end
707
708 def clean_hash_for_json(hash)
d9a698f @technoweenie dont mutate options when cleaning for json
technoweenie authored
709 new_hash = {}
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
710 hash.keys.each do |key|
d9a698f @technoweenie dont mutate options when cleaning for json
technoweenie authored
711 new_hash[key] = clean_for_json(hash[key])
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
712 end
d9a698f @technoweenie dont mutate options when cleaning for json
technoweenie authored
713 new_hash
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
714 end
715
716 def clean_array_for_json(array)
d9a698f @technoweenie dont mutate options when cleaning for json
technoweenie authored
717 array.map { |value| clean_for_json(value) }
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
718 end
719
b0fbdac @technoweenie convert irc response strings to valid utf-8
technoweenie authored
720 # overridden in Hookshot for proper UTF-8 transcoding with CharlockHolmes
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
721 def clean_string_for_json(str)
b0fbdac @technoweenie convert irc response strings to valid utf-8
technoweenie authored
722 str.to_s.force_encoding(Service::UTF8)
924d437 @technoweenie add hooks to clean string values before generating JSON
technoweenie authored
723 end
724
725 def clean_for_json(value)
726 case value
727 when Hash then clean_hash_for_json(value)
728 when Array then clean_array_for_json(value)
729 when String then clean_string_for_json(value)
730 else value
731 end
d25952f @technoweenie use JSON.generate
technoweenie authored
732 end
733
c49482d @technoweenie tomdoc
technoweenie authored
734 # Public: Checks for an SSL error, and re-raises a Services configuration error.
735 #
736 # Returns nothing.
6593dcc @technoweenie wrap ssl errors and return them as config errors
technoweenie authored
737 def check_ssl
738 yield
739 rescue OpenSSL::SSL::SSLError => e
740 raise_config_error "Invalid SSL cert"
741 end
742
383d2f5 @technoweenie implement basic log messages for services
technoweenie authored
743 # Public: Builds a log message for this Service request. Respects the white
744 # listed attributes in the Service schema.
745 #
746 # Returns a String.
747 def log_message(status = 0)
748 "[%s] %03d %s/%s %s" % [Time.now.utc.to_s(:db), status,
d25952f @technoweenie use JSON.generate
technoweenie authored
749 self.class.hook_name, @event, generate_json(log_data)]
383d2f5 @technoweenie implement basic log messages for services
technoweenie authored
750 end
751
752 # Public: Builds a sanitized Hash of the Data hash without passwords.
753 #
754 # Returns a Hash.
755 def log_data
756 @log_data ||= self.class.white_listed.inject({}) do |hash, key|
757 if value = data[key]
06a9fed @technoweenie sanitize logged attribute strings that might be URIs with passwords
technoweenie authored
758 hash.update key => sanitize_log_value(value)
383d2f5 @technoweenie implement basic log messages for services
technoweenie authored
759 else
760 hash
761 end
762 end
763 end
764
06a9fed @technoweenie sanitize logged attribute strings that might be URIs with passwords
technoweenie authored
765 # Attempts to sanitize passwords out of URI strings.
766 #
767 # value - The String attribute value.
768 #
769 # Returns a sanitized String.
770 def sanitize_log_value(value)
771 string = value.to_s
772 string.strip!
773 if string =~ /^[a-z]+\:\/\//
774 uri = Addressable::URI.parse(string)
fdda02d @BM5k obfuscate password length when sanitizing
BM5k authored
775 uri.password = "*" * 8 if uri.password
06a9fed @technoweenie sanitize logged attribute strings that might be URIs with passwords
technoweenie authored
776 uri.to_s
777 else
778 string
779 end
780 rescue Addressable::URI::InvalidURIError
781 string
782 end
783
da3e470 @technoweenie epic tomdoc
technoweenie authored
784 # Public: Gets the Hash of secret configuration options. These are set on
785 # the GitHub servers and never committed to git.
786 #
787 # Returns a Hash.
98c6342 @technoweenie lazily read in secrets yml
technoweenie authored
788 def secrets
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
789 @secrets || Service.secrets
98c6342 @technoweenie lazily read in secrets yml
technoweenie authored
790 end
791
da3e470 @technoweenie epic tomdoc
technoweenie authored
792 # Public: Gets the Hash of email configuration options. These are set on
793 # the GitHub servers and never committed to git.
794 #
795 # Returns a Hash.
9d64149 @technoweenie port the email hook
technoweenie authored
796 def email_config
5321fd2 @technoweenie move secrets/email_config to the Service class. let service instance…
technoweenie authored
797 @email_config || Service.email_config
9d64149 @technoweenie port the email hook
technoweenie authored
798 end
799
da3e470 @technoweenie epic tomdoc
technoweenie authored
800 # Public: Raises a configuration error inside a service, and halts further
801 # processing.
802 #
803 # Raises a Service;:ConfigurationError.
c260750 @technoweenie #raise_config_error should be public
technoweenie authored
804 def raise_config_error(msg = "Invalid configuration")
805 raise ConfigurationError, msg
806 end
807
9d3232c @technoweenie return 404s
technoweenie authored
808 def raise_missing_error(msg = "Remote endpoint not found")
809 raise MissingError, msg
810 end
811
da3e470 @technoweenie epic tomdoc
technoweenie authored
812 # Gets the path to the SSL Certificate Authority certs. These were taken
813 # from: http://curl.haxx.se/ca/cacert.pem
814 #
815 # Returns a String path.
04f8c31 @technoweenie verify cacert
technoweenie authored
816 def ca_file
817 @ca_file ||= File.expand_path('../../config/cacert.pem', __FILE__)
818 end
819
54c4db0 @technoweenie move the sample payload to Service::PushHelpers
technoweenie authored
820 # Generates a sample payload for the current Service instance.
821 #
822 # Returns a Hash payload.
823 def sample_payload
035aaa1 @technoweenie not all events need helpers
technoweenie authored
824 @helper ? @helper.sample_payload : {}
54c4db0 @technoweenie move the sample payload to Service::PushHelpers
technoweenie authored
825 end
826
f1a43b9 @technoweenie log lighthouse body too
technoweenie authored
827 def reportable_http_env(env, time)
828 {
829 :request => {
830 :url => env[:url].to_s,
831 :headers => env[:request_headers]
832 }, :response => {
833 :status => env[:status],
834 :headers => env[:response_headers],
835 :body => env[:body].to_s,
836 :duration => "%.02fs" % [Time.now - time]
f26bd63 @technoweenie report the adapter if given
technoweenie authored
837 },
838 :adapter => env[:adapter]
f1a43b9 @technoweenie log lighthouse body too
technoweenie authored
839 }
840 end
841
7b0d615 @technoweenie move service exceptions to a separate file
technoweenie authored
842 # Raised when an unexpected error occurs during service hook execution.
843 class Error < StandardError
844 attr_reader :original_exception
845 def initialize(message, original_exception=nil)
846 original_exception = message if message.kind_of?(Exception)
847 @original_exception = original_exception
848 super(message)
849 end
850 end
851
852 class TimeoutError < Timeout::Error
853 end
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
854
7b0d615 @technoweenie move service exceptions to a separate file
technoweenie authored
855 # Raised when a service hook fails due to bad configuration. Services that
856 # fail with this exception may be automatically disabled.
857 class ConfigurationError < Error
858 end
9d3232c @technoweenie return 404s
technoweenie authored
859
860 class MissingError < Error
861 end
45df8b3 @technoweenie backport tweaks
technoweenie authored
862
863 class HttpReporter < Faraday::Response::Middleware
864 def initialize(app, service = nil)
865 super(app)
866 @service = service
8604bed @technoweenie show the request duration
technoweenie authored
867 @time = Time.now
45df8b3 @technoweenie backport tweaks
technoweenie authored
868 end
869
870 def on_complete(env)
f1a43b9 @technoweenie log lighthouse body too
technoweenie authored
871 @service.receive_http(@service.reportable_http_env(env, @time))
45df8b3 @technoweenie backport tweaks
technoweenie authored
872 end
873 end
7b0d615 @technoweenie move service exceptions to a separate file
technoweenie authored
874 end
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
875
6e1805c @technoweenie always load timeout
technoweenie authored
876 require 'timeout'
4efeabb @technoweenie move Service::Timeout logic to service.rb
technoweenie authored
877 begin
878 require 'system_timer'
879 Service::Timeout = SystemTimer
880 rescue LoadError
881 Service::Timeout = Timeout
882 end
Something went wrong with that request. Please try again.