lita-enhance 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE +19 -0
  6. data/README.md +95 -0
  7. data/Rakefile +6 -0
  8. data/lib/lita-enhance.rb +7 -0
  9. data/lib/lita/handlers/enhance.rb +209 -0
  10. data/lib/lita/handlers/enhance/chef_indexer.rb +133 -0
  11. data/lib/lita/handlers/enhance/enhancer.rb +58 -0
  12. data/lib/lita/handlers/enhance/enhancers/hostname_enhancer.rb +72 -0
  13. data/lib/lita/handlers/enhance/enhancers/instance_id_enhancer.rb +40 -0
  14. data/lib/lita/handlers/enhance/enhancers/ip_enhancer.rb +40 -0
  15. data/lib/lita/handlers/enhance/enhancers/mac_address_enhancer.rb +42 -0
  16. data/lib/lita/handlers/enhance/node.rb +57 -0
  17. data/lib/lita/handlers/enhance/node_index.rb +41 -0
  18. data/lib/lita/handlers/enhance/session.rb +77 -0
  19. data/lita-enhance.gemspec +23 -0
  20. data/locales/en.yml +16 -0
  21. data/spec/data/box01.json +214 -0
  22. data/spec/data/box02.json +163 -0
  23. data/spec/data/box03.json +123 -0
  24. data/spec/data/stg-web01.json +89 -0
  25. data/spec/data/web01.json +89 -0
  26. data/spec/lita/handlers/enhance/chef_indexer_spec.rb +18 -0
  27. data/spec/lita/handlers/enhance/enhancer_example.rb +16 -0
  28. data/spec/lita/handlers/enhance/enhancers/hostname_enhancer_spec.rb +58 -0
  29. data/spec/lita/handlers/enhance/enhancers/instance_id_enhancer_spec.rb +31 -0
  30. data/spec/lita/handlers/enhance/enhancers/ip_enhancer_spec.rb +48 -0
  31. data/spec/lita/handlers/enhance/enhancers/mac_address_enhancer_spec.rb +32 -0
  32. data/spec/lita/handlers/enhance/node_index_spec.rb +33 -0
  33. data/spec/lita/handlers/enhance/node_spec.rb +51 -0
  34. data/spec/lita/handlers/enhance/session_spec.rb +48 -0
  35. data/spec/lita/handlers/enhance_spec.rb +136 -0
  36. data/spec/spec_helper.rb +64 -0
  37. metadata +168 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d328609e647b18fe537f7456b95728754019f40f
4
+ data.tar.gz: 6a90c0ca0b0e92da38d212caa14b9477d5548e05
5
+ SHA512:
6
+ metadata.gz: 74f55095d74a7c740dde25093af30f70e8919237549fdfdc8a6c88537c322e9d5c8b847bfc0a92e8298b491098143f4d8024e5f8dd0c6e9c0ee6388376d352b0
7
+ data.tar.gz: 4ce5807e836c8a5c448439a873f4b7f8252ba7e5904cc06d6a973917f75fea0f37806ae89a5b150f95c7366e0995aad9a9fdada3f72a67a3ba04aac5f66187d1
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .ruby-version
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ cache: bundler
2
+ language: ruby
3
+ rvm:
4
+ - 2.1
5
+ - 2.0
6
+ services:
7
+ - redis-server
8
+ sudo: false
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'travis'
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 PagerDuty
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # lita-enhance
2
+
3
+ Enhances text that contains opaque machine identifiers by replacing them with that machine's hostname.
4
+
5
+ ```
6
+ you> enhance 10.214.0.1
7
+ lita> *box01*
8
+ ```
9
+
10
+ Text that is enhanced is returned in the same format that is passed in, which makes enhancing log lines or CLI output nice to view.
11
+
12
+ ```
13
+ you> enhance Finished hinted handoff of 8826 rows to endpoint /192.168.100.1
14
+ lita> Finished hinted handoff of 8826 rows to endpoint /*box01*
15
+
16
+ you> enhance tcp 0 0 10.214.0.1:ssh 10.214.0.2:4997 ESTABLISHED
17
+ lita> tcp 0 0 *box01*:ssh *box02*:4997 ESTABLISHED
18
+ ```
19
+
20
+ You can increase the level of enhancement to add more machine details to the output.
21
+
22
+ ```
23
+ you> enhance 10.214.0.1
24
+ lita> *box01*
25
+
26
+ you> enhance lvl:2 10.214.0.1
27
+ lita> *box01 (us-west-2)*
28
+ ```
29
+
30
+ Machine details are obtained by indexing database of machine assets. Right now only Chef servers are indexed. Results are indexed every 15 minutes by default. Old machines are left in the index so you can identify them even after they have been torn down.
31
+
32
+ ```
33
+ you> enhance lvl:2 old-box01
34
+ lita> ¿old-box01 (us-east-1)?
35
+ ```
36
+
37
+ [And for fun](https://www.youtube.com/watch?v=Vxq9yj2pVWk), you can implicitly enhance previously enhanced text by just sending ```enhance```. The string to enhance is retained separately in each room that the enhance string was sent.
38
+
39
+ ```
40
+ you> enhance 10.214.0.1
41
+ lita> *box01*
42
+
43
+ you> enhance
44
+ lita> *box01 (us-west-2)*
45
+ ```
46
+
47
+ ## Installation
48
+
49
+ Add lita-enhance to your Lita instance's Gemfile:
50
+
51
+ ``` ruby
52
+ gem "lita-enhance"
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ ```ruby
58
+ # Configure one or more knife files for the Chef server that lita-enhance should index
59
+ config.handlers.enhance.knife_configs = {
60
+ 'staging' => 'knife-staging.rb',
61
+ 'production' => 'knife-production.rb'
62
+ }
63
+
64
+ # How often to refresh enhance's index in seconds. Default is 15 minutes.
65
+ config.handlers.enhance.refresh_interval = 15 * 60
66
+
67
+ # How long in seconds to remember messages to implicitly enhance. Default is 1 week.
68
+ config.handlers.enhance.blurry_message_ttl = 7 * 24 * 60 * 60
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ```
74
+ # Enhance IP addresses (public and private) with the machine's name if it is known
75
+ enhance 54.189.200.22
76
+ enhance 10.214.0.1
77
+
78
+ # Enhance EC2 host names
79
+ enhance ec2-54-189-200-22.us-west-2.compute.amazonaws.com
80
+ enhance ip-10-214-13-102.us-west-2.compute.internal
81
+
82
+ # Enhance host names (short & long)
83
+ enhance box01
84
+ enhance box01.example.com
85
+
86
+ # Enhance EC2 instance IDs
87
+ enhance i-123456
88
+
89
+ # Enhance MAC addresses
90
+ enhance 22:00:0a:00:32:23
91
+ ```
92
+
93
+ ## License
94
+
95
+ [MIT](http://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ require "lita"
2
+
3
+ Lita.load_locales Dir[File.expand_path(
4
+ File.join("..", "..", "locales", "*.yml"), __FILE__
5
+ )]
6
+
7
+ require "lita/handlers/enhance"
@@ -0,0 +1,209 @@
1
+ require 'thread'
2
+
3
+ require 'lita/handlers/enhance/chef_indexer'
4
+ require 'lita/handlers/enhance/node'
5
+ require 'lita/handlers/enhance/enhancer'
6
+ require 'lita/handlers/enhance/session'
7
+
8
+ module Lita
9
+ module Handlers
10
+ class Enhance < Handler
11
+ on :loaded, :setup_background_refresh
12
+
13
+ route(
14
+ /^refresh enhance$/,
15
+ :refresh,
16
+ command: true
17
+ )
18
+
19
+ route(
20
+ /^enhance stats$/,
21
+ :stats,
22
+ command: true
23
+ )
24
+
25
+ route(
26
+ /\Aenhance(\slvl:([0-9]))?(\s(.*))?\z/m,
27
+ :enhance,
28
+ command: true,
29
+ help: {
30
+ 'enhance <anything>' => 'Enhances details in your text. https://www.youtube.com/watch?v=Vxq9yj2pVWk'
31
+ }
32
+ )
33
+
34
+ def self.default_config(config)
35
+ config.knife_configs = {}
36
+ if File.exist?('~/.chef/knife.rb')
37
+ config.knife_configs['default'] = '~/.chef/knife.rb'
38
+ end
39
+
40
+ config.refresh_interval = 15 * 60
41
+ config.add_quote = true
42
+
43
+ # How long to remember the previously enhanced message for.
44
+ config.blurry_message_ttl = 7 * 24 * 60 * 60 # seconds
45
+ end
46
+
47
+ def setup_background_refresh(payload)
48
+ @@chef_indexer = ChefIndexer.new(redis, config.knife_configs)
49
+ @@enhancers = Enhancer.all.map do |enhancer_klass|
50
+ enhancer_klass.new(redis)
51
+ end
52
+
53
+ bg_refresh = proc do
54
+ begin
55
+ lock_and_refresh_index
56
+ rescue => e
57
+ # Keep error from killing background thread
58
+ log.error { "#{e.message}\n#{e.backtrace.join("\n")}" }
59
+ end
60
+ end
61
+
62
+ log.info { "Will refresh enhance index every #{config.refresh_interval} seconds" }
63
+ after(0, &bg_refresh)
64
+ every(config.refresh_interval, &bg_refresh)
65
+ end
66
+
67
+ def refresh(response)
68
+ response.reply(t 'refresh.queued')
69
+
70
+ after(0) do
71
+ begin
72
+ lock_and_refresh_index
73
+ response.reply(success(t 'refresh.success'))
74
+ rescue => e
75
+ response.reply(failed(t 'refresh.failed'))
76
+ log.info { "#{e.message}\n#{e.backtrace.join("\n")}" }
77
+ end
78
+ end
79
+ end
80
+
81
+ def enhance(response)
82
+ level = response.matches[0][1]
83
+ level = level.to_i if level
84
+
85
+ blurry_string = response.matches[0][3]
86
+ return if blurry_string == "stats"
87
+
88
+ key = last_message_key(response)
89
+ level_key = key + ":level"
90
+
91
+ session = Session.new(redis, key, config.blurry_message_ttl)
92
+
93
+ if blurry_string && !blurry_string.empty?
94
+ level = 1 unless level
95
+ else
96
+ blurry_string = session.last_message
97
+ end
98
+
99
+ unless blurry_string
100
+ response.reply(failed(t 'enhance.message_required'))
101
+ return
102
+ end
103
+
104
+ level = session.last_level + 1 unless level
105
+
106
+ if level > max_level
107
+ response.reply(failed(t 'enhance.level_too_high', max_level: max_level))
108
+ return
109
+ elsif level < 1
110
+ response.reply(failed(t 'enhance.level_too_low', max_level: max_level))
111
+ return
112
+ end
113
+
114
+ enhanced_message = session.enhance!(blurry_string, level)
115
+
116
+ if enhanced_message != blurry_string
117
+ response.reply(mono(enhanced_message))
118
+ else
119
+ response.reply(no_change(t 'enhance.nothing_to_enhance'))
120
+ end
121
+ end
122
+
123
+ def stats(response)
124
+ INDEX_MUTEX.synchronize do
125
+ response_msg = t('stats.last_refreshed', last_refreshed: (@@chef_indexer.last_refreshed.to_s || 'never'))
126
+ response_msg += "\n" + t('stats.refresh_frequency', refresh_mins: ('%.2f' % (config.refresh_interval / 60.0)))
127
+ @@enhancers.each do |e|
128
+ response_msg += "\n#{e}"
129
+ end
130
+ response.reply(response_msg)
131
+ end
132
+ end
133
+
134
+ private
135
+ # This mutex must be obtained to refresh the index
136
+ REFRESH_MUTEX = Mutex.new unless defined?(REFRESH_MUTEX)
137
+
138
+ # This mutex must be obtains to update the index with new data, or to use the index to enhance some text
139
+ INDEX_MUTEX = Mutex.new unless defined?(INDEX_MUTEX)
140
+
141
+ def lock_and_refresh_index
142
+ REFRESH_MUTEX.synchronize do
143
+ @@chef_indexer.refresh
144
+ end
145
+ end
146
+
147
+ def last_message_key(response)
148
+ response.message.source.room || response.message.source.user.id
149
+ end
150
+
151
+ def max_level
152
+ @@enhancers.map {|x| x.max_level }.max
153
+ end
154
+
155
+ def adapter
156
+ if Lita.respond_to?(:config)
157
+ Lita.config.robot.adapter
158
+ elsif robot.respond_to?(:config)
159
+ robot.config.robot.adapter
160
+ else
161
+ :unknown
162
+ end
163
+ end
164
+
165
+ # Calls out that this message was successful via adapter specific messaging
166
+ def success(message)
167
+ case adapter
168
+ when :hipchat
169
+ "(successful) #{message}"
170
+ else
171
+ message
172
+ end
173
+ end
174
+
175
+ # Calls out that the action failed via adapter specific messaging
176
+ def failed(message)
177
+ case adapter
178
+ when :hipchat
179
+ "(failed) #{message}"
180
+ else
181
+ message
182
+ end
183
+ end
184
+
185
+ # Calls out that action resulted in no change via adapter specific messaging
186
+ def no_change(message)
187
+ case adapter
188
+ when :hipchat
189
+ "(nothingtodohere) #{message}"
190
+ else
191
+ message
192
+ end
193
+ end
194
+
195
+ # Attempts to render the message using a monospaced font via adapter specific messaging
196
+ def mono(message)
197
+ case adapter
198
+ when :hipchat
199
+ "/quote #{message}"
200
+ else
201
+ message
202
+ end
203
+ end
204
+ end
205
+
206
+
207
+ Lita.register_handler(Enhance)
208
+ end
209
+ end
@@ -0,0 +1,133 @@
1
+ require 'chef'
2
+
3
+ module Lita
4
+ module Handlers
5
+ class Enhance < Handler
6
+ # This class is responsible for indexing Chef servers to extract
7
+ # enhanceable data to populate the enhance indices.
8
+ class ChefIndexer
9
+ attr_reader :last_refreshed
10
+
11
+ attr_reader :redis, :knife_configs
12
+
13
+ def initialize(redis, knife_configs)
14
+ @redis = redis
15
+ @knife_configs = knife_configs
16
+ end
17
+
18
+ def refresh
19
+ log.debug { "Refreshing enhance index..." }
20
+
21
+ self.knife_configs.each do |_, config_path|
22
+ index(config_path, @enhancers)
23
+ end
24
+
25
+ @last_refreshed = Time.now
26
+
27
+ log.debug { "Refreshed enhance index" }
28
+
29
+ # Refreshing the index pulls a lot of large objects into memory,
30
+ # forcing a GC run to ensure that our heap doesn't grow aggressively.
31
+ GC.start
32
+
33
+ nil
34
+ end
35
+
36
+ def index_chef_node(chef_node)
37
+ node = node_from_chef_node(chef_node)
38
+ node.store!(redis)
39
+
40
+ index_hostname(chef_node, node)
41
+ index_instanceid(chef_node, node)
42
+ index_ip(chef_node, node)
43
+ index_mac_address(chef_node, node)
44
+ end
45
+
46
+ def node_from_chef_node(chef_node)
47
+ Node.new.tap do |n|
48
+ n.name = chef_node.name
49
+ n.dc = if chef_node['ec2']
50
+ chef_node['ec2']['placement_availability_zone']
51
+ elsif chef_node['cloud']
52
+ chef_node['cloud']['provider']
53
+ end
54
+ n.environment = chef_node.environment
55
+ n.fqdn = chef_node['fqdn']
56
+ n.last_seen_at = Time.now
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def index(chef_config_path, enhancers)
63
+ log.debug { "Indexing #{chef_config_path}" }
64
+
65
+ Chef::Config.from_file(File.expand_path(chef_config_path))
66
+ query = Chef::Search::Query.new
67
+
68
+ query.search("node", "*:*") do |chef_node|
69
+ index_chef_node(chef_node)
70
+ end
71
+ end
72
+
73
+ def index_hostname(chef_node, node)
74
+ enhancer = HostnameEnhancer.new(redis)
75
+
76
+ enhancer.index(chef_node['fqdn'], node)
77
+
78
+ if chef_node['cloud']
79
+ enhancer.index(chef_node['cloud']['local_hostname'], node)
80
+ enhancer.index(chef_node['cloud']['public_hostname'], node)
81
+ end
82
+
83
+ if chef_node['cloud_v2']
84
+ enhancer.index(chef_node['cloud_v2']['local_hostname'], node)
85
+ enhancer.index(chef_node['cloud_v2']['public_hostname'], node)
86
+ end
87
+ end
88
+
89
+ def index_instanceid(chef_node, node)
90
+ enhancer = InstanceIdEnhancer.new(redis)
91
+
92
+ if chef_node['ec2']
93
+ enhancer.index(chef_node['ec2']['instance_id'], node)
94
+ end
95
+ end
96
+
97
+ def index_ip(chef_node, node)
98
+ enhancer = IpEnhancer.new(redis)
99
+
100
+ enhancer.index(chef_node['ipaddress'], node)
101
+
102
+ if chef_node['cloud']
103
+ enhancer.index(chef_node['cloud']['local_ipv4'], node)
104
+ enhancer.index(chef_node['cloud']['public_ipv4'], node)
105
+ end
106
+
107
+ if chef_node['cloud_v2']
108
+ if chef_node['cloud_v2']['public_ipv4_addrs']
109
+ ips = chef_node['cloud_v2']['public_ipv4_addrs']
110
+ ips.each {|ip| enhancer.index(ip, node) }
111
+ end
112
+ if chef_node['cloud_v2']['local_ipv4_addrs']
113
+ ips = chef_node['cloud_v2']['local_ipv4_addrs']
114
+ ips.each {|ip| enhancer.index(ip, node) }
115
+ end
116
+ end
117
+ end
118
+
119
+ def index_mac_address(chef_node, node)
120
+ enhancer = MacAddressEnhancer.new(redis)
121
+
122
+ if chef_node['macaddress']
123
+ enhancer.index(chef_node['macaddress'], node)
124
+ end
125
+ end
126
+
127
+ def log
128
+ Lita.logger
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end