lita-enhance 0.9.0
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.
- 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
|