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,98 @@
1
+ module CollinsNotify; module ConfigurationMixin
2
+
3
+ include Collins::Util
4
+
5
+ def config
6
+ raise NotImplementedError.new "ConfigurationMixin#config must be implemented"
7
+ end
8
+
9
+ def valid?
10
+ raise NotImplementedError.new "ConfigurationMixin#valid? must be implemented"
11
+ end
12
+
13
+ def fatal?; severity <= Logger::FATAL; end
14
+ def error?; severity <= Logger::ERROR; end
15
+ def warn?; severity <= Logger::WARN; end
16
+ def info?; severity <= Logger::INFO; end
17
+ def debug?; severity <= Logger::DEBUG; end
18
+ def trace?; severity <= Logger::TRACE; end
19
+ def sevname
20
+ if trace? then
21
+ "TRACE"
22
+ else
23
+ Logger::SEV_LABEL[severity]
24
+ end
25
+ end
26
+
27
+ def test?; (config.test == true); end
28
+
29
+ # Handles magic getters/setters
30
+ def method_missing m, *args, &block
31
+ kname = m.to_s.gsub(/=$/, '') # key name, remove = in case it's assignment
32
+ is_configurable = configurable.key?(kname.to_sym)
33
+ is_assignment = is_configurable && m.to_s[-1].eql?('=') && args.size > 0
34
+
35
+ # handles gets, these are the simplest
36
+ if is_configurable && !is_assignment then
37
+ get_cfg_var "@#{kname}".to_sym
38
+ elsif is_assignment then # handle set case
39
+ formatter_name = "format_#{kname}".to_sym
40
+ if args.size == 1 then
41
+ value = args.first
42
+ else
43
+ value = args
44
+ end
45
+
46
+ # Format value if a formatter exists
47
+ if respond_to?(formatter_name) then
48
+ value = send(formatter_name, value)
49
+ end
50
+
51
+ validator_name = "valid_#{kname}?".to_sym
52
+ unless respond_to?(validator_name) then
53
+ raise NotImplementedError.new "ConfigurationMixin##{validator_name} must be implemented"
54
+ end
55
+ if send(validator_name, value) then
56
+ set_cfg_var "@#{kname}".to_sym, value
57
+ else
58
+ raise CollinsNotify::ConfigurationError.new "#{kname} #{value.inspect} is not valid"
59
+ end
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def to_hash
66
+ configurable.inject({}) do |ret, (k,v)|
67
+ ret.update(k => get_cfg_var("@#{k.to_s}".to_sym))
68
+ end
69
+ end
70
+
71
+ protected
72
+ def configurable
73
+ {
74
+ :collins => {}, # collins configs
75
+ :config_file => nil,
76
+ :logfile => nil,
77
+ :recipient => nil,
78
+ :selector => {}, # optional collins asset to notify about
79
+ :severity => Logger::INFO,
80
+ :template => nil,
81
+ :template_dir => nil, # legacy needs
82
+ :template_format => :default,
83
+ :template_processor => :default,
84
+ :test => false,
85
+ :timeout => 10,
86
+ :type => nil,
87
+ }
88
+ end
89
+
90
+ def get_cfg_var name
91
+ config.instance_variable_get name
92
+ end
93
+
94
+ def set_cfg_var name, value
95
+ config.instance_variable_set name, value
96
+ end
97
+
98
+ end; end
@@ -0,0 +1,4 @@
1
+ module CollinsNotify
2
+ class CollinsNotifyException < StandardError; end
3
+ class ConfigurationError < CollinsNotifyException; end
4
+ end
@@ -0,0 +1,165 @@
1
+ require 'erb'
2
+ require 'nokogiri'
3
+
4
+ module CollinsNotify
5
+
6
+ class Notifier
7
+
8
+ include Collins::Util
9
+
10
+ class << self
11
+ def require_config *keys
12
+ @requires_config = keys.map{|k| k.to_sym}
13
+ end
14
+ def requires_config?
15
+ @requires_config && !@requires_config.empty?
16
+ end
17
+ def required_config_keys
18
+ @requires_config || []
19
+ end
20
+ def register_name name
21
+ @adapter_name = name
22
+ end
23
+ def adapter_name
24
+ @adapter_name
25
+ end
26
+ def supports_mimetype *mimetypes
27
+ @supported_mimetypes = mimetypes.map{|e| e.to_sym}
28
+ end
29
+ def mimetypes
30
+ @supported_mimetypes || [:text]
31
+ end
32
+ def adapters
33
+ ObjectSpace.each_object(Class).select { |klass| klass < self }.inject({}) do |ret,k|
34
+ ret.update(k.adapter_name => k)
35
+ end
36
+ end
37
+ def get_adapter application
38
+ type = application.type
39
+ config = application.config
40
+ adapter_config = config.adapters[type]
41
+ adapter = adapters[type]
42
+ (raise CollinsNotifyException.new("Invalid notify type #{type}")) if adapter.nil?
43
+ if adapter.requires_config? then
44
+ (raise CollinsNotify::ConfigurationError.new "No config found for #{type}") if adapter_config.nil?
45
+ adapter.required_config_keys.each do |key|
46
+ unless adapter_config.key?(key) then
47
+ raise CollinsNotify::ConfigurationError.new "Missing #{type}.#{key} config key"
48
+ end
49
+ end
50
+ end
51
+ (raise CollinsNotifyException.new("No config found for #{type}")) if adapter.requires_config? and adapter_config.nil?
52
+ adapter.new application
53
+ end
54
+ end
55
+
56
+ def initialize app
57
+ @config = app.config
58
+ @logger = app.logger
59
+ end
60
+
61
+ # Throw an exception if it cant be configured
62
+ def configure!
63
+ raise NotImplementedError.new "CollinsNotify::Notifier#configure! must be implemented"
64
+ end
65
+
66
+ # Return boolean indicating success/fail
67
+ def notify! message_obj = OpenStruct.new, to = nil
68
+ raise NotImplementedError.new "CollinsNotify::Notifier#notify! must be implemented"
69
+ end
70
+
71
+ def supports_text?
72
+ self.class.mimetypes.include?(:text)
73
+ end
74
+ def supports_html?
75
+ self.class.mimetypes.include?(:html)
76
+ end
77
+
78
+ protected
79
+ attr_reader :config
80
+ attr_reader :logger
81
+
82
+ # Retrieve (safely) a key from a binding
83
+ def fetch_bound_option b, name, default
84
+ begin
85
+ eval(name, b)
86
+ rescue Exception => e
87
+ logger.trace "Could not find value name #{name} in binding"
88
+ default
89
+ end
90
+ end
91
+
92
+ # Retrieve (safely) a key from a message object which may be a hash, OpenStruct, or arbitrary
93
+ # value
94
+ def fetch_mo_option mo, key, default
95
+ value = default
96
+ if mo.is_a?(Hash) && mo.key?(key) && !mo[key].nil? then
97
+ value = mo[key]
98
+ elsif mo.respond_to?(key) && !mo.send(key).nil? then
99
+ value = mo.send(key)
100
+ end
101
+ value
102
+ end
103
+
104
+ # b is the binding to use
105
+ def get_message_body b
106
+ if config.stdin? then
107
+ message_txt = $stdin.read.strip
108
+ if config.template_processor == :erb then
109
+ render_template message_txt, b
110
+ else
111
+ message_txt
112
+ end
113
+ elsif config.template? then
114
+ tmpl = config.resolved_template
115
+ logger.debug "Using template file #{tmpl}"
116
+ render_template File.new(tmpl), b
117
+ else
118
+ raise CollinsNotify::CollinsNotifyException.new "Unknown message body type"
119
+ end
120
+ end
121
+
122
+ def render_template tmpl, b
123
+ template_format = config.template_format
124
+ if template_format == :default && tmpl.is_a?(File) && tmpl.path.include?(".html") then
125
+ logger.info "Detected HTML formatted template file, will render to HTML if possible"
126
+ # If format was unspecified but it seems like it's html, make it so
127
+ template_format = :html
128
+ end
129
+ tmpl_txt = tmpl.is_a?(File) ? tmpl.read : tmpl
130
+ begin
131
+ template = ERB.new(tmpl_txt, nil, '<>')
132
+ html_text = plain_text = nil
133
+ if template_format == :html then
134
+ logger.debug "Template format is html, rendering it as HTML"
135
+ html_text = template.result(b)
136
+ plain_text = as_plain_text html_text
137
+ else
138
+ logger.debug "Template format is plain text, treating it as such"
139
+ plain_text = template.result(b)
140
+ end
141
+ if supports_html? then
142
+ logger.info "HTML is supported by #{self.class.adapter_name}"
143
+ handle_html b, html_text, plain_text
144
+ else
145
+ logger.info "Only plain text supported by #{self.class.adapter_name}"
146
+ logger.debug "Rendered plain text: '#{plain_text.gsub(/[\r\n]/, ' ')}'"
147
+ plain_text
148
+ end
149
+ rescue Exception => e
150
+ raise CollinsNotify::CollinsNotifyException.new "Invalid template #{tmpl} - #{e}"
151
+ end
152
+ end
153
+
154
+ # b is original binding, html is html version, plain_text is plain text version
155
+ def handle_html b, html, plain_text
156
+ html
157
+ end
158
+
159
+ def as_plain_text html
160
+ Nokogiri::HTML(html).text
161
+ end
162
+
163
+ end
164
+
165
+ end
@@ -0,0 +1,128 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+
4
+ module CollinsNotify
5
+
6
+ class Options < OptionParser
7
+ ETC_CONFIG = "/etc/collins_notify.yaml"
8
+
9
+ include Collins::Util
10
+
11
+ def self.app_name
12
+ File.basename($0)
13
+ end
14
+ def self.get_instance config
15
+ i = CollinsNotify::Options.new config
16
+ i.banner = "Usage: #{app_name} --config=CONFIG [options]"
17
+ i.separator ""
18
+ i.instance_eval { setup }
19
+ i
20
+ end
21
+
22
+ def parse! argv = default_argv
23
+ res = super
24
+ cfg_file = get_config_file
25
+ if cfg_file then #config.config_file then
26
+ yaml = YAML::load(File.open(cfg_file))
27
+ adapters = CollinsNotify::Notifier.adapters.keys
28
+ yaml.each do |k,v|
29
+ if adapters.include?(k.to_sym) then
30
+ config.adapters[k.to_sym] = symbolize_hash(v)
31
+ else
32
+ try_configure k, v
33
+ end
34
+ end
35
+ end
36
+ res
37
+ end
38
+
39
+ protected
40
+ attr_reader :config
41
+ def initialize config
42
+ @config = config
43
+ super()
44
+ end
45
+
46
+ def get_config_file
47
+ if config.config_file then
48
+ config.config_file
49
+ elsif File.exists?(ETC_CONFIG) && File.readable?(ETC_CONFIG) then
50
+ ETC_CONFIG
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def setup
57
+ separator "Specific options:"
58
+ on('-c', '--config=CONFIG', 'Notifier config. Must include any neccesary credentials for the specified notify type') do |config|
59
+ @config.config_file = config
60
+ end
61
+ on('--recipient=NAME', 'Email address, username, channel, etc') do |n|
62
+ @config.recipient = n
63
+ end
64
+ on('--selector=SELECTOR', 'Selector to use to retrieve collins assets') do |s|
65
+ @config.selector = eval(s)
66
+ end
67
+ on('--tag=TAG', 'Unique tag of asset') do |t|
68
+ @config.selector = {:tag => t}
69
+ end
70
+ adapters = CollinsNotify::Notifier.adapters.keys
71
+ msg = "Select notification type (#{adapters.sort.join(', ')})"
72
+ on('--type=TYPE', adapters, msg) do |t|
73
+ @config.type = t
74
+ end
75
+ separator ""
76
+ separator "Common options:"
77
+ on_tail('-d', '--debug', 'Legacy, same as -v') do
78
+ @config.increase_verbosity
79
+ end
80
+ on_tail('-h', '--help', 'This help') do
81
+ $stdout.puts self
82
+ exit 0
83
+ end
84
+ on_tail('--logfile=FILE', 'Log file to use, or stderr') do |file|
85
+ @config.logfile = file
86
+ end
87
+ on_tail('--template=TEMPLATE', 'Template to use for notifications, erb file') do |t|
88
+ @config.template = t
89
+ end
90
+ on_tail('--template-dir=DIR', 'Use fully qualified path in --template, do not use this') do |d|
91
+ @config.template_dir = d
92
+ end
93
+ on_tail('--template-format=FMT', [:default, :html], 'Template format (default, html).') do |t|
94
+ @config.template_format = t.to_sym
95
+ end
96
+ on_tail('--template-processor=PROC', [:default, :erb], 'Template processor (default, erb).') do |t|
97
+ @config.template_processor = t.to_sym
98
+ end
99
+ on_tail('-t', '--[no-]test', 'Enable or disable testing') do |t|
100
+ @config.test = t
101
+ end
102
+ on_tail('--timeout=TIMEOUT', Integer, 'Timeout in seconds, defaults to 10') do |t|
103
+ @config.timeout = t
104
+ end
105
+ on_tail('-v', '--verbose', verbose_help) do
106
+ @config.increase_verbosity
107
+ end
108
+ on_tail('-V', '--version', 'Software version') do
109
+ $stdout.puts "collins_notify #{CollinsNotify::Version}"
110
+ exit 0
111
+ end
112
+ end
113
+ def verbose_help
114
+ "Increase verbosity. Specify multiple -v options to increase (up to 4)"
115
+ end
116
+
117
+ private
118
+ def try_configure key, value
119
+ begin
120
+ config.send("#{key}=".to_sym, value)
121
+ rescue Exception => e
122
+ raise CollinsNotify::ConfigurationError.new "#{key} is not a valid config option"
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,19 @@
1
+ module CollinsNotify; module Version
2
+
3
+ class << self
4
+ def location= loc
5
+ @location = loc
6
+ end
7
+ def location
8
+ @location || File.absolute_path(File.join(File.dirname(__FILE__), '..', '..', 'VERSION'))
9
+ end
10
+ def to_s
11
+ if location && File.exists?(location) then
12
+ File.read(location)
13
+ else
14
+ "0.0.0-pre"
15
+ end
16
+ end
17
+ end
18
+
19
+ end; end
@@ -0,0 +1,32 @@
1
+ ---
2
+ :collins:
3
+ host: "https://collins.example.net"
4
+ username: "some_username"
5
+ password: "some_password"
6
+ timeout: 30
7
+ :email:
8
+ address: "smtp.sendgrid.net"
9
+ port: 587
10
+ domain: "example.com"
11
+ user_name: "some_username"
12
+ password: "some_password"
13
+ authentication: "plain"
14
+ enable_starttls_auto: true
15
+ sender_address: "Collins <collins@example.com>"
16
+ :hipchat:
17
+ api_token: "api_token_here"
18
+ colors:
19
+ success: "green"
20
+ error: "red"
21
+ notify: true
22
+ room: "AutoBots"
23
+ from_username: "Collins"
24
+ :irc:
25
+ username: "some_username"
26
+ password: "some_password"
27
+ host: "hostname"
28
+ port: 6697
29
+ ssl: true
30
+ join: true
31
+ notice: true
32
+ channel: "#test"