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