lita-enhance 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +8 -0
- data/Gemfile +5 -0
- data/LICENSE +19 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/lib/lita-enhance.rb +7 -0
- data/lib/lita/handlers/enhance.rb +209 -0
- data/lib/lita/handlers/enhance/chef_indexer.rb +133 -0
- data/lib/lita/handlers/enhance/enhancer.rb +58 -0
- data/lib/lita/handlers/enhance/enhancers/hostname_enhancer.rb +72 -0
- data/lib/lita/handlers/enhance/enhancers/instance_id_enhancer.rb +40 -0
- data/lib/lita/handlers/enhance/enhancers/ip_enhancer.rb +40 -0
- data/lib/lita/handlers/enhance/enhancers/mac_address_enhancer.rb +42 -0
- data/lib/lita/handlers/enhance/node.rb +57 -0
- data/lib/lita/handlers/enhance/node_index.rb +41 -0
- data/lib/lita/handlers/enhance/session.rb +77 -0
- data/lita-enhance.gemspec +23 -0
- data/locales/en.yml +16 -0
- data/spec/data/box01.json +214 -0
- data/spec/data/box02.json +163 -0
- data/spec/data/box03.json +123 -0
- data/spec/data/stg-web01.json +89 -0
- data/spec/data/web01.json +89 -0
- data/spec/lita/handlers/enhance/chef_indexer_spec.rb +18 -0
- data/spec/lita/handlers/enhance/enhancer_example.rb +16 -0
- data/spec/lita/handlers/enhance/enhancers/hostname_enhancer_spec.rb +58 -0
- data/spec/lita/handlers/enhance/enhancers/instance_id_enhancer_spec.rb +31 -0
- data/spec/lita/handlers/enhance/enhancers/ip_enhancer_spec.rb +48 -0
- data/spec/lita/handlers/enhance/enhancers/mac_address_enhancer_spec.rb +32 -0
- data/spec/lita/handlers/enhance/node_index_spec.rb +33 -0
- data/spec/lita/handlers/enhance/node_spec.rb +51 -0
- data/spec/lita/handlers/enhance/session_spec.rb +48 -0
- data/spec/lita/handlers/enhance_spec.rb +136 -0
- data/spec/spec_helper.rb +64 -0
- metadata +168 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'weakref'
|
2
|
+
|
3
|
+
module Lita
|
4
|
+
module Handlers
|
5
|
+
class Enhance < Handler
|
6
|
+
class Enhancer
|
7
|
+
@@subclasses = []
|
8
|
+
|
9
|
+
@@current = %w(* *)
|
10
|
+
@@old = %w(¿ ?)
|
11
|
+
|
12
|
+
def self.all
|
13
|
+
@@subclasses.select! {|x| x.weakref_alive? }
|
14
|
+
@@subclasses
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.inherited(subclass)
|
18
|
+
@@subclasses << WeakRef.new(subclass)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :redis
|
22
|
+
|
23
|
+
def initialize(redis)
|
24
|
+
@redis = redis
|
25
|
+
end
|
26
|
+
|
27
|
+
def render(node, level)
|
28
|
+
"#{start_mark(node)}#{node.render(level)}#{end_mark(node)}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def max_level
|
32
|
+
5
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def start_mark(node)
|
37
|
+
node.old? ? @@old.first : @@current.first
|
38
|
+
end
|
39
|
+
|
40
|
+
def end_mark(node)
|
41
|
+
node.old? ? @@old.last : @@current.last
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Substitution < Struct.new(:range, :new_text)
|
46
|
+
def overlap?(other)
|
47
|
+
range.cover?(other.range.begin) || other.range.cover?(range.end)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
require 'lita/handlers/enhance/enhancers/instance_id_enhancer'
|
52
|
+
require 'lita/handlers/enhance/enhancers/ip_enhancer'
|
53
|
+
require 'lita/handlers/enhance/enhancers/hostname_enhancer'
|
54
|
+
require 'lita/handlers/enhance/enhancers/mac_address_enhancer'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'lita/handlers/enhance/node_index'
|
2
|
+
require 'lita/handlers/enhance/enhancer'
|
3
|
+
|
4
|
+
module Lita
|
5
|
+
module Handlers
|
6
|
+
class Enhance
|
7
|
+
class HostnameEnhancer < Enhancer
|
8
|
+
HOSTNAME_REGEX = /\b(?:(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))\b/
|
9
|
+
|
10
|
+
def initialize(redis)
|
11
|
+
super
|
12
|
+
@nodes_by_hostname = NodeIndex.new(redis, 'nodes_by_hostname')
|
13
|
+
@nodes_by_short_hostname = NodeIndex.new(redis, 'nodes_by_short_hostname')
|
14
|
+
end
|
15
|
+
|
16
|
+
def index(hostname, node)
|
17
|
+
map_hostname_to_node(hostname, node)
|
18
|
+
end
|
19
|
+
|
20
|
+
def enhance!(string, level)
|
21
|
+
substitutions = []
|
22
|
+
string.scan(HOSTNAME_REGEX) do
|
23
|
+
match = Regexp.last_match
|
24
|
+
hostname = match.to_s
|
25
|
+
range = (match.begin(0)...match.end(0))
|
26
|
+
|
27
|
+
node = @nodes_by_hostname[hostname]
|
28
|
+
if node
|
29
|
+
new_text = render(node, level)
|
30
|
+
substitutions << Substitution.new(range, new_text)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
string.scan(short_hostname_regex) do
|
34
|
+
match = Regexp.last_match
|
35
|
+
hostname = match.to_s
|
36
|
+
range = (match.begin(0)...match.end(0))
|
37
|
+
|
38
|
+
node = @nodes_by_short_hostname[hostname]
|
39
|
+
if node
|
40
|
+
new_text = render(node, level)
|
41
|
+
sub = Substitution.new(range, new_text)
|
42
|
+
unless substitutions.any? {|s| s.overlap?(sub) }
|
43
|
+
substitutions << Substitution.new(range, new_text)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
substitutions
|
49
|
+
end
|
50
|
+
|
51
|
+
def short_hostname_regex
|
52
|
+
@short_hostname_regex ||= /\b(?<!\*)#{Regexp.union(@nodes_by_short_hostname.keys)}\b(?<!\*)/
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
"#{self.class.name}: #{@nodes_by_short_hostname.size} short hostnames, #{@nodes_by_hostname.size} long hostnames indexed"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def map_hostname_to_node(hostname, node)
|
62
|
+
return if hostname.nil?
|
63
|
+
|
64
|
+
short_hostname = hostname.split('.')[0]
|
65
|
+
|
66
|
+
@nodes_by_hostname[hostname] = node
|
67
|
+
@nodes_by_short_hostname[short_hostname] = node
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'lita/handlers/enhance/node_index'
|
2
|
+
|
3
|
+
module Lita
|
4
|
+
module Handlers
|
5
|
+
class Enhance
|
6
|
+
class InstanceIdEnhancer < Enhancer
|
7
|
+
INSTANCE_ID_REGEX = /i-[0-9a-f]{8}/
|
8
|
+
|
9
|
+
def initialize(redis)
|
10
|
+
super
|
11
|
+
@nodes_by_instance_id = NodeIndex.new(redis, 'nodes_by_instance_id')
|
12
|
+
end
|
13
|
+
|
14
|
+
def index(instance_id, node)
|
15
|
+
@nodes_by_instance_id[instance_id] = node
|
16
|
+
end
|
17
|
+
|
18
|
+
def enhance!(string, level)
|
19
|
+
substitutions = []
|
20
|
+
string.scan(INSTANCE_ID_REGEX) do
|
21
|
+
match = Regexp.last_match
|
22
|
+
instance_id = match.to_s
|
23
|
+
range = (match.begin(0)...match.end(0))
|
24
|
+
|
25
|
+
node = @nodes_by_instance_id[instance_id]
|
26
|
+
if node
|
27
|
+
new_text = render(node, level)
|
28
|
+
substitutions << Substitution.new(range, new_text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
substitutions
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"#{self.class.name}: #{@nodes_by_instance_id.size} instance IDs indexed"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'lita/handlers/enhance/node_index'
|
2
|
+
|
3
|
+
module Lita
|
4
|
+
module Handlers
|
5
|
+
class Enhance
|
6
|
+
class IpEnhancer < Enhancer
|
7
|
+
IP_REGEX = /(?:[0-9]{1,3}\.){3}[0-9]{1,3}/
|
8
|
+
|
9
|
+
def initialize(redis)
|
10
|
+
super
|
11
|
+
@nodes_by_ip = NodeIndex.new(redis, 'nodes_by_ip')
|
12
|
+
end
|
13
|
+
|
14
|
+
def index(ip, node)
|
15
|
+
@nodes_by_ip[ip] = node
|
16
|
+
end
|
17
|
+
|
18
|
+
def enhance!(string, level)
|
19
|
+
substitutions = []
|
20
|
+
string.scan(IP_REGEX) do
|
21
|
+
match = Regexp.last_match
|
22
|
+
ip = match.to_s
|
23
|
+
range = (match.begin(0)...match.end(0))
|
24
|
+
|
25
|
+
node = @nodes_by_ip[ip]
|
26
|
+
if node
|
27
|
+
new_text = render(node, level)
|
28
|
+
substitutions << Substitution.new(range, new_text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
substitutions
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"#{self.class.name}: #{@nodes_by_ip.size} IPs indexed"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'lita/handlers/enhance/node_index'
|
2
|
+
|
3
|
+
module Lita
|
4
|
+
module Handlers
|
5
|
+
class Enhance
|
6
|
+
class MacAddressEnhancer < Enhancer
|
7
|
+
# TODO: enhance unknown MAC address with OUI names http://standards.ieee.org/develop/regauth/oui/oui.txt
|
8
|
+
|
9
|
+
MAC_ADDRESS_REGEX = /([0-9a-f]{2}:){5}[0-9a-f]{2}/i
|
10
|
+
|
11
|
+
def initialize(redis)
|
12
|
+
super
|
13
|
+
@nodes_by_mac_address = NodeIndex.new(redis, 'nodes_by_mac_address')
|
14
|
+
end
|
15
|
+
|
16
|
+
def index(mac_address, node)
|
17
|
+
@nodes_by_mac_address[mac_address.downcase] = node
|
18
|
+
end
|
19
|
+
|
20
|
+
def enhance!(string, level)
|
21
|
+
substitutions = []
|
22
|
+
string.scan(MAC_ADDRESS_REGEX) do
|
23
|
+
match = Regexp.last_match
|
24
|
+
mac_address = match.to_s
|
25
|
+
range = (match.begin(0)...match.end(0))
|
26
|
+
|
27
|
+
node = @nodes_by_mac_address[mac_address.downcase]
|
28
|
+
if node
|
29
|
+
new_text = render(node, level)
|
30
|
+
substitutions << Substitution.new(range, new_text)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
substitutions
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"#{self.class.name}: #{@nodes_by_mac_address.size} MAC addresses indexed"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
class Enhance < Handler
|
4
|
+
class Node
|
5
|
+
attr_accessor :name, :dc, :environment, :fqdn, :last_seen_at
|
6
|
+
|
7
|
+
# Creates a new Node instance and loads its data from Redis
|
8
|
+
def self.load(redis, name)
|
9
|
+
node = nil
|
10
|
+
|
11
|
+
node_data_json = redis.hget('nodes', name)
|
12
|
+
if node_data_json
|
13
|
+
node_data = JSON.parse(node_data_json)
|
14
|
+
node = self.from_json(node_data)
|
15
|
+
end
|
16
|
+
|
17
|
+
node
|
18
|
+
end
|
19
|
+
|
20
|
+
# Stores this node in Redis for later retrieval
|
21
|
+
def store!(redis)
|
22
|
+
node_data_json = JSON.generate(self.as_json)
|
23
|
+
redis.hset('nodes', self.name, node_data_json)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_json(json)
|
27
|
+
self.new.tap do |node|
|
28
|
+
%w(name dc environment fqdn).each do |field|
|
29
|
+
node.send("#{field}=", json[field])
|
30
|
+
end
|
31
|
+
node.last_seen_at = Time.parse(json['last_seen_at']) if json['last_seen_at']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def as_json
|
36
|
+
{name: name, dc: dc, environment: environment, fqdn: fqdn, last_seen_at: last_seen_at}
|
37
|
+
end
|
38
|
+
|
39
|
+
def render(level)
|
40
|
+
case level
|
41
|
+
when 1 then name
|
42
|
+
when 2 then "#{name} (#{dc})"
|
43
|
+
when 3 then "#{name} (#{dc}, #{environment})"
|
44
|
+
when 4 then "#{name} (#{dc}, #{environment}, last seen #{last_seen_at})"
|
45
|
+
when 5 then "#{fqdn} (#{dc}, #{environment}, last seen #{last_seen_at})"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# True if this node appears to be gone away because we haven't seen it
|
50
|
+
# for a while.
|
51
|
+
def old?
|
52
|
+
last_seen_at < (Time.now - 6 * 60 * 60)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
class Enhance < Handler
|
4
|
+
# Instances of this class represent indexes of nodes based on identifying
|
5
|
+
# facets of that node. Nodes are added to the index where the key is the
|
6
|
+
# term that we're indexing on, and the value is the node that matches
|
7
|
+
# that term.
|
8
|
+
#
|
9
|
+
# Nodes can then be later found in the index.
|
10
|
+
class NodeIndex
|
11
|
+
attr_reader :redis, :index_name
|
12
|
+
|
13
|
+
def initialize(redis, index_name)
|
14
|
+
@redis = redis
|
15
|
+
@index_name = index_name
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds a node to the index
|
19
|
+
def []=(key, node)
|
20
|
+
redis.hset(index_name, key, node.name)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Finds a node in the index. A Node object is return if found, otherwise nil is returned.
|
24
|
+
def [](key)
|
25
|
+
node_name = redis.hget(index_name, key)
|
26
|
+
Node.load(redis, node_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the number of keys that are stored in this index.
|
30
|
+
def size
|
31
|
+
redis.hlen(index_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns all the keys that defined in this index.
|
35
|
+
def keys
|
36
|
+
redis.hkeys(index_name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
class Enhance < Handler
|
4
|
+
# Represents a session of message enhancing. This class is the entry
|
5
|
+
# point into logic for enhancing a string. Since state is stored in
|
6
|
+
# Redis, a new session can be created for each interaction.
|
7
|
+
class Session
|
8
|
+
# A redis namespace under which we will store our data
|
9
|
+
attr_reader :redis
|
10
|
+
|
11
|
+
# The key by which the previous enhanced messages should be remembered.
|
12
|
+
attr_reader :last_message_key
|
13
|
+
|
14
|
+
# How long to remember previous messages (in seconds)
|
15
|
+
attr_reader :last_message_ttl
|
16
|
+
|
17
|
+
def initialize(redis, last_message_key, last_message_ttl)
|
18
|
+
@redis = redis
|
19
|
+
@last_message_key = last_message_key
|
20
|
+
@last_message_ttl = last_message_ttl
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the last level of enhancement that was performed in this
|
24
|
+
# session. Using this value it is possible to implicitly raise the
|
25
|
+
# level.
|
26
|
+
def last_level
|
27
|
+
last_level_raw = redis.get(last_level_key)
|
28
|
+
last_level_raw && last_level_raw.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the last user supplied message that was enhanced for this
|
32
|
+
# session. Using this value, it is possible to re-enhance this string.
|
33
|
+
def last_message
|
34
|
+
redis.get(last_message_key)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Enhances message at the supplied level.
|
38
|
+
def enhance!(message, level)
|
39
|
+
log.debug { "Enhancing (level: #{level}):\n#{message}" }
|
40
|
+
|
41
|
+
redis.setex(last_message_key, last_message_ttl, message)
|
42
|
+
redis.setex(last_level_key, last_message_ttl, level)
|
43
|
+
|
44
|
+
@enhancers = Enhancer.all.map do |enhancer_klass|
|
45
|
+
enhancer_klass.new(redis)
|
46
|
+
end
|
47
|
+
|
48
|
+
substitutions = @enhancers.flat_map do |e|
|
49
|
+
e.enhance!(message, level)
|
50
|
+
end
|
51
|
+
|
52
|
+
enhanced_message = message.dup
|
53
|
+
|
54
|
+
substitutions.sort! {|a,b| a.range.begin <=> b.range.begin }
|
55
|
+
|
56
|
+
current_offset = 0
|
57
|
+
substitutions.each do |sub|
|
58
|
+
enhanced_message[sub.range.begin + current_offset, sub.range.size] = sub.new_text
|
59
|
+
|
60
|
+
current_offset += sub.new_text.length - sub.range.size
|
61
|
+
end
|
62
|
+
|
63
|
+
enhanced_message
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def log
|
68
|
+
Lita.logger
|
69
|
+
end
|
70
|
+
|
71
|
+
def last_level_key
|
72
|
+
last_message_key + ":level"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "lita-enhance"
|
3
|
+
spec.version = "0.9.0"
|
4
|
+
spec.authors = ["Doug Barth"]
|
5
|
+
spec.email = ["doug@pagerduty.com"]
|
6
|
+
spec.description = %q{A Lita handler that enhances text by replacing opaque identifiers with Chef node names}
|
7
|
+
spec.summary = %q{A Lita handler that enhances text by replacing opaque identifiers with Chef node names}
|
8
|
+
spec.homepage = "https://github.com/PagerDuty/lita-enhance"
|
9
|
+
spec.license = "MIT"
|
10
|
+
spec.metadata = { "lita_plugin_type" => "handler" }
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split($/)
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_runtime_dependency "lita", ">= 3.1"
|
18
|
+
spec.add_runtime_dependency "chef", ">= 11.0"
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rspec", ">= 3.0.0.beta2"
|
23
|
+
end
|