collins_notify 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ require 'hipchat'
2
+
3
+ module CollinsNotify
4
+
5
+ class HipchatAdapter < Notifier
6
+ register_name :hipchat
7
+ supports_mimetype :text
8
+ require_config :api_token, :from_username # who to send the message as
9
+
10
+ # Constants for coloring different messages
11
+ module HipchatStatus
12
+ SUCCESS = "green"
13
+ WARNING = "yellow"
14
+ ERROR = "red"
15
+ STATUS = "purple"
16
+ RANDOM = "random"
17
+ end
18
+
19
+ def configure!
20
+ @client = HipChat::Client.new api_token
21
+ cfg_colors = hipchat_options.fetch(:colors, {})
22
+ @colors = {
23
+ :success => cfg_colors.fetch(:success, HipchatStatus::SUCCESS),
24
+ :warning => cfg_colors.fetch(:warning, HipchatStatus::WARNING),
25
+ :error => cfg_colors.fetch(:error, HipchatStatus::ERROR),
26
+ :status => cfg_colors.fetch(:status, HipchatStatus::STATUS),
27
+ :random => cfg_colors.fetch(:random, HipchatStatus::RANDOM)
28
+ }
29
+ logger.info "Configured Hipchat client"
30
+ end
31
+
32
+ # Bindings available
33
+ # room_name - hipchat room
34
+ # username - user message being sent as
35
+ # at - If we are at'ing a person
36
+ def notify! message_obj = OpenStruct.new, to = nil
37
+ room_name = get_room message_obj
38
+ username = get_from_username message_obj
39
+ if room_name.nil? then
40
+ raise CollinsNotify::CollinsNotifyException.new "No obj.room or hipchat.room specified"
41
+ end
42
+ message = get_message_body(binding).strip.gsub(/[\n\r]/, ' ')
43
+ message = "@#{to} #{message}" unless to.nil?
44
+ logger.info "Notifying #{room_name} from #{username} with message #{message}"
45
+ if config.test? then
46
+ logger.info "Not sending hipchat message in test mode"
47
+ return true
48
+ end
49
+ opts = client_options(message_obj)
50
+ logger.debug "Using hipchat options #{opts.inspect}"
51
+ client[room_name].send(username, message, client_options(message_obj))
52
+ end
53
+
54
+ protected
55
+ attr_accessor :client, :colors
56
+ def api_token
57
+ hipchat_options[:api_token]
58
+ end
59
+ def color mo
60
+ if color = fetch_mo_option(mo, :color, false) then
61
+ return color
62
+ end
63
+ [:success, :warning, :error, :status].each do |key|
64
+ if fetch_mo_option(mo, key, false) then
65
+ return colors[key]
66
+ end
67
+ end
68
+ colors[:status]
69
+ end
70
+ def client_options mo
71
+ opts = {}
72
+ opts[:notify] = true if hipchat_options[:notify]
73
+ opts[:color] = color(mo)
74
+ opts
75
+ end
76
+ def get_from_username mo
77
+ fetch_mo_option(mo, :from_username, hipchat_options[:from_username])
78
+ end
79
+ def hipchat_options
80
+ @hipchat_options ||= symbolize_hash(deep_copy_hash(config.adapters[:hipchat]))
81
+ end
82
+ def get_room mo
83
+ if config.recipient then
84
+ config.recipient
85
+ else
86
+ fetch_mo_option(mo, :room, hipchat_options[:room])
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,88 @@
1
+ require 'collins_notify/adapter/helper/carried-pigeon'
2
+
3
+ module CollinsNotify
4
+ class IrcAdapter < Notifier
5
+ register_name :irc
6
+ supports_mimetype :text
7
+ require_config :username, :host, :port
8
+
9
+ def configure!
10
+ # An exception gets thrown if needed
11
+ get_channel symbolize_hash(deep_copy_hash(config.adapters[:irc])), nil
12
+ nil
13
+ end
14
+
15
+ # Available in template binding:
16
+ # message_obj - Depends on call
17
+ # nick - nickname
18
+ # channel - channel sending to
19
+ # host - host connecting to
20
+ # port - port on host being connected to
21
+ def notify! message_obj = OpenStruct.new, to = nil
22
+ tmp_config = symbolize_hash(deep_copy_hash(config.adapters[:irc]))
23
+ host = tmp_config.delete(:host)
24
+ port = tmp_config.delete(:port).to_i
25
+ nick = tmp_config.delete(:username)
26
+ channel = get_channel(tmp_config, to)
27
+ tmp_config.delete(:channel)
28
+ cp_config = {
29
+ :host => host,
30
+ :port => port,
31
+ :nick => nick,
32
+ :channel => channel,
33
+ :logger => logger
34
+ }
35
+ cp_config.merge!(tmp_config)
36
+ logger.trace "Using IRC config: #{cp_config.inspect}"
37
+ if config.test? then
38
+ logger.info "Not sending message in test mode"
39
+ return true
40
+ end
41
+ cp = try_connect cp_config
42
+ return false unless cp
43
+ logger.info "Connected to IRC"
44
+ begin
45
+ body = get_message_body(binding).strip.gsub(/[\n\r]/, ' ')
46
+ cp.message body, cp_config[:notice]
47
+ true
48
+ rescue CollinsNotify::CollinsNotifyException => e
49
+ logger.error "error sending irc notification - #{e}"
50
+ raise e
51
+ rescue Exception => e
52
+ logger.error "#{e.class.to_s} - error sending irc notification - #{e}"
53
+ raise CollinsNotify::CollinsNotifyException.new e
54
+ ensure
55
+ cp.die
56
+ end
57
+ end
58
+
59
+ protected
60
+ def get_channel hash, to
61
+ if config.recipient then
62
+ make_channel config.recipient
63
+ elsif to then
64
+ make_channel to
65
+ elsif hash[:channel] then
66
+ make_channel hash.delete(:channel)
67
+ else
68
+ raise CollinsNotify::ConfigurationError.new "No irc.channel or config.recipient specified"
69
+ end
70
+ end
71
+
72
+ def make_channel chan
73
+ name = chan.start_with?('#') ? chan : "##{chan}"
74
+ name.gsub(/[^A-Za-z0-9#]/, '_')
75
+ end
76
+
77
+ def try_connect config
78
+ begin
79
+ CarriedPigeon.new config
80
+ rescue Exception => e
81
+ logger.error "error connecting to server #{config[:host]} - #{e}"
82
+ false
83
+ end
84
+ end
85
+
86
+ end # class IrcAdapter
87
+
88
+ end # module CollinsNotify
@@ -0,0 +1,55 @@
1
+ module CollinsNotify
2
+ class Application
3
+ include CollinsNotify::ConfigurationMixin
4
+ include Collins::Util::Logging
5
+
6
+ attr_writer :config
7
+ attr_reader :logger
8
+
9
+ def initialize
10
+ @logger = get_logger :logfile => $stderr, :program => 'collins_notify'
11
+ @config = CollinsNotify::Configuration.new @logger
12
+ end
13
+
14
+ # Override ConfigMixin::#config
15
+ def config
16
+ @config
17
+ end
18
+
19
+ def configure! argv
20
+ o = CollinsNotify::Options.get_instance config
21
+ o.parse! argv
22
+ if config.logfile then
23
+ @logger = get_logger :logfile => config.logfile, :program => 'collins_notify'
24
+ end
25
+ @logger.level = config.severity
26
+ end
27
+
28
+ def notify! message_obj = OpenStruct.new, to = nil
29
+ Timeout::timeout(config.timeout) do
30
+ notifier.notify! message_obj, to
31
+ end
32
+ end
33
+
34
+ # Override ConfigurationMixin#valid?
35
+ def valid?
36
+ if config.valid? then
37
+ begin
38
+ @notifier = CollinsNotify::Notifier.get_adapter self
39
+ @notifier.configure!
40
+ rescue Exception => e
41
+ logger.error "Error configuring #{type} notification - #{e}"
42
+ pp e.backtrace
43
+ return false
44
+ end
45
+ true
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ protected
52
+ attr_accessor :notifier
53
+
54
+ end
55
+ end
@@ -0,0 +1,73 @@
1
+ module CollinsNotify
2
+ class CommandRunner
3
+
4
+ def run args
5
+ app = CollinsNotify::Application.new
6
+
7
+ begin
8
+ app.configure! args
9
+ get_contact_and_asset app, true # throws exception if not configured properly
10
+ rescue SystemExit => e
11
+ exit e.status
12
+ rescue CollinsNotify::ConfigurationError => e
13
+ app.logger.fatal "Error with input option - #{e}"
14
+ exit 1
15
+ rescue StandardError => e
16
+ app.logger.fatal "Error parsing CLI options - #{e}"
17
+ $stdout.puts CollinsNotify::Options.get_instance(app.config)
18
+ exit 2
19
+ end
20
+ # This is legacy behavior
21
+ app.config.type = :email if app.config.type.nil?
22
+
23
+ unless app.valid? then
24
+ $stdout.puts CollinsNotify::Options.get_instance(app.config)
25
+ exit 3
26
+ end
27
+
28
+ app.logger.trace "Configuration -> #{app.config.to_hash.inspect}"
29
+
30
+ begin
31
+ contact, asset = get_contact_and_asset app
32
+ if !app.notify!(asset, contact) then
33
+ raise Exception.new "failed"
34
+ end
35
+ rescue Timeout::Error => e
36
+ app.logger.fatal "TIMEOUT sending notification via #{app.type}"
37
+ exit 4
38
+ rescue Exception => e
39
+ app.logger.fatal "ERROR sending notification via #{app.type} - #{e}"
40
+ exit 5
41
+ end
42
+
43
+ app.logger.info "Successfully sent #{app.type} notification"
44
+ exit 0
45
+ end
46
+
47
+ protected
48
+ def get_contact_and_asset app, test_only = false
49
+ return [nil, OpenStruct.new] unless app.selector and !app.selector.empty?
50
+ ccfg = app.config.collins
51
+ if ccfg.nil? or ccfg.empty? then
52
+ raise CollinsNotify::ConfigurationError.new "No collins configuration but selector specified"
53
+ end
54
+ collins = Collins::Client.new ccfg.merge(:logger => app.logger)
55
+ return if test_only
56
+ asset = nil
57
+ if app.selector[:tag] then
58
+ app.logger.info "Finding collins asset using asset tag #{app.selector[:tag]}"
59
+ asset = collins.get app.selector[:tag]
60
+ else
61
+ app.logger.info "Finding collins asset using asset selector #{app.selector.inspect}"
62
+ assets = collins.find(app.selector)
63
+ app.logger.debug "Found #{assets.size} assets in collins using selector, only using first"
64
+ asset = assets.first
65
+ end
66
+ if asset.nil? then
67
+ raise CollinsNotify::ConfigurationError.new "No asset found using #{app.selector.inspect}"
68
+ end
69
+ [asset.contact, asset]
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,198 @@
1
+ module CollinsNotify
2
+
3
+ class Configuration
4
+ include CollinsNotify::ConfigurationMixin
5
+
6
+ attr_reader :logger
7
+ attr_accessor :adapters
8
+
9
+ def initialize logger, opts = {}
10
+ @adapters = {}
11
+ configurable.each do |key, value|
12
+ instance_variable_set "@#{key.to_s}".to_sym, value
13
+ end
14
+ @logger = logger
15
+ merge opts
16
+ end
17
+
18
+ # Override ConfigurationMixin#config
19
+ def config
20
+ self
21
+ end
22
+
23
+ # Override ConfigurationMixin#valid?
24
+ def valid?
25
+ adapters = CollinsNotify::Notifier.adapters.keys
26
+ if type.nil? || !adapters.include?(type) then
27
+ msg = "Invalid notifier type specified. Valid notifiers: #{adapters.sort.join(', ')}"
28
+ logger.error msg
29
+ return false
30
+ end
31
+ unless message_body? then
32
+ logger.error "No message body found in specified template or stdin"
33
+ return false
34
+ end
35
+ true
36
+ end
37
+
38
+ # Shorthand for severity assignment but silently fails if too verbose
39
+ def increase_verbosity
40
+ if severity > -1 then
41
+ self.severity -= 1
42
+ if trace? then
43
+ $DEBUG = true
44
+ end
45
+ end
46
+ end
47
+
48
+ def message_body?
49
+ template? || stdin?
50
+ end
51
+
52
+ def resolved_template
53
+ return nil unless template
54
+ if File.exists?(File.expand_path(template)) then
55
+ return File.expand_path(template)
56
+ elsif template_dir && File.exists?(File.expand_path(File.join(template_dir, template))) then
57
+ return File.expand_path(File.join(template_dir, template))
58
+ elsif File.exists?(File.expand_path(File.join(default_template_dir, template))) then
59
+ return File.expand_path(File.join(default_template_dir, template))
60
+ end
61
+ end
62
+
63
+ def stdin?
64
+ !$stdin.tty?
65
+ end
66
+
67
+ def template?
68
+ !resolved_template.nil?
69
+ end
70
+
71
+ alias_method :original_to_hash, :to_hash
72
+ def to_hash
73
+ original_to_hash.merge(:severity_name => sevname, :adapters => adapters)
74
+ end
75
+
76
+ protected
77
+ def default_template_dir
78
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'templates'))
79
+ end
80
+
81
+ # Merge opts from the constructor into here
82
+ def merge opts
83
+ opts.each do |k,v|
84
+ if configurable.key?(k.to_sym) && !v.nil? then
85
+ self.send("#{k}=".to_sym, v)
86
+ end
87
+ end
88
+ end
89
+
90
+ ###################################################################################
91
+ # Validators and Formatters
92
+ ###################################################################################
93
+ def format_collins cfg
94
+ symbolize_hash(cfg) unless cfg.nil?
95
+ end
96
+ def valid_collins? cfg
97
+ if !cfg.nil? && cfg.is_a?(Hash) then
98
+ [:host,:username,:password].each do |key|
99
+ if !cfg.key?(key) then
100
+ return false
101
+ end
102
+ end
103
+ true
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ def format_config_file file
110
+ format_file file
111
+ end
112
+ def valid_config_file? file
113
+ valid_file? file
114
+ end
115
+
116
+ def format_logfile file
117
+ format_file file
118
+ end
119
+ def valid_logfile? file
120
+ valid_file? file
121
+ end
122
+
123
+ def valid_recipient? recip
124
+ !recip.nil?
125
+ end
126
+
127
+ def valid_selector? sel
128
+ !sel.nil? && sel.is_a?(Hash) && !sel.empty?
129
+ end
130
+
131
+ def format_severity sev
132
+ sev.to_i unless sev.nil?
133
+ end
134
+ def valid_severity? sev
135
+ !sev.nil? && sev.is_a?(Fixnum) && sev >= -1 && sev <= 3
136
+ end
137
+
138
+ def format_template file
139
+ f = format_file file
140
+ if File.exists?(f) then
141
+ f
142
+ elsif File.exists?(File.expand_path(File.join(default_template_dir, file))) then
143
+ File.expand_path(File.join(default_template_dir, file))
144
+ end
145
+ end
146
+ def valid_template? file
147
+ valid_file? file
148
+ end
149
+
150
+ def format_template_dir d
151
+ File.expand_path(d.to_s) unless d.nil?
152
+ end
153
+ def valid_template_dir? d
154
+ !d.nil? && File.exists?(d)
155
+ end
156
+
157
+ def format_template_format f
158
+ f.to_sym unless f.nil?
159
+ end
160
+ def valid_template_format? t
161
+ !t.nil? && t.is_a?(Symbol) && [:default,:html].include?(t)
162
+ end
163
+
164
+ def format_template_processor f
165
+ f.to_sym unless f.nil?
166
+ end
167
+ def valid_template_processor? t
168
+ !t.nil? && t.is_a?(Symbol) && [:default,:erb].include?(t)
169
+ end
170
+
171
+ def valid_test? t
172
+ !t.nil? && (t.is_a?(TrueClass) || t.is_a?(FalseClass))
173
+ end
174
+
175
+ def format_timeout t
176
+ t.to_i unless t.nil?
177
+ end
178
+ def valid_timeout? t
179
+ !t.nil? && t.is_a?(Fixnum) && t > 0
180
+ end
181
+
182
+ def format_type t
183
+ t.to_sym unless t.nil?
184
+ end
185
+ def valid_type? t
186
+ !t.nil? && t.is_a?(Symbol)
187
+ end
188
+
189
+ private
190
+ def format_file file
191
+ File.expand_path(file) unless file.nil?
192
+ end
193
+ def valid_file? file
194
+ !file.nil? && File.exists?(file) && File.readable?(file)
195
+ end
196
+ end
197
+
198
+ end