interferon 0.1.0 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +83 -0
- data/.travis.yml +4 -1
- data/bin/interferon +10 -9
- data/interferon.gemspec +18 -17
- data/lib/interferon/alert.rb +4 -10
- data/lib/interferon/alert_dsl.rb +12 -7
- data/lib/interferon/destinations/datadog.rb +103 -103
- data/lib/interferon/group_sources/filesystem.rb +5 -5
- data/lib/interferon/host_sources/aws_dynamo.rb +17 -19
- data/lib/interferon/host_sources/aws_elasticache.rb +20 -22
- data/lib/interferon/host_sources/aws_rds.rb +33 -33
- data/lib/interferon/host_sources/optica.rb +12 -10
- data/lib/interferon/host_sources/optica_services.rb +17 -15
- data/lib/interferon/host_sources/test_host_source.rb +1 -1
- data/lib/interferon/loaders.rb +4 -5
- data/lib/interferon/logging.rb +2 -3
- data/lib/interferon/version.rb +1 -1
- data/lib/interferon/work_hours_helper.rb +5 -5
- data/lib/interferon.rb +79 -80
- data/script/pre-commit +15 -20
- data/spec/fixtures/loaders/host_sources/test_host_source.rb +1 -1
- data/spec/fixtures/loaders/test_sources/order_test_source.rb +1 -1
- data/spec/fixtures/loaders/test_sources/test_source.rb +1 -1
- data/spec/fixtures/loaders2/test_sources/order_test_source.rb +1 -1
- data/spec/fixtures/loaders2/test_sources/secondary_source.rb +1 -1
- data/spec/fixtures/loaders2/test_sources/test_source.rb +1 -2
- data/spec/helpers/logging_helper.rb +2 -2
- data/spec/helpers/mock_alert.rb +1 -1
- data/spec/helpers/optica_helper.rb +70 -70
- data/spec/lib/interferon/destinations/datadog_spec.rb +58 -59
- data/spec/lib/interferon/group_sources/filesystem_spec.rb +29 -24
- data/spec/lib/interferon/host_sources/optica_services_spec.rb +11 -9
- data/spec/lib/interferon/host_sources/optica_spec.rb +6 -3
- data/spec/lib/interferon/loaders_spec.rb +19 -15
- data/spec/lib/interferon_spec.rb +61 -59
- data/spec/lib/work_hours_helper_spec.rb +15 -15
- data/spec/spec_helper.rb +1 -1
- metadata +61 -65
@@ -3,7 +3,7 @@ include ::Interferon::Logging
|
|
3
3
|
module Interferon::GroupSources
|
4
4
|
class Filesystem
|
5
5
|
def initialize(options)
|
6
|
-
raise ArgumentError,
|
6
|
+
raise ArgumentError, 'missing paths for loading groups from filesystem' \
|
7
7
|
unless options['paths']
|
8
8
|
|
9
9
|
@paths = options['paths']
|
@@ -15,14 +15,14 @@ module Interferon::GroupSources
|
|
15
15
|
|
16
16
|
@paths.each do |path|
|
17
17
|
path = File.expand_path(path)
|
18
|
-
unless Dir.
|
18
|
+
unless Dir.exist?(path)
|
19
19
|
log.warn "no such directory #{path} for reading group files"
|
20
20
|
next
|
21
21
|
end
|
22
22
|
|
23
23
|
Dir.glob(File.join(path, '*.{json,yml,yaml}')).each do |group_file|
|
24
24
|
begin
|
25
|
-
group = YAML
|
25
|
+
group = YAML.parse(File.read(group_file))
|
26
26
|
rescue YAML::SyntaxError => e
|
27
27
|
log.error "syntax error in group file #{group_file}: #{e}"
|
28
28
|
rescue StandardError => e
|
@@ -32,7 +32,7 @@ module Interferon::GroupSources
|
|
32
32
|
if group['people']
|
33
33
|
groups[group['name']] = group['people'] || []
|
34
34
|
elsif group['alias_for']
|
35
|
-
aliases[group['name']] = {:
|
35
|
+
aliases[group['name']] = { group: group['alias_for'], group_file: group_file }
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -48,7 +48,7 @@ module Interferon::GroupSources
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
groups
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -3,46 +3,44 @@ require 'aws'
|
|
3
3
|
module Interferon::HostSources
|
4
4
|
class AwsDynamo
|
5
5
|
def initialize(options)
|
6
|
-
missing = %w
|
6
|
+
missing = %w(access_key_id secret_access_key).reject { |r| options.key?(r) }
|
7
7
|
|
8
|
-
AWS.config(
|
9
|
-
|
10
|
-
:secret_access_key => options['secret_access_key']
|
11
|
-
}) if missing.empty?
|
8
|
+
AWS.config(access_key_id: options['access_key_id'],
|
9
|
+
secret_access_key: options['secret_access_key']) if missing.empty?
|
12
10
|
|
13
11
|
# initialize a list of regions to check
|
14
|
-
if options['regions'] && !options['regions'].empty?
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
@regions = if options['regions'] && !options['regions'].empty?
|
13
|
+
options['regions']
|
14
|
+
else
|
15
|
+
AWS.regions.map(&:name)
|
16
|
+
end
|
19
17
|
end
|
20
18
|
|
21
19
|
def list_hosts
|
22
20
|
hosts = []
|
23
21
|
|
24
22
|
@regions.each do |region|
|
25
|
-
client = AWS::DynamoDB.new(:
|
23
|
+
client = AWS::DynamoDB.new(region: region)
|
26
24
|
|
27
25
|
AWS.memoize do
|
28
26
|
client.tables.each do |table|
|
29
27
|
hosts << {
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
28
|
+
source: 'aws_dynamo',
|
29
|
+
region: region,
|
30
|
+
table_name: table.name,
|
33
31
|
|
34
|
-
:
|
35
|
-
:
|
32
|
+
read_capacity: table.read_capacity_units,
|
33
|
+
write_capacity: table.write_capacity_units,
|
36
34
|
|
37
35
|
# dynamodb does not support tagging
|
38
|
-
:
|
39
|
-
:
|
36
|
+
owners: [],
|
37
|
+
owner_groups: [],
|
40
38
|
}
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
45
|
-
|
43
|
+
hosts
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
@@ -3,19 +3,17 @@ require 'aws'
|
|
3
3
|
module Interferon::HostSources
|
4
4
|
class AwsElasticache
|
5
5
|
def initialize(options)
|
6
|
-
missing = %w
|
6
|
+
missing = %w(access_key_id secret_access_key).reject { |r| options.key?(r) }
|
7
7
|
|
8
|
-
AWS.config(
|
9
|
-
|
10
|
-
:secret_access_key => options['secret_access_key']
|
11
|
-
}) if missing.empty?
|
8
|
+
AWS.config(access_key_id: options['access_key_id'],
|
9
|
+
secret_access_key: options['secret_access_key']) if missing.empty?
|
12
10
|
|
13
11
|
# initialize a list of regions to check
|
14
|
-
if options['regions'] && !options['regions'].empty?
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
@regions = if options['regions'] && !options['regions'].empty?
|
13
|
+
options['regions']
|
14
|
+
else
|
15
|
+
AWS.regions.map(&:name)
|
16
|
+
end
|
19
17
|
end
|
20
18
|
|
21
19
|
def list_hosts
|
@@ -23,12 +21,12 @@ module Interferon::HostSources
|
|
23
21
|
|
24
22
|
@regions.each do |region|
|
25
23
|
clusters = []
|
26
|
-
client = AWS::ElastiCache.new(:
|
24
|
+
client = AWS::ElastiCache.new(region: region).client
|
27
25
|
|
28
26
|
AWS.memoize do
|
29
27
|
# read the list of cache clusters; we have to do our own pagination
|
30
28
|
clusters = []
|
31
|
-
options = {:
|
29
|
+
options = { show_cache_node_info: true }
|
32
30
|
loop do
|
33
31
|
r = client.describe_cache_clusters(options)
|
34
32
|
clusters += r.data[:cache_clusters]
|
@@ -41,26 +39,26 @@ module Interferon::HostSources
|
|
41
39
|
clusters.each do |cluster|
|
42
40
|
cluster[:cache_nodes].each do |node|
|
43
41
|
hosts << {
|
44
|
-
:
|
45
|
-
:
|
42
|
+
source: 'aws_elasticache',
|
43
|
+
region: region,
|
46
44
|
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
50
|
-
:
|
45
|
+
cluster_id: cluster[:cache_cluster_id],
|
46
|
+
cluster_status: cluster[:cache_cluster_status],
|
47
|
+
node_type: cluster[:cache_node_type],
|
48
|
+
peer_nodes: cluster[:num_cache_nodes],
|
51
49
|
|
52
|
-
:
|
50
|
+
node_status: node[:cache_node_status],
|
53
51
|
|
54
52
|
# elasticache does not support tagging
|
55
|
-
:
|
56
|
-
:
|
53
|
+
owners: [],
|
54
|
+
owner_groups: [],
|
57
55
|
}
|
58
56
|
end
|
59
57
|
end
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
63
|
-
|
61
|
+
hosts
|
64
62
|
end
|
65
63
|
end
|
66
64
|
end
|
@@ -3,72 +3,71 @@ require 'aws'
|
|
3
3
|
module Interferon::HostSources
|
4
4
|
class AwsRds
|
5
5
|
def initialize(options)
|
6
|
-
missing = %w
|
6
|
+
missing = %w(access_key_id secret_access_key).reject { |r| options.key?(r) }
|
7
7
|
|
8
|
-
AWS.config(
|
9
|
-
|
10
|
-
:secret_access_key => options['secret_access_key']
|
11
|
-
}) if missing.empty?
|
8
|
+
AWS.config(access_key_id: options['access_key_id'],
|
9
|
+
secret_access_key: options['secret_access_key']) if missing.empty?
|
12
10
|
|
13
11
|
# initialize a list of regions to check
|
14
|
-
if options['regions'] && !options['regions'].empty?
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
@regions = if options['regions'] && !options['regions'].empty?
|
13
|
+
options['regions']
|
14
|
+
else
|
15
|
+
AWS.regions.map(&:name)
|
16
|
+
end
|
19
17
|
end
|
20
18
|
|
21
19
|
def list_hosts
|
22
20
|
hosts = []
|
23
21
|
|
24
22
|
@regions.each do |region|
|
25
|
-
rds = AWS::RDS.new(:
|
23
|
+
rds = AWS::RDS.new(region: region)
|
26
24
|
|
27
25
|
AWS.memoize do
|
28
26
|
rds.instances.each do |instance|
|
29
27
|
# get the tags for the instance
|
30
28
|
arn = arn(region, instance.id)
|
31
|
-
tag_list = rds.client.list_tags_for_resource(:
|
32
|
-
tags = Hash[
|
29
|
+
tag_list = rds.client.list_tags_for_resource(resource_name: arn)[:tag_list]
|
30
|
+
tags = Hash[tag_list.map { |h| [h[:key], h[:value]] }]
|
33
31
|
|
34
32
|
tags['owners'] ||= ''
|
35
33
|
tags['owner_groups'] ||= ''
|
36
34
|
|
37
35
|
# build the host data for this instance
|
38
36
|
hosts << {
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
37
|
+
source: 'aws_rds',
|
38
|
+
region: region,
|
39
|
+
instance_id: instance.id,
|
40
|
+
db_name: instance.db_name,
|
41
|
+
engine: instance.engine,
|
42
|
+
engine_version: instance.engine_version,
|
45
43
|
|
46
44
|
# metrics
|
47
|
-
:
|
48
|
-
:
|
45
|
+
allocated_storage: instance.allocated_storage,
|
46
|
+
iops: instance.iops,
|
49
47
|
|
50
48
|
# replication info
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
49
|
+
is_replica: !instance.read_replica_source_db_instance_identifier.nil?,
|
50
|
+
replica_source_name: instance.read_replica_source_db_instance_identifier,
|
51
|
+
replica_names: instance.read_replica_db_instance_identifiers.join(','),
|
52
|
+
replicas: instance.read_replica_db_instance_identifiers.count,
|
55
53
|
|
56
|
-
:
|
57
|
-
:
|
54
|
+
owners: tags['owners'].split(','),
|
55
|
+
owner_groups: tags['owner_groups'].split(','),
|
58
56
|
|
59
|
-
:
|
60
|
-
:
|
57
|
+
db_env: tags['db_env'],
|
58
|
+
db_role: tags['db_role'],
|
61
59
|
}
|
62
60
|
end
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
66
|
-
|
64
|
+
hosts
|
67
65
|
end
|
68
66
|
|
69
67
|
private
|
68
|
+
|
70
69
|
def arn(region, instance_id)
|
71
|
-
|
70
|
+
"arn:aws:rds:#{region}:#{account_number}:db:#{instance_id}"
|
72
71
|
end
|
73
72
|
|
74
73
|
# unfortunately, this appears to be the only way to get your account number
|
@@ -77,8 +76,9 @@ module Interferon::HostSources
|
|
77
76
|
|
78
77
|
begin
|
79
78
|
my_arn = AWS::IAM.new(
|
80
|
-
:
|
81
|
-
:
|
79
|
+
access_key_id: @access_key_id,
|
80
|
+
secret_access_key: @secret_access_key
|
81
|
+
).client.get_user[:user][:arn]
|
82
82
|
rescue AWS::IAM::Errors::AccessDenied => e
|
83
83
|
my_arn = e.message.split[1]
|
84
84
|
end
|
@@ -6,7 +6,7 @@ module Interferon::HostSources
|
|
6
6
|
include ::Interferon::Logging
|
7
7
|
|
8
8
|
def initialize(options)
|
9
|
-
raise ArgumentError,
|
9
|
+
raise ArgumentError, 'missing host for optica source' \
|
10
10
|
unless options['host']
|
11
11
|
|
12
12
|
@host = options['host']
|
@@ -14,15 +14,17 @@ module Interferon::HostSources
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def list_hosts
|
17
|
-
|
18
|
-
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
17
|
+
optica_data['nodes'].map do |_ip, host|
|
18
|
+
{
|
19
|
+
source: 'optica',
|
20
|
+
hostname: host['hostname'],
|
21
|
+
role: host['role'],
|
22
|
+
environment: host['environment'],
|
22
23
|
|
23
|
-
:
|
24
|
-
:
|
25
|
-
}
|
24
|
+
owners: host['ownership'] && host['ownership']['people'] || [],
|
25
|
+
owner_groups: host['ownership'] && host['ownership']['groups'] || [],
|
26
|
+
}
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def optica_data
|
@@ -32,7 +34,7 @@ module Interferon::HostSources
|
|
32
34
|
con.open_timeout = 60
|
33
35
|
|
34
36
|
response = con.get('/')
|
35
|
-
JSON
|
37
|
+
JSON.parse(response.body)
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
@@ -7,7 +7,7 @@ module Interferon::HostSources
|
|
7
7
|
include ::Interferon::Logging
|
8
8
|
|
9
9
|
def initialize(options)
|
10
|
-
raise ArgumentError,
|
10
|
+
raise ArgumentError, 'missing host for optica source' \
|
11
11
|
unless options['host']
|
12
12
|
|
13
13
|
@host = options['host']
|
@@ -22,24 +22,26 @@ module Interferon::HostSources
|
|
22
22
|
con.open_timeout = 60
|
23
23
|
|
24
24
|
response = con.get('/')
|
25
|
-
JSON
|
25
|
+
JSON.parse(response.body)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
def list_hosts
|
30
|
-
services = Hash.new
|
31
|
-
|
32
|
-
:
|
30
|
+
services = Hash.new do |h, service|
|
31
|
+
h[service] = {
|
32
|
+
source: 'optica_services',
|
33
|
+
service: service,
|
33
34
|
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
37
|
-
:
|
38
|
-
:
|
39
|
-
}
|
35
|
+
owners: Set.new,
|
36
|
+
owner_groups: Set.new,
|
37
|
+
consumer_roles: Set.new,
|
38
|
+
consumer_machine_count: 0,
|
39
|
+
provider_machine_count: 0,
|
40
|
+
}
|
41
|
+
end
|
40
42
|
|
41
|
-
optica_data['nodes'].each do |
|
42
|
-
next unless @envs.empty?
|
43
|
+
optica_data['nodes'].each do |_ip, host|
|
44
|
+
next unless @envs.empty? || @envs.include?(host['environment'])
|
43
45
|
|
44
46
|
# make it easier by initializing possibly-missing data to sane defaults
|
45
47
|
host['ownership'] ||= {}
|
@@ -62,13 +64,13 @@ module Interferon::HostSources
|
|
62
64
|
end
|
63
65
|
|
64
66
|
# convert all sets to arrays
|
65
|
-
services.each do |k,v|
|
67
|
+
services.each do |k, v|
|
66
68
|
services[k][:owners] = v[:owners].to_a
|
67
69
|
services[k][:owner_groups] = v[:owner_groups].to_a
|
68
70
|
services[k][:consumer_roles] = v[:consumer_roles].to_a
|
69
71
|
end
|
70
72
|
|
71
|
-
|
73
|
+
services.values
|
72
74
|
end
|
73
75
|
end
|
74
76
|
end
|
data/lib/interferon/loaders.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
|
2
2
|
module Interferon
|
3
|
-
|
4
3
|
# lets create namespaces for things we'll be loading
|
5
4
|
module Destinations; end
|
6
5
|
module HostSources; end
|
@@ -40,7 +39,7 @@ module Interferon
|
|
40
39
|
next
|
41
40
|
end
|
42
41
|
|
43
|
-
|
42
|
+
unless enabled
|
44
43
|
log.info "skipping #{@loader_for} #{type} because it's not enabled"
|
45
44
|
next
|
46
45
|
end
|
@@ -86,14 +85,14 @@ module Interferon
|
|
86
85
|
klass = @module.const_get(class_name)
|
87
86
|
rescue LoadError => e
|
88
87
|
raise ArgumentError,\
|
89
|
-
|
88
|
+
"Loading Error; interferon does not define #{@loader_for} #{type}: #{e}"
|
90
89
|
rescue NameError => e
|
91
90
|
raise ArgumentError,\
|
92
|
-
|
91
|
+
"Name Error; class #{class_name} is not defined in #{relative_filename}: #{e}"
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
96
|
-
|
95
|
+
klass
|
97
96
|
end
|
98
97
|
end
|
99
98
|
|
data/lib/interferon/logging.rb
CHANGED
@@ -3,12 +3,11 @@ require 'statsd'
|
|
3
3
|
|
4
4
|
module Interferon
|
5
5
|
module Logging
|
6
|
-
|
7
6
|
def statsd
|
8
7
|
@statsd ||= Statsd.new(
|
9
8
|
Statsd::DEFAULT_HOST,
|
10
9
|
Statsd::DEFAULT_PORT,
|
11
|
-
:
|
10
|
+
namespace: 'alerts_framework'
|
12
11
|
)
|
13
12
|
end
|
14
13
|
|
@@ -20,7 +19,7 @@ module Interferon
|
|
20
19
|
logger = Logger.new(STDERR)
|
21
20
|
logger.level = Logger::INFO unless ENV['DEBUG']
|
22
21
|
logger.progname = classname
|
23
|
-
|
22
|
+
logger
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
data/lib/interferon/version.rb
CHANGED
@@ -4,18 +4,18 @@ module Interferon
|
|
4
4
|
class WorkHoursHelper
|
5
5
|
DEFAULT_WORK_DAYS = (1..5)
|
6
6
|
DEFAULT_WORK_HOURS = (9..16)
|
7
|
-
DEFAULT_WORK_TIMEZONE = 'America/Los_Angeles'
|
7
|
+
DEFAULT_WORK_TIMEZONE = 'America/Los_Angeles'.freeze
|
8
8
|
DEFAULT_WORK_ARGS = {
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
9
|
+
hours: DEFAULT_WORK_HOURS,
|
10
|
+
days: DEFAULT_WORK_DAYS,
|
11
|
+
timezone: DEFAULT_WORK_TIMEZONE,
|
12
12
|
}.freeze
|
13
13
|
|
14
14
|
def self.is_work_hour?(time, args = {})
|
15
15
|
args = args.merge(DEFAULT_WORK_ARGS)
|
16
16
|
tz = TZInfo::Timezone.get args[:timezone]
|
17
17
|
time_in_tz = time + tz.period_for_utc(time).utc_offset
|
18
|
-
|
18
|
+
args[:days].include?(time_in_tz.wday) && args[:hours].include?(time_in_tz.hour)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|