god 0.13.4 → 0.13.5

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/History.txt +13 -0
  3. data/doc/god.asciidoc +50 -8
  4. data/ext/god/extconf.rb +1 -0
  5. data/ext/god/kqueue_handler.c +9 -1
  6. data/ext/god/netlink_handler.c +17 -0
  7. data/god.gemspec +12 -3
  8. data/lib/god.rb +3 -1
  9. data/lib/god/contacts/slack.rb +100 -0
  10. data/lib/god/contacts/statsd.rb +46 -0
  11. data/lib/god/contacts/webhook.rb +1 -0
  12. data/lib/god/process.rb +10 -5
  13. data/test/helper.rb +8 -3
  14. data/test/test_airbrake.rb +1 -1
  15. data/test/test_behavior.rb +2 -2
  16. data/test/test_campfire.rb +1 -1
  17. data/test/test_condition.rb +3 -3
  18. data/test/test_conditions_disk_usage.rb +1 -1
  19. data/test/test_conditions_http_response_code.rb +1 -1
  20. data/test/test_conditions_process_running.rb +1 -1
  21. data/test/test_conditions_socket_responding.rb +1 -1
  22. data/test/test_conditions_tries.rb +3 -3
  23. data/test/test_contact.rb +9 -9
  24. data/test/test_driver.rb +4 -3
  25. data/test/test_email.rb +1 -1
  26. data/test/test_event_handler.rb +2 -2
  27. data/test/test_god.rb +9 -7
  28. data/test/test_handlers_kqueue_handler.rb +1 -1
  29. data/test/test_hipchat.rb +1 -1
  30. data/test/test_jabber.rb +1 -1
  31. data/test/test_logger.rb +3 -3
  32. data/test/test_metric.rb +1 -1
  33. data/test/test_process.rb +7 -3
  34. data/test/test_prowl.rb +1 -1
  35. data/test/test_registry.rb +1 -1
  36. data/test/test_slack.rb +56 -0
  37. data/test/test_socket.rb +1 -1
  38. data/test/test_statsd.rb +22 -0
  39. data/test/test_sugar.rb +1 -1
  40. data/test/test_system_portable_poller.rb +1 -1
  41. data/test/test_system_process.rb +1 -1
  42. data/test/test_task.rb +4 -4
  43. data/test/test_timeline.rb +1 -1
  44. data/test/test_trigger.rb +5 -1
  45. data/test/test_watch.rb +1 -1
  46. data/test/test_webhook.rb +1 -1
  47. metadata +240 -223
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4e92e64f9fc2eaea16ace1b200c266186acbfe80
4
+ data.tar.gz: 3f1a94de8fa67f06e41b96e1176faf71e02ee7ab
5
+ SHA512:
6
+ metadata.gz: 97bc476662014ecdd34126fc2a1e1fa0352e477c5e7770eca6440b63770fed590d97f1e0e0326b00f8715b9b6db4ab7771b35cfe00583b6ea0f8aa86b9d8bc5c
7
+ data.tar.gz: 8f36cbb7934ee98a1b2c78af71740ed2a502ccd08479b0a01f15d233224873d7ff15cdf7874282713e99a4aa2b02e02bf70b4a630f4d5a3e0518179dfdf2c54f
@@ -1,3 +1,16 @@
1
+ == 0.13.5 / 2015-01-09
2
+ * Minor Enhancements
3
+ * statsd notifier (#144)
4
+ * Slack notifier (#172)
5
+ * Bug fixes
6
+ * Add support for Ruby 2.2.0 (#206)
7
+ * Fix tests for Ruby 2.0 (#205)
8
+ * Make group assignment more like login (#159)
9
+ * Flush file descriptors to prevent too many processes from being started (#141)
10
+ * Allow SSL URLs (#168)
11
+ * Documentation fixes
12
+ * Clarify Hipchat privilege requirements (#176)
13
+
1
14
  == 0.13.4 / 2014-03-05
2
15
  * Minor Enhancements
3
16
  * Hipchat reporter (#162)
@@ -436,8 +436,8 @@ A `watch` represents a single process that has concrete start, stop, and/or
436
436
  restart operations. You can define as many watches as you like. In the example
437
437
  above, I've got some Rails instances running in Mongrels that I need to keep
438
438
  alive. Every watch must have a unique `name` so that it can be identified
439
- later on. The `start` and `stop` attributes specify the commands to start
440
- and stop the process. If no `restart` attribute is set, restart will be
439
+ later on. The `start` and `stop` attributes specify the commands to start
440
+ and stop the process. If no `restart` attribute is set, restart will be
441
441
  represented by a call to stop followed by a call to start. The
442
442
  optional `grace` attribute sets the amount of time following a
443
443
  start/stop/restart command to wait before resuming normal monitoring
@@ -575,7 +575,7 @@ Watching Non-Daemon Processes
575
575
  Need to watch a script that doesn't have built in daemonization? No problem!
576
576
  God will daemonize and keep track of your process for you. If you don't
577
577
  specify a `pid_file` attribute for a watch, it will be auto-daemonized and a
578
- PID file will be stored for it in `/var/run/god`.
578
+ PID file will be stored for it in `/var/run/god`.
579
579
 
580
580
 
581
581
  ```ruby
@@ -585,9 +585,9 @@ God.pid_file_directory = '/home/tom/pids'
585
585
  God.watch do |w|
586
586
  w.name = 'mongrel'
587
587
  w.pid_file = w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid")
588
-
588
+
589
589
  w.start = "mongrel_rails start -P #{RAILS_ROOT}/log/mongrel.pid -d"
590
-
590
+
591
591
  # ...
592
592
  end
593
593
 
@@ -595,15 +595,15 @@ end
595
595
  God.watch do |w|
596
596
  w.name = 'worker'
597
597
  # w.pid_file = is not set
598
-
598
+
599
599
  w.start = "rake resque:worker"
600
-
600
+
601
601
  # ...
602
602
  end
603
603
  ```
604
604
 
605
605
 
606
- If you'd rather have the PID file stored in a different location, you can
606
+ If you'd rather have the PID file stored in a different location, you can
607
607
  set it at the top of your config:
608
608
 
609
609
  ```ruby
@@ -1022,6 +1022,8 @@ ssl - A Boolean determining whether or not to use SSL
1022
1022
  from - The String representing who the message should be sent as.
1023
1023
  ```
1024
1024
 
1025
+ NOTE: in Hipchat you must have a token with 'admin' privileges. 'Notification' privileges will not be enough.
1026
+
1025
1027
  Email
1026
1028
  ~~~~~
1027
1029
 
@@ -1243,6 +1245,46 @@ end
1243
1245
  apikey - The String API key.
1244
1246
  ```
1245
1247
 
1248
+ Slack
1249
+ ~~~~~
1250
+
1251
+ Send a message to a channel in Slack (https://slack.com/).
1252
+
1253
+ First, set up an Incoming Webhook in your Slack account.
1254
+
1255
+ Then, in your God configuration, set the defaults:
1256
+
1257
+ ```ruby
1258
+ God::Contacts::Slack.defaults do |d|
1259
+ d.account = "foo"
1260
+ d.token = "abc123abc123abc123"
1261
+ c.notify_channel = true
1262
+ c.format = '%{host} alert: %{message}'
1263
+ end
1264
+ ```
1265
+
1266
+ `account` is the name of your Slack account; if you view slack at
1267
+ "foo.slack.com", then your account is "foo". `token` is from your
1268
+ newly-created webhook, and will be a string of unintelligible
1269
+ characters.
1270
+
1271
+ The `notify_channel` and `format` settings are optional. The first
1272
+ controls whether the message includes `@channel` (sending notifications
1273
+ to everyone in the channel); the second controls how the message is
1274
+ formatted. Acceptable values within the format are `priority`, `host`,
1275
+ `message`, `category`, and `time`.
1276
+
1277
+ Once you've set the defaults, create contacts for the channels that you
1278
+ want to notify. You can create as many as you like, and they'll look
1279
+ something like this:
1280
+
1281
+ ```ruby
1282
+ God.contact(:slack) do |c|
1283
+ c.name = '#ops'
1284
+ c.channel = '#ops'
1285
+ end
1286
+ ```
1287
+
1246
1288
  /////////////////////////////////////////////////////////////////////////////
1247
1289
  /////////////////////////////////////////////////////////////////////////////
1248
1290
 
@@ -9,6 +9,7 @@ def create_dummy_makefile
9
9
  end
10
10
  end
11
11
 
12
+ have_func('rb_wait_for_single_fd')
12
13
  case RUBY_PLATFORM
13
14
  when /bsd/i, /darwin/i
14
15
  unless have_header('sys/event.h')
@@ -5,6 +5,10 @@
5
5
  #include <sys/time.h>
6
6
  #include <errno.h>
7
7
 
8
+ #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
9
+ #include <ruby/io.h>
10
+ #endif
11
+
8
12
  static VALUE mGod;
9
13
  static VALUE cKQueueHandler;
10
14
  static VALUE cEventHandler;
@@ -63,6 +67,10 @@ kqh_handle_events()
63
67
  {
64
68
  int nevents, i, num_to_fetch;
65
69
  struct kevent *events;
70
+
71
+ #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
72
+ rb_wait_for_single_fd(kq, RB_WAITFD_IN, NULL);
73
+ #else
66
74
  fd_set read_set;
67
75
 
68
76
  FD_ZERO(&read_set);
@@ -70,7 +78,7 @@ kqh_handle_events()
70
78
 
71
79
  // Don't actually run this method until we've got an event
72
80
  rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL);
73
-
81
+ #endif
74
82
  // Grabbing num_events once for thread safety
75
83
  num_to_fetch = num_events;
76
84
  events = (struct kevent*)malloc(num_to_fetch * sizeof(struct kevent));
@@ -1,6 +1,12 @@
1
1
  #ifdef __linux__ /* only build on linux */
2
2
 
3
3
  #include <ruby.h>
4
+
5
+ #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
6
+ #include <ruby/io.h>
7
+ #endif
8
+
9
+
4
10
  #include <sys/types.h>
5
11
  #include <unistd.h>
6
12
  #include <sys/socket.h>
@@ -31,6 +37,16 @@ nlh_handle_events()
31
37
 
32
38
  VALUE extra_data;
33
39
 
40
+ #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
41
+ int ret;
42
+ if((ret = rb_wait_for_single_fd(nl_sock, RB_WAITFD_IN, NULL)) < 0){
43
+ rb_raise(rb_eStandardError, "%s", strerror(errno));
44
+ }
45
+ /* If there were no events detected, return */
46
+ if(!(ret & RB_WAITFD_IN)){
47
+ return INT2FIX(0);
48
+ }
49
+ #else
34
50
  fd_set fds;
35
51
 
36
52
  FD_ZERO(&fds);
@@ -44,6 +60,7 @@ nlh_handle_events()
44
60
  if (! FD_ISSET(nl_sock, &fds)) {
45
61
  return INT2FIX(0);
46
62
  }
63
+ #endif
47
64
 
48
65
  /* if there are events, make calls */
49
66
  if (-1 == recv(nl_sock, buff, sizeof(buff), 0)) {
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'god'
6
- s.version = '0.13.4'
7
- s.date = '2014-03-05'
6
+ s.version = '0.13.5'
7
+ s.date = '2015-01-09'
8
8
 
9
9
  s.summary = "Process monitoring framework."
10
10
  s.description = "An easy to configure, easy to extend monitoring framework written in Ruby."
@@ -25,18 +25,23 @@ Gem::Specification.new do |s|
25
25
 
26
26
  s.add_development_dependency('json', '~> 1.6')
27
27
  s.add_development_dependency('rake')
28
+ s.add_development_dependency('minitest')
28
29
  s.add_development_dependency('rdoc', '~> 3.10')
29
30
  s.add_development_dependency('twitter', '~> 4.0')
30
31
  s.add_development_dependency('prowly', '~> 0.3')
31
32
  s.add_development_dependency('xmpp4r', '~> 0.5')
32
33
  s.add_development_dependency('dike', '~> 0.0.3')
33
- s.add_development_dependency('rcov', '~> 0.9')
34
+ # s.add_development_dependency('rcov', '~> 0.9')
34
35
  s.add_development_dependency('daemons', '~> 1.1')
35
36
  s.add_development_dependency('mocha', '~> 0.10')
36
37
  s.add_development_dependency('gollum', '~> 1.3.1')
38
+ #the last version to support 1.8.7 is 0.99.6
39
+ s.add_development_dependency('mustache', ['~> 0.99.0', '< 0.99.7'])
37
40
  s.add_development_dependency('airbrake', '~> 3.1.7')
38
41
  s.add_development_dependency('nokogiri', '~> 1.5.0')
39
42
  s.add_development_dependency('activesupport', [ '>= 2.3.10', '< 4.0.0' ])
43
+ s.add_development_dependency('statsd-ruby')
44
+ s.add_development_dependency('i18n', '< 0.7.0')
40
45
  # = MANIFEST =
41
46
  s.files = %w[
42
47
  Announce.txt
@@ -87,6 +92,8 @@ Gem::Specification.new do |s|
87
92
  lib/god/contacts/jabber.rb
88
93
  lib/god/contacts/prowl.rb
89
94
  lib/god/contacts/scout.rb
95
+ lib/god/contacts/slack.rb
96
+ lib/god/contacts/statsd.rb
90
97
  lib/god/contacts/twitter.rb
91
98
  lib/god/contacts/webhook.rb
92
99
  lib/god/driver.rb
@@ -162,7 +169,9 @@ Gem::Specification.new do |s|
162
169
  test/test_process.rb
163
170
  test/test_prowl.rb
164
171
  test/test_registry.rb
172
+ test/test_slack.rb
165
173
  test/test_socket.rb
174
+ test/test_statsd.rb
166
175
  test/test_sugar.rb
167
176
  test/test_system_portable_poller.rb
168
177
  test/test_system_process.rb
data/lib/god.rb CHANGED
@@ -90,9 +90,11 @@ load_contact(:email)
90
90
  load_contact(:jabber)
91
91
  load_contact(:prowl)
92
92
  load_contact(:scout)
93
+ load_contact(:statsd)
93
94
  load_contact(:twitter)
94
95
  load_contact(:webhook)
95
96
  load_contact(:airbrake)
97
+ load_contact(:slack)
96
98
 
97
99
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
98
100
 
@@ -158,7 +160,7 @@ end
158
160
 
159
161
  module God
160
162
  # The String version number for this package.
161
- VERSION = '0.13.4'
163
+ VERSION = '0.13.5'
162
164
 
163
165
  # The Integer number of lines of backlog to keep for the logger.
164
166
  LOG_BUFFER_SIZE_DEFAULT = 100
@@ -0,0 +1,100 @@
1
+ # Send a message to a Slack channel
2
+ #
3
+ # account - The name of your Slack account (visible in URL, e.g. foo.slack.com)
4
+ # token - The token of the webhook created in Slack
5
+ # channel - The name of the channel to send the message to, prefixed with #
6
+ # notify_channel - Whether to send an "@channel" in the message, to alert everyone in the channel
7
+ # format - An optional format string to change how the alert is displayed
8
+
9
+ require 'net/http'
10
+ require 'uri'
11
+
12
+ CONTACT_DEPS[:slack] = ['json']
13
+ CONTACT_DEPS[:slack].each do |d|
14
+ require d
15
+ end
16
+
17
+ module God
18
+ module Contacts
19
+
20
+ class Slack < Contact
21
+ class << self
22
+ attr_accessor :account, :token, :channel, :notify_channel, :format, :username, :emoji
23
+ end
24
+
25
+ self.channel = "#general"
26
+ self.notify_channel = false
27
+ self.format = "%{priority} alert on %{host}: %{message} (%{category}, %{time})"
28
+
29
+ def valid?
30
+ valid = true
31
+ valid &= complain("Attribute 'account' must be specified", self) unless arg(:account)
32
+ valid &= complain("Attribute 'token' must be specified", self) unless arg(:token)
33
+ valid
34
+ end
35
+
36
+ attr_accessor :account, :token, :channel, :notify_channel, :format, :username, :emoji
37
+
38
+ def text(data)
39
+ text = ""
40
+ text << "<!channel> " if arg(:notify_channel)
41
+
42
+ if RUBY_VERSION =~ /^1\.8/
43
+ text << arg(:format).gsub(/%\{(\w+)\}/) do |match|
44
+ data[$1.to_sym]
45
+ end
46
+ else
47
+ text << arg(:format) % data
48
+ end
49
+
50
+ text
51
+ end
52
+
53
+ def notify(message, time, priority, category, host)
54
+ text = text({
55
+ :message => message,
56
+ :time => time,
57
+ :priority => priority,
58
+ :category => category,
59
+ :host => host
60
+ })
61
+
62
+ request(text)
63
+ end
64
+
65
+ def api_url
66
+ URI.parse("https://#{arg(:account)}.slack.com/services/hooks/incoming-webhook?token=#{arg(:token)}")
67
+ end
68
+
69
+ def request(text)
70
+ http = Net::HTTP.new(api_url.host, api_url.port)
71
+ http.use_ssl = true
72
+
73
+ req = Net::HTTP::Post.new(api_url.request_uri)
74
+ req.body = {
75
+ :link_names => 1,
76
+ :text => text,
77
+ :channel => arg(:channel)
78
+ }.tap { |payload|
79
+ payload[:username] = arg(:username) if arg(:username)
80
+ payload[:icon_emoji] = arg(:emoji) if arg(:emoji)
81
+ }.to_json
82
+
83
+ res = http.request(req)
84
+
85
+ case res
86
+ when Net::HTTPSuccess
87
+ self.info = "successfully notified slack on channel #{arg(:channel)}"
88
+ else
89
+ self.info = "failed to send webhook to #{arg(:url)}: #{res.error!}"
90
+ end
91
+ rescue Object => e
92
+ applog(nil, :info, "failed to send webhook to #{arg(:url)}: #{e.message}")
93
+ applog(nil, :debug, e.backtrace.join("\n"))
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
100
+
@@ -0,0 +1,46 @@
1
+ # Send a notice to statsd
2
+ #
3
+ # host - statsd host
4
+ # port - statsd port (optional)
5
+
6
+ require 'statsd-ruby'
7
+
8
+ module God
9
+ module Contacts
10
+
11
+ class Statsd < Contact
12
+ class << self
13
+ attr_accessor :host, :port
14
+ end
15
+
16
+ attr_accessor :host, :port
17
+
18
+ def valid?
19
+ valid = true
20
+ valid &= complain("Attribute 'statsd_host' must be specified", self) unless arg(:host)
21
+ valid
22
+ end
23
+
24
+ def notify(message, time, priority, category, hostname)
25
+ statsd = ::Statsd.new host, (port ? port.to_i : 8125) # 8125 is the default statsd port
26
+
27
+ hostname.gsub! /\./, '_'
28
+ app = message.gsub /([^\s]*).*/, '\1'
29
+
30
+ [
31
+ 'cpu out of bounds',
32
+ 'memory out of bounds',
33
+ 'process is flapping'
34
+ ].each do |event_type|
35
+ statsd.increment "god.#{event_type.gsub(/\s/, '_')}.#{hostname}.#{app}" if message.include? event_type
36
+ end
37
+
38
+ self.info = 'sent statsd alert'
39
+ rescue => e
40
+ applog(nil, :info, "failed to send statsd alert: #{e.message}")
41
+ applog(nil, :debug, e.backtrace.join("\n"))
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -41,6 +41,7 @@ module God
41
41
 
42
42
  uri = URI.parse(arg(:url))
43
43
  http = Net::HTTP.new(uri.host, uri.port)
44
+ http.use_ssl = true if uri.scheme == "https"
44
45
 
45
46
  req = nil
46
47
  res = nil
@@ -32,16 +32,18 @@ module God
32
32
  begin
33
33
  uid_num = Etc.getpwnam(self.uid).uid if self.uid
34
34
  gid_num = Etc.getgrnam(self.gid).gid if self.gid
35
+ gid_num = Etc.getpwnam(self.uid).gid if self.gid.nil? && self.uid
35
36
 
36
37
  ::Dir.chroot(self.chroot) if self.chroot
37
- ::Process.groups = [gid_num] if self.gid
38
- ::Process::Sys.setgid(gid_num) if self.gid
38
+ ::Process.groups = [gid_num] if gid_num
39
+ ::Process.initgroups(self.uid, gid_num) if self.uid && gid_num
40
+ ::Process::Sys.setgid(gid_num) if gid_num
39
41
  ::Process::Sys.setuid(uid_num) if self.uid
40
42
  rescue ArgumentError, Errno::EPERM, Errno::ENOENT
41
43
  exit(1)
42
44
  end
43
45
 
44
- File.writable?(file_in_chroot(file)) ? exit(0) : exit(1)
46
+ File.writable?(file_in_chroot(file)) ? exit!(0) : exit!(1)
45
47
  end
46
48
 
47
49
  wpid, status = ::Process.waitpid2(pid)
@@ -246,6 +248,7 @@ module God
246
248
  r.close
247
249
  pid = self.spawn(command)
248
250
  puts pid.to_s # send pid back to forker
251
+ exit!(0)
249
252
  end
250
253
 
251
254
  ::Process.waitpid(opid, 0)
@@ -295,11 +298,13 @@ module God
295
298
  File.umask self.umask if self.umask
296
299
  uid_num = Etc.getpwnam(self.uid).uid if self.uid
297
300
  gid_num = Etc.getgrnam(self.gid).gid if self.gid
301
+ gid_num = Etc.getpwnam(self.uid).gid if self.gid.nil? && self.uid
298
302
 
299
303
  ::Dir.chroot(self.chroot) if self.chroot
300
304
  ::Process.setsid
301
- ::Process.groups = [gid_num] if self.gid
302
- ::Process::Sys.setgid(gid_num) if self.gid
305
+ ::Process.groups = [gid_num] if gid_num
306
+ ::Process.initgroups(self.uid, gid_num) if self.uid && gid_num
307
+ ::Process::Sys.setgid(gid_num) if gid_num
303
308
  ::Process::Sys.setuid(uid_num) if self.uid
304
309
  self.dir ||= '/'
305
310
  Dir.chdir self.dir