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