flapjack 0.4.12 → 0.5.1

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 (55) hide show
  1. data/README.md +77 -50
  2. data/Rakefile +78 -26
  3. data/TODO.md +15 -32
  4. data/bin/flapjack-benchmark +50 -0
  5. data/bin/flapjack-notifier +11 -36
  6. data/bin/flapjack-notifier-manager +1 -3
  7. data/bin/flapjack-worker +5 -19
  8. data/doc/PACKAGING.md +25 -0
  9. data/etc/flapjack/flapjack-notifier.conf.example +34 -0
  10. data/etc/flapjack/recipients.conf.example +14 -0
  11. data/features/flapjack-notifier-manager.feature +19 -0
  12. data/features/flapjack-worker-manager.feature +25 -0
  13. data/features/packaging-lintian.feature +15 -0
  14. data/features/persistence/couch.feature +105 -0
  15. data/features/persistence/sqlite3.feature +105 -0
  16. data/features/persistence/steps/couch_steps.rb +25 -0
  17. data/features/persistence/steps/generic_steps.rb +102 -0
  18. data/features/persistence/steps/sqlite3_steps.rb +13 -0
  19. data/features/steps/flapjack-notifier-manager_steps.rb +24 -0
  20. data/features/steps/flapjack-worker-manager_steps.rb +50 -0
  21. data/features/steps/packaging-lintian_steps.rb +13 -0
  22. data/features/support/env.rb +22 -0
  23. data/features/support/silent_system.rb +4 -0
  24. data/flapjack.gemspec +7 -11
  25. data/lib/flapjack/applications/notifier.rb +222 -0
  26. data/lib/flapjack/applications/worker.rb +99 -0
  27. data/lib/flapjack/checks/ping +10 -0
  28. data/lib/flapjack/cli/notifier.rb +80 -218
  29. data/lib/flapjack/cli/worker.rb +1 -86
  30. data/lib/flapjack/filters/any_parents_failed.rb +14 -0
  31. data/lib/flapjack/filters/ok.rb +13 -0
  32. data/lib/flapjack/inifile.rb +44 -0
  33. data/lib/flapjack/{notifier.rb → notifier_engine.rb} +13 -9
  34. data/lib/flapjack/notifiers/mailer/mailer.rb +12 -13
  35. data/lib/flapjack/notifiers/xmpp/xmpp.rb +2 -2
  36. data/lib/flapjack/patches.rb +25 -0
  37. data/lib/flapjack/persistence/couch.rb +5 -0
  38. data/lib/flapjack/persistence/couch/connection.rb +66 -0
  39. data/lib/flapjack/persistence/couch/couch.rb +63 -0
  40. data/lib/flapjack/persistence/data_mapper.rb +3 -0
  41. data/lib/flapjack/persistence/data_mapper/data_mapper.rb +67 -0
  42. data/lib/flapjack/{models → persistence/data_mapper/models}/check.rb +3 -7
  43. data/lib/flapjack/{models → persistence/data_mapper/models}/check_template.rb +0 -0
  44. data/lib/flapjack/persistence/data_mapper/models/event.rb +17 -0
  45. data/lib/flapjack/{models → persistence/data_mapper/models}/node.rb +0 -0
  46. data/lib/flapjack/{models → persistence/data_mapper/models}/related_check.rb +0 -0
  47. data/lib/flapjack/persistence/sqlite3.rb +3 -0
  48. data/lib/flapjack/persistence/sqlite3/sqlite3.rb +166 -0
  49. data/lib/flapjack/transports/beanstalkd.rb +33 -0
  50. data/lib/flapjack/transports/result.rb +58 -0
  51. metadata +46 -56
  52. data/etc/flapjack/flapjack-notifier.yaml.example +0 -8
  53. data/etc/flapjack/recipients.yaml.example +0 -10
  54. data/lib/flapjack/database.rb +0 -10
  55. data/lib/flapjack/result.rb +0 -47
@@ -0,0 +1,13 @@
1
+ module Flapjack
2
+ module Filters
3
+ class Ok
4
+ def initialize(opts={})
5
+ @log = opts[:log]
6
+ end
7
+
8
+ def block?(result)
9
+ !result.warning? || !result.critical?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Nothing Flapjack-specific here - feel free to reuse.
4
+
5
+ module Flapjack
6
+ class Inifile
7
+
8
+ def initialize(string)
9
+ @data = {}
10
+
11
+ string.split("\n").each do |line|
12
+ # sections
13
+ if line =~ /^\s*\[(.+)\]\s*(;.+)*$/
14
+ @current_section = $1
15
+ @data[@current_section] ||= {}
16
+ end
17
+ # parameters
18
+ if line =~ /^\s*(.+)\s*=\s*([^;]+)\s*(;.+)*$/ # after = captures up to ; then groups everything after ;
19
+ key = $1.strip
20
+ value = $2.strip
21
+ @data[@current_section][key] = value
22
+ end
23
+ end
24
+ end
25
+
26
+ def [](key)
27
+ @data[key]
28
+ end
29
+
30
+ def keys
31
+ @data.keys
32
+ end
33
+
34
+ def self.read(filename)
35
+ self.new(File.read(filename))
36
+ end
37
+
38
+ def all
39
+ @data
40
+ end
41
+
42
+ end
43
+ end
44
+
@@ -3,14 +3,13 @@
3
3
  require 'ostruct'
4
4
 
5
5
  module Flapjack
6
- class Notifier
6
+ class NotifierEngine
7
7
 
8
- attr_reader :recipients, :log, :notifiers
8
+ attr_reader :log, :notifiers
9
9
 
10
10
  def initialize(opts={})
11
- @log = opts[:logger]
11
+ @log = opts[:log]
12
12
  raise "you have to specify a logger" unless @log
13
- @recipients = (opts[:recipients] || [])
14
13
 
15
14
  @notifiers = []
16
15
  if opts[:notifiers]
@@ -19,15 +18,20 @@ module Flapjack
19
18
  @log.info("using the #{n.class.to_s.split("::").last} notifier")
20
19
  end
21
20
  else
22
- @log.warning("there are no notifiers")
21
+ @log.warning("There are no notifiers! flapjack-notifier won't be useful.")
23
22
  end
24
23
  end
25
24
 
26
- # FIXME: use opts={} convention
27
- def notify!(result, event)
25
+ def notify!(options={})
26
+ result = options[:result]
27
+ event = options[:event]
28
+ recipients = options[:recipients]
29
+
30
+ raise ArgumentError, "A result + event were not passed!" unless result && event
31
+
28
32
  @notifiers.each do |n|
29
- @recipients.each do |recipient|
30
- @log.info("Notifying #{recipient.name} via #{n.class} about check #{result.id}")
33
+ recipients.each do |recipient|
34
+ @log.info("Notifying #{recipient.name} via #{n.class} about check #{result.check_id}")
31
35
  n.notify(:result => result, :who => recipient, :event => event)
32
36
  end
33
37
  end
@@ -3,40 +3,39 @@
3
3
  require 'rubygems'
4
4
  require 'net/smtp'
5
5
  require 'tmail'
6
- require 'log4r'
7
6
 
8
7
  module Flapjack
9
8
  module Notifiers
10
9
 
11
10
  class Mailer
12
11
 
12
+ attr_accessor :log, :from_address
13
+
13
14
  def initialize(opts={})
14
- if opts[:from_address]
15
- @from_address = opts[:from_address]
16
- else
17
- raise ArgumentError, "from address must be provided"
18
- end
19
- @website_uri = opts[:website_uri] ? opts[:website_uri].gsub(/\/$/, '') : "http://#{`hostname`}"
20
- @log = opts[:logger]
21
- @log ||= ::Log4r::Logger.new("notifier")
15
+ @log = opts[:log]
16
+ @from_address = opts[:from_address]
17
+ @website_uri = opts[:website_uri]
18
+
19
+ raise ArgumentError, "from address must be provided" unless @from_address
22
20
  end
23
21
 
24
22
  def notify(opts={})
25
23
  raise ArgumentError, "a recipient was not specified" unless opts[:who]
26
24
  raise ArgumentError, "a result was not specified" unless opts[:result]
27
-
25
+
26
+ # potential FIXME: refactor TMail out entirely?
28
27
  mail = TMail::Mail.new
29
28
  mail.to = opts[:who].email
30
29
  mail.from = @from_address
31
- mail.subject = "Check: #{opts[:result].id}, Status: #{opts[:result].status}"
30
+ mail.subject = "Check: #{opts[:result].check_id}, Status: #{opts[:result].status}"
32
31
  mail.body = <<-DESC
33
- Check #{opts[:result].id} returned the status "#{opts[:result].status}".
32
+ Check #{opts[:result].check_id} returned the status "#{opts[:result].status}".
34
33
 
35
34
  Here was the output:
36
35
  #{opts[:result].output}
37
36
 
38
37
  You can respond to this issue at:
39
- #{@website_uri}/issue/#{opts[:result].id}
38
+ #{@website_uri}/issue/#{opts[:result].check_id}
40
39
  DESC
41
40
 
42
41
  begin
@@ -31,8 +31,8 @@ module Flapjack
31
31
  raise ArgumentError, "a result was not specified" unless opts[:result]
32
32
 
33
33
  text = <<-DESC
34
- Check #{opts[:result].id} returned the status "#{opts[:result].status}".
35
- http://localhost:4000/checks/#{opts[:result].id}
34
+ Check #{opts[:result].check_id} returned the status "#{opts[:result].status}".
35
+ http://localhost:4000/checks/#{opts[:result].check_id}
36
36
  DESC
37
37
 
38
38
  message = Jabber::Message.new(opts[:who].jid, text)
@@ -22,5 +22,30 @@ module Log4r
22
22
  def error(args)
23
23
  err(args)
24
24
  end
25
+
26
+ def warning(args)
27
+ warn(args)
28
+ end
29
+ end
30
+ end
31
+
32
+ # extracted from Extlib.
33
+ # FIXME: what's the licensing here?
34
+ class String
35
+ def camel_case
36
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
37
+ split('_').map{|e| e.capitalize}.join
38
+ end
39
+ end
40
+
41
+ # http://gist.github.com/151324
42
+ class Hash
43
+ def symbolize_keys
44
+ inject({}) do |acc, (k,v)|
45
+ key = String === k ? k.to_sym : k
46
+ value = Hash === v ? v.symbolize_keys : v
47
+ acc[key] = value
48
+ acc
49
+ end
25
50
  end
26
51
  end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), 'couch', 'couch')
4
+ require File.join(File.dirname(__FILE__), 'couch', 'connection')
5
+
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Flapjack
4
+ module Persistence
5
+ class Couch
6
+ class Connection
7
+ class << self
8
+ attr_accessor :host, :port
9
+ def setup(options={})
10
+ @host = options[:host]
11
+ @port = options[:port]
12
+ @database = options[:database]
13
+ end
14
+
15
+ def get(id)
16
+ uri = "/#{@database}/#{id}"
17
+ req = ::Net::HTTP::Get.new(uri)
18
+ request(req)
19
+ end
20
+
21
+ def post(options={})
22
+ document = options[:document]
23
+ uri = "/#{@database}/"
24
+
25
+ req = ::Net::HTTP::Post.new(uri)
26
+ req["content-type"] = "application/json"
27
+ req.body = document.to_json
28
+
29
+ request(req)
30
+ end
31
+
32
+ def put(options={})
33
+ document = options[:document]
34
+ uri = "/#{@database}/#{(options[:document]["id"] || options[:document]["_id"])}"
35
+
36
+ req = ::Net::HTTP::Put.new(uri)
37
+ req["content-type"] = "application/json"
38
+ req.body = document.to_json
39
+
40
+ request(req)
41
+ end
42
+
43
+ def delete(options={})
44
+ document = options[:document]
45
+
46
+ uri = "/#{@database}/#{(options[:document]["id"] || options[:document]["_id"])}"
47
+
48
+ req = ::Net::HTTP::Put.new(uri)
49
+ req["content-type"] = "application/json"
50
+ req.body = document.to_json
51
+
52
+ request(req)
53
+ end
54
+
55
+ def request(request)
56
+ response = Net::HTTP.start(@host, @port) {|http| http.request(request)}
57
+
58
+ @parser = Yajl::Parser.new
59
+ hash = @parser.parse(response.body)
60
+ end
61
+ end
62
+ end
63
+ end # class Couch
64
+ end # module Persistence
65
+ end # module Flapjack
66
+
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'net/http'
4
+ require 'yajl/json_gem'
5
+
6
+ module Flapjack
7
+ module Persistence
8
+ class Couch
9
+
10
+ attr_accessor :config
11
+
12
+ def initialize(options={})
13
+ @options = options
14
+ @config = OpenStruct.new(options)
15
+ @log = @config.log
16
+
17
+ Flapjack::Persistence::Couch::Connection.setup(@options)
18
+ end
19
+
20
+ def any_parents_failed?(id)
21
+ id == "1" ? false : true
22
+ end
23
+
24
+ def save_check(attrs)
25
+ true
26
+ end
27
+
28
+ def get_check(id)
29
+ id == "4" ? nil : true
30
+ end
31
+
32
+ def delete_check(id)
33
+ true
34
+ end
35
+
36
+ def all_checks
37
+ [true, true, true]
38
+ end
39
+
40
+ def save_check_relationship(attrs)
41
+ true
42
+ end
43
+
44
+ def create_event(result)
45
+ true
46
+ end
47
+
48
+ def all_events_for(id)
49
+ [true, true, true]
50
+ end
51
+
52
+ def all_events
53
+ [true, true, true]
54
+ end
55
+
56
+ def all_check_relationships
57
+ [true, true, true]
58
+ end
59
+
60
+ end # class Couch
61
+ end # module Persistence
62
+ end # module Flapjack
63
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), 'data_mapper', 'data_mapper')
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'dm-core'
4
+ require 'dm-validations'
5
+ require 'dm-types'
6
+ require 'dm-timestamps'
7
+
8
+ Dir.glob(File.join(File.dirname(__FILE__), 'models', '*.rb')).each do |model|
9
+ require model
10
+ end
11
+
12
+ module Flapjack
13
+ module Persistence
14
+ class DataMapper
15
+ def initialize(options={})
16
+ @options = options
17
+ @config = OpenStruct.new(options)
18
+ @log = @config.log
19
+ connect
20
+ end
21
+
22
+ def any_parents_failed?(result)
23
+ check = Check.get(result.check_id)
24
+ if check
25
+ check.worst_parent_status != 0
26
+ else
27
+ @log.warning("Check with id #{result.check_id} doesn't exist!")
28
+ false # this will notify
29
+ end
30
+ end
31
+
32
+ def save(result)
33
+ check = Check.get(result.check_id)
34
+ if check
35
+ check.status = result.status
36
+ check.save
37
+ else
38
+ @log.warning("Check with id #{result.check_id} doesn't exist!")
39
+ true
40
+ end
41
+ end
42
+
43
+ def create_event(result)
44
+ event = Event.new(:check_id => result.check_id)
45
+ event.save
46
+ end
47
+
48
+ private
49
+ def connect
50
+ raise ArgumentError, "Database URI wasn't specified" unless @config.uri
51
+ ::DataMapper.setup(:default, @config.uri)
52
+ validate_structure
53
+ end
54
+
55
+ def validate_structure
56
+ begin
57
+ ::DataMapper.repository(:default).adapter.execute("SELECT 'id' FROM 'checks';")
58
+ rescue Sqlite3Error => e
59
+ @log.warning("The specified database doesn't appear to have any structure!")
60
+ @log.warning("Exiting.")
61
+ raise
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
@@ -74,17 +74,13 @@ class Check
74
74
  end
75
75
 
76
76
 
77
- GOOD = 0
78
- BAD = 1
79
- UGLY = 2
80
-
81
77
  def pretty_print_status
82
78
  case self.status
83
- when GOOD
79
+ when 0
84
80
  "good"
85
- when BAD
81
+ when 1
86
82
  "bad"
87
- when UGLY
83
+ when 2
88
84
  "ugly"
89
85
  end
90
86
  end
@@ -0,0 +1,17 @@
1
+ class Event
2
+ include DataMapper::Resource
3
+
4
+ timestamps :at
5
+
6
+ belongs_to :check
7
+
8
+ property :id, Serial, :key => true
9
+ property :check_id, Integer, :nullable => false
10
+ #property :check_output, Text, :nullable => false
11
+
12
+ # dm-timestamps
13
+ property :created_at, DateTime
14
+ property :updated_at, DateTime
15
+ property :deleted_at, ParanoidDateTime
16
+
17
+ end