collins_notify 0.0.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.
@@ -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