sport_ngin_aws_auditor 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: beef821f62dfeb8b9235d25a120b2faf8ddec09a
4
- data.tar.gz: f0c504bfd3cbd9fbdd8f305c733f8eba50ce7ac9
3
+ metadata.gz: 17745ae35107bce99406b1270ca64d8f9f81a403
4
+ data.tar.gz: 06ceb2d5d68b6f716e0dfdfd0e8030d7aaabc855
5
5
  SHA512:
6
- metadata.gz: 62da765c56b2599a4817d1b585bd3844bec7397e4a1c2cd93ab7e1d409c0e255fa5123b2fecf56e0463cce87e497436620764a34c4caba67accba942f4317d5c
7
- data.tar.gz: 71545feabd3057e6062725598b98b565cc31a99ed1c2847edc9867ae2ae721b8e78826395ad181657e4faa60277cbb12b7426eb7f47a74389db50484ddbc267a
6
+ metadata.gz: eb20a8082e473c2255f3d3644c656a0922220ed4ce0b554f910b0f027ee0a680da08cff6fe98c42e660789d19e427e7041c57b5f7512202642aabefc2aaf3a0a
7
+ data.tar.gz: e94b7838fbcb173e27d36a43602105de44064f89ea7fb388361568f54919be2c8dda0541ba468b6eb725120f147bcb06d3b7ac427c383ebb35f50283073761bf
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,8 @@
1
+ #### v3.10.0
2
+ * Handling region-based RIs
3
+
4
+ > Emma Sax: Andy Fleener, Luke Ludwig, Tim Sandquist, Unknown User: https://github.com/sportngin/sport_ngin_aws_auditor/pull/21
5
+
1
6
  #### v3.9.0
2
7
  * Add the ability to pass config data in as a flag
3
8
 
@@ -3,7 +3,7 @@ require_relative './instance_helper'
3
3
  module SportNginAwsAuditor
4
4
  class AuditData
5
5
 
6
- attr_accessor :data, :retired_tags, :retired_ris, :selected_audit_type, :klass, :tag_name
6
+ attr_accessor :data, :retired_tags, :retired_ris, :selected_audit_type, :klass, :tag_name, :region
7
7
  def initialize(instances, reserved, class_type, tag_name)
8
8
  self.selected_audit_type = (!instances && !reserved) ? "all" : (instances ? "instances" : "reserved")
9
9
  self.klass = SportNginAwsAuditor.const_get(class_type)
@@ -25,15 +25,17 @@ module SportNginAwsAuditor
25
25
  def gather_data
26
26
  if instances?
27
27
  instance_hash, retired_tags = gather_instances_data
28
+ retired_ris = nil
28
29
  elsif reserved?
29
30
  instance_hash = self.klass.instance_count_hash(self.klass.get_reserved_instances)
31
+ retired_tags, retired_ris = nil
30
32
  elsif all?
31
33
  instance_hash, retired_tags, retired_ris = gather_all_data
32
34
  end
33
35
 
34
36
  compared_array = []
35
37
  instance_hash.each do |key, value|
36
- compared_array.push(Instance.new(key, value))
38
+ compared_array.push(Instance.new(key, value, self.region))
37
39
  end
38
40
 
39
41
  self.data = compared_array
@@ -43,22 +45,34 @@ module SportNginAwsAuditor
43
45
 
44
46
  def gather_instances_data
45
47
  instances = self.klass.get_instances(tag_name)
48
+ gather_region(instances)
46
49
  retired_tags = self.klass.get_retired_tags(instances)
47
50
  instances_with_tag = self.klass.filter_instances_with_tags(instances)
48
- instances_without_tag = self.klass.filter_instance_without_tags(instances)
51
+ instances_without_tag = self.klass.filter_instances_without_tags(instances)
49
52
  instance_hash = self.klass.instance_count_hash(instances_without_tag)
50
- self.klass.add_instances_with_tag_to_hash(instances_with_tag, instance_hash)
53
+ self.klass.apply_tagged_instances(instances_with_tag, instance_hash)
51
54
 
52
55
  return instance_hash, retired_tags
53
56
  end
54
57
 
55
58
  def gather_all_data
56
59
  instances = self.klass.get_instances(tag_name)
60
+ gather_region(instances)
57
61
  retired_tags = self.klass.get_retired_tags(instances)
58
62
  instance_hash = self.klass.compare(instances)
59
63
  retired_ris = self.klass.get_recent_retired_reserved_instances
60
64
 
61
65
  return instance_hash, retired_tags, retired_ris
62
66
  end
67
+
68
+ def gather_region(instances)
69
+ if self.klass == SportNginAwsAuditor::EC2Instance
70
+ # if instances.first.availability_zone = 'us-east-1a'...
71
+ match = instances.first.availability_zone.match(/(\w{2}-\w{4,})/)
72
+
73
+ # then region = 'us-east'
74
+ self.region = match[0] unless match.nil?
75
+ end
76
+ end
63
77
  end
64
78
  end
@@ -35,11 +35,12 @@ module SportNginAwsAuditor
35
35
  end
36
36
  end
37
37
 
38
- attr_accessor :id, :name, :instance_type, :engine, :count, :tag_value, :tag_reason, :expiration_date
38
+ attr_accessor :id, :name, :instance_type, :scope, :engine, :count, :tag_value, :tag_reason, :expiration_date
39
39
  def initialize(cache_instance, account_id=nil, tag_name=nil, cache=nil)
40
40
  if cache_instance.class.to_s == "Aws::ElastiCache::Types::ReservedCacheNode"
41
41
  self.id = cache_instance.reserved_cache_node_id
42
42
  self.name = cache_instance.reserved_cache_node_id
43
+ self.scope = nil
43
44
  self.instance_type = cache_instance.cache_node_type
44
45
  self.engine = cache_instance.product_description
45
46
  self.count = cache_instance.cache_node_count
@@ -47,6 +48,7 @@ module SportNginAwsAuditor
47
48
  elsif cache_instance.class.to_s == "Aws::ElastiCache::Types::CacheCluster"
48
49
  self.id = cache_instance.cache_cluster_id
49
50
  self.name = cache_instance.cache_cluster_id
51
+ self.scope = nil
50
52
  self.instance_type = cache_instance.cache_node_type
51
53
  self.engine = cache_instance.engine
52
54
  self.count = cache_instance.num_cache_nodes
@@ -10,6 +10,7 @@ command 'audit' do |c|
10
10
  c.flag [:h, :config_json], :default_value => nil, :desc => "Print the audit according to this config json object instead of to config file"
11
11
  c.switch [:n, :no_tag], :desc => "Ignore all tags during audit"
12
12
  c.switch [:s, :slack], :desc => "Will print condensed version of audit to a Slack channel"
13
+ c.switch [:z, :zone_output], :desc => "Will print the Missing RIs and Tagged instances with zones"
13
14
  c.action do |global_options, options, args|
14
15
  require_relative '../scripts/audit'
15
16
  raise ArgumentError, 'You must specify an AWS account' unless args.first
@@ -60,13 +60,14 @@ module SportNginAwsAuditor
60
60
  private :get_more_info
61
61
  end
62
62
 
63
- attr_accessor :id, :name, :platform, :availability_zone, :instance_type, :count, :stack_name, :tag_value, :tag_reason, :expiration_date
63
+ attr_accessor :id, :name, :platform, :availability_zone, :scope, :instance_type, :count, :stack_name, :tag_value, :tag_reason, :expiration_date
64
64
  def initialize(ec2_instance, tag_name, count=1)
65
65
  if ec2_instance.class.to_s == "Aws::EC2::Types::ReservedInstances"
66
66
  self.id = ec2_instance.reserved_instances_id
67
67
  self.name = nil
68
68
  self.platform = platform_helper(ec2_instance.product_description)
69
- self.availability_zone = ec2_instance.availability_zone
69
+ self.scope = ec2_instance.scope
70
+ self.availability_zone = self.scope == 'Region' ? nil : ec2_instance.availability_zone
70
71
  self.instance_type = ec2_instance.instance_type
71
72
  self.count = count
72
73
  self.stack_name = nil
@@ -75,6 +76,7 @@ module SportNginAwsAuditor
75
76
  self.id = ec2_instance.instance_id
76
77
  self.name = ec2_instance.key_name
77
78
  self.platform = platform_helper((ec2_instance.platform || ''), ec2_instance.vpc_id)
79
+ self.scope = nil
78
80
  self.availability_zone = ec2_instance.placement.availability_zone
79
81
  self.instance_type = ec2_instance.instance_type
80
82
  self.count = count
@@ -4,29 +4,49 @@ module SportNginAwsAuditor
4
4
  class Instance
5
5
  extend InstanceHelper
6
6
 
7
- attr_accessor :type, :count, :category, :tag_value, :reason, :name
8
- def initialize(type, data_array)
7
+ attr_accessor :type, :count, :category, :tag_value, :reason, :name, :region_based
8
+ def initialize(type, data_hash, region)
9
9
  if type.include?(" with tag")
10
10
  type = type.dup # because type is a frozen string right now
11
11
  type.slice!(" with tag")
12
12
  self.type = type
13
13
  self.category = "tagged"
14
- self.name = data_array[1] || nil
15
- self.reason = data_array[2] || nil
16
- self.tag_value = data_array[3] || nil
14
+ self.name = data_hash[:name] || nil
15
+ self.reason = data_hash[:tag_reason] || nil
16
+ self.tag_value = data_hash[:tag_value] || nil
17
+ self.region_based = data_hash[:region_based] || nil
17
18
  else
18
- self.type = type
19
+ self.region_based = data_hash[:region_based] || nil
19
20
 
20
- if data_array[0] < 0
21
+ if data_hash[:count] < 0
21
22
  self.category = "running"
22
- elsif data_array[0] == 0
23
+ elsif data_hash[:count] == 0
23
24
  self.category = "matched"
24
- elsif data_array[0] > 0
25
+ elsif data_hash[:count] > 0
25
26
  self.category = "reserved"
26
27
  end
28
+
29
+ if region_based?
30
+ # if type = 'Linux VPC t2.small'...
31
+ my_match = type.match(/(\w*\s*\w*\s{1})\s*(\s*\S*)/)
32
+
33
+ # then platform = 'Linux VPC '...
34
+ platform = my_match[1] if my_match
35
+
36
+ # and size = 't2.small'
37
+ size = my_match[2] if my_match
38
+
39
+ self.type = platform << region << ' ' << size
40
+ else
41
+ self.type = type
42
+ end
27
43
  end
28
44
 
29
- self.count = data_array[0].abs
45
+ self.count = data_hash[:count].abs
46
+ end
47
+
48
+ def region_based?
49
+ self.region_based
30
50
  end
31
51
 
32
52
  def tagged?
@@ -20,53 +20,90 @@ module SportNginAwsAuditor
20
20
  end if instances
21
21
 
22
22
  instance_hash.each do |key, value|
23
- instance_hash[key] = [instance_hash[key]]
23
+ instance_hash[key] = {:count => instance_hash[key], :region_based => false}
24
24
  end
25
25
  instance_hash
26
26
  end
27
27
 
28
- def add_instances_with_tag_to_hash(instances_to_add, instance_hash)
28
+ def apply_tagged_instances(instances_to_add, instance_hash)
29
29
  instances_to_add.each do |instance|
30
30
  next if instance.nil?
31
31
  key = instance.to_s.dup << " with tag (" << instance.name << ")"
32
- instance_result = []
32
+ instance_result = {}
33
33
 
34
- if instance_hash.has_key?(instance.to_s) && instance_hash[instance.to_s][0] > 0
35
- current_val = instance_hash[instance.to_s][0]
34
+ if instance_hash.has_key?(instance.to_s) && instance_hash[instance.to_s][:count] > 0
35
+ current_val = instance_hash[instance.to_s][:count]
36
36
  val = current_val - instance.count
37
37
  new_val = val >= 0 ? val : 0
38
- instance_hash[instance.to_s][0] = new_val
38
+ instance_hash[instance.to_s][:count] = new_val
39
39
 
40
40
  val = instance.count - current_val
41
41
  new_val = val >= 0 ? val : 0
42
- instance_result << new_val
42
+ instance_result[:count] = new_val
43
43
  else
44
- instance_result << instance.count
44
+ instance_result[:count] = instance.count
45
45
  end
46
46
 
47
- instance_result << instance.name
48
- instance_result << instance.tag_reason
49
- instance_result << instance.tag_value
47
+ instance_result.merge!({:name => instance.name, :tag_reason => instance.tag_reason,
48
+ :tag_value => instance.tag_value, :region_based => false})
49
+
50
50
  instance_hash[key] = instance_result
51
51
  end if instances_to_add
52
52
 
53
53
  instance_hash
54
54
  end
55
55
 
56
- def compare(instances)
56
+ def apply_region_ris(ris_region, differences)
57
+ ris_region.each do |ri|
58
+ differences.each do |key, value|
59
+ # if key = 'Linux VPC us-east-1a t2.medium'...
60
+ my_match = key.match(/(\w*\s*\w*\s*)\w{2}-\w{2,}-\w{2}(\s*\S*)/)
61
+
62
+ # then platform = 'Linux VPC'...
63
+ platform = my_match[1] if my_match
64
+ platform[platform.length - 1] = ''
65
+
66
+ # and size = 't2.medium'
67
+ size = my_match[2] if my_match
68
+ size[0] = ''
69
+
70
+ if (platform == ri.platform) && (size == ri.instance_type) && (value[:count] < 0)
71
+ until (ri.count == 0) || (value[:count] == 0)
72
+ value[:count] = value[:count] + 1
73
+ ri.count = ri.count - 1
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ ris_region.each do |ri|
80
+ differences[ri.to_s] = {:count => ri.count, :region_based => true}
81
+ end
82
+ end
83
+
84
+ def measure_differences(instance_hash, ris_hash)
57
85
  differences = Hash.new()
86
+ instance_hash.keys.concat(ris_hash.keys).uniq.each do |key|
87
+ instance_count = instance_hash.has_key?(key) ? instance_hash[key][:count] : 0
88
+ ris_count = ris_hash.has_key?(key) ? ris_hash[key][:count] : 0
89
+ differences[key] = {:count => ris_count - instance_count, :region_based => false}
90
+ end
91
+ differences
92
+ end
93
+
94
+ def compare(instances)
58
95
  instances_with_tag = filter_instances_with_tags(instances)
59
- instances_without_tag = filter_instance_without_tags(instances)
96
+ instances_without_tag = filter_instances_without_tags(instances)
60
97
  instance_hash = instance_count_hash(instances_without_tag)
61
- ris = instance_count_hash(get_reserved_instances)
62
-
63
- instance_hash.keys.concat(ris.keys).uniq.each do |key|
64
- instance_count = instance_hash.has_key?(key) ? instance_hash[key][0] : 0
65
- ris_count = ris.has_key?(key) ? ris[key][0] : 0
66
- differences[key] = [ris_count - instance_count]
67
- end
98
+
99
+ ris = get_reserved_instances
100
+ ris_availability = filter_ris_availability_zone(ris)
101
+ ris_region = filter_ris_region_based(ris)
102
+ ris_hash = instance_count_hash(ris_availability)
68
103
 
69
- add_instances_with_tag_to_hash(instances_with_tag, differences)
104
+ differences = measure_differences(instance_hash, ris_hash)
105
+ apply_region_ris(ris_region, differences)
106
+ apply_tagged_instances(instances_with_tag, differences)
70
107
  differences
71
108
  end
72
109
 
@@ -87,13 +124,23 @@ module SportNginAwsAuditor
87
124
  end
88
125
 
89
126
  # assuming the value of the tag is in the form: 01/01/2000 like a date
90
- def filter_instance_without_tags(instances)
127
+ def filter_instances_without_tags(instances)
91
128
  instances.select do |instance|
92
129
  value = gather_instance_tag_date(instance)
93
130
  value.nil? || (Date.today.to_s >= value.to_s)
94
131
  end
95
132
  end
96
133
 
134
+ # this gathers all RIs except the region-based RIs
135
+ def filter_ris_availability_zone(ris)
136
+ ris.reject { |ri| ri.scope == 'Region' }
137
+ end
138
+
139
+ # this filters all of the region-based RIs
140
+ def filter_ris_region_based(ris)
141
+ ris.select { |ri| ri.scope == 'Region' }
142
+ end
143
+
97
144
  # this returns a hash of all instances that have retired between 1 week ago and today
98
145
  def get_retired_tags(instances)
99
146
  return_array = []
@@ -35,10 +35,11 @@ module SportNginAwsAuditor
35
35
  end
36
36
  end
37
37
 
38
- attr_accessor :id, :name, :multi_az, :instance_type, :engine, :count, :tag_value, :tag_reason, :expiration_date
38
+ attr_accessor :id, :name, :multi_az, :scope, :instance_type, :engine, :count, :tag_value, :tag_reason, :expiration_date
39
39
  def initialize(rds_instance, account_id=nil, tag_name=nil, rds=nil)
40
40
  if rds_instance.class.to_s == "Aws::RDS::Types::ReservedDBInstance"
41
41
  self.id = rds_instance.reserved_db_instances_offering_id
42
+ self.scope = nil
42
43
  self.multi_az = rds_instance.multi_az ? "Multi-AZ" : "Single-AZ"
43
44
  self.instance_type = rds_instance.db_instance_class
44
45
  self.engine = engine_helper(rds_instance.product_description)
@@ -47,6 +48,7 @@ module SportNginAwsAuditor
47
48
  elsif rds_instance.class.to_s == "Aws::RDS::Types::DBInstance"
48
49
  self.id = rds_instance.db_instance_identifier
49
50
  self.name = rds_instance.db_name
51
+ self.scope = nil
50
52
  self.multi_az = rds_instance.multi_az ? "Multi-AZ" : "Single-AZ"
51
53
  self.instance_type = rds_instance.db_instance_class
52
54
  self.engine = engine_helper(rds_instance.engine)
@@ -25,6 +25,8 @@ module SportNginAwsAuditor
25
25
  tag_name = options[:tag]
26
26
  end
27
27
 
28
+ zone_output = options[:zone_output]
29
+
28
30
  cycle = [["EC2Instance", options[:ec2]],
29
31
  ["RDSInstance", options[:rds]],
30
32
  ["CacheInstance", options[:cache]]]
@@ -38,37 +40,54 @@ module SportNginAwsAuditor
38
40
  cycle.each do |c|
39
41
  audit_results = AuditData.new(options[:instances], options[:reserved], c.first, tag_name)
40
42
  audit_results.gather_data
41
- print_data(slack, audit_results, c.first, environment) if (c.last || no_selection)
43
+ output_options = {:slack => slack, :class_type => c.first,
44
+ :environment => environment, :zone_output => zone_output}
45
+ print_data(audit_results, output_options) if (c.last || no_selection)
42
46
  end
43
47
  end
44
48
 
45
- def self.print_data(slack, audit_results, class_type, environment)
49
+ def self.print_data(audit_results, output_options)
46
50
  audit_results.data.sort_by! { |instance| [instance.category, instance.type] }
47
51
 
48
- if slack
49
- print_to_slack(audit_results, class_type, environment)
52
+ if output_options[:slack]
53
+ print_to_slack(audit_results, output_options)
50
54
  elsif options[:reserved] || options[:instances]
51
- puts header(class_type)
55
+ puts header(output_options[:class_type])
52
56
  audit_results.data.each{ |instance| say "<%= color('#{instance.type}: #{instance.count}', :white) %>" }
53
57
  else
54
- retired_ris = audit_results.retired_ris
55
- retired_tags = audit_results.retired_tags
56
-
57
- puts header(class_type)
58
- audit_results.data.each{ |instance| colorize(instance) }
58
+ puts header(output_options[:class_type])
59
+ audit_results.data.each{ |instance| colorize(instance, output_options[:zone_output]) }
59
60
 
60
- say_retired_ris(retired_ris, class_type, environment) unless retired_ris.empty?
61
- say_retired_tags(retired_tags, class_type, environment) unless retired_tags.empty?
61
+ say_retired_ris(audit_results, output_options) unless audit_results.retired_ris.empty?
62
+ say_retired_tags(audit_results, output_options) unless audit_results.retired_tags.empty?
62
63
  end
63
64
  end
64
65
 
65
- def self.say_retired_ris(retired_ris, class_type, environment)
66
- say "The following reserved #{class_type}Instances have recently expired in #{environment}:"
67
- retired_ris.each { |ri| say "#{ri.to_s} (#{ri.count}) on #{ri.expiration_date}" }
66
+ def self.say_retired_ris(audit_results, output_options)
67
+ retired_ris = audit_results.retired_ris
68
+ say "The following reserved #{output_options[:class_type]}Instances have recently expired in #{output_options[:environment]}:"
69
+ retired_ris.each do |ri|
70
+ if ri.availability_zone.nil?
71
+ # if ri.to_s = 'Linux VPC t2.small'...
72
+ my_match = ri.to_s.match(/(\w*\s*\w*\s{1})\s*(\s*\S*)/)
73
+
74
+ # then platform = 'Linux VPC '...
75
+ platform = my_match[1] if my_match
76
+
77
+ # and size = 't2.small'
78
+ size = my_match[2] if my_match
79
+
80
+ n = platform << audit_results.region << ' ' << size
81
+ say "#{n} (#{ri.count}) on #{ri.expiration_date}"
82
+ else
83
+ say "#{ri.to_s} (#{ri.count}) on #{ri.expiration_date}"
84
+ end
85
+ end
68
86
  end
69
87
 
70
- def self.say_retired_tags(retired_tags, class_type, environment)
71
- say "The following #{class_type}Instance tags have recently expired in #{environment}:"
88
+ def self.say_retired_tags(audit_results, output_options)
89
+ retired_tags = audit_results.retired_tags
90
+ say "The following #{output_options[:class_type]}Instance tags have recently expired in #{output_options[:environment]}:"
72
91
  retired_tags.each do |tag|
73
92
  if tag.reason
74
93
  say "#{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value} because of #{tag.reason}"
@@ -78,10 +97,11 @@ module SportNginAwsAuditor
78
97
  end
79
98
  end
80
99
 
81
- def self.colorize(instance)
82
- name = instance.type
100
+ def self.colorize(instance, zone_output=nil)
101
+ name = !zone_output && (instance.tagged? || instance.running?) ? print_without_zone(instance.type) : instance.type
83
102
  count = instance.count
84
103
  color, rgb, prefix = color_chooser(instance)
104
+
85
105
  if instance.tagged?
86
106
  if instance.reason
87
107
  puts "#{prefix} #{name}: (expiring on #{instance.tag_value} because of #{instance.reason})".blue
@@ -93,7 +113,7 @@ module SportNginAwsAuditor
93
113
  end
94
114
  end
95
115
 
96
- def self.print_to_slack(audit_results, class_type, environment)
116
+ def self.print_to_slack(audit_results, output_options)
97
117
  discrepancy_array = []
98
118
  tagged_array = []
99
119
 
@@ -104,32 +124,29 @@ module SportNginAwsAuditor
104
124
  end
105
125
 
106
126
  unless discrepancy_array.empty?
107
- print_discrepancies(discrepancy_array, audit_results, class_type, environment)
127
+ print_discrepancies(discrepancy_array, output_options)
108
128
  end
109
129
 
110
- audit_results.data.each do |instance|
130
+ audit_results.data.each do |instance|
111
131
  if instance.tagged?
112
132
  tagged_array.push(instance)
113
133
  end
114
134
  end
115
135
 
116
136
  unless tagged_array.empty?
117
- print_tagged(tagged_array, audit_results, class_type, environment)
137
+ print_tagged(tagged_array, output_options)
118
138
  end
119
139
 
120
- retired_ris = audit_results.retired_ris
121
- retired_tags = audit_results.retired_tags
122
-
123
- print_retired_ris(retired_ris, class_type, environment) unless retired_ris.empty?
124
- print_retired_tags(retired_tags, class_type, environment) unless retired_tags.empty?
140
+ print_retired_ris(audit_results, output_options) unless audit_results.retired_ris.empty?
141
+ print_retired_tags(audit_results, output_options) unless audit_results.retired_tags.empty?
125
142
  end
126
143
 
127
- def self.print_discrepancies(discrepancy_array, audit_results, class_type, environment)
128
- title = "Some #{class_type} discrepancies for #{environment} exist:\n"
144
+ def self.print_discrepancies(discrepancy_array, output_options)
145
+ title = "Some #{output_options[:class_type]} discrepancies for #{output_options[:environment]} exist:\n"
129
146
  slack_instances = NotifySlack.new(title, options[:config_json])
130
147
 
131
148
  discrepancy_array.each do |discrepancy|
132
- type = discrepancy.type
149
+ type = !output_options[:zone_output] && discrepancy.running? ? print_without_zone(discrepancy.type) : discrepancy.type
133
150
  count = discrepancy.count
134
151
  color, rgb, prefix = color_chooser(discrepancy)
135
152
 
@@ -142,12 +159,12 @@ module SportNginAwsAuditor
142
159
  slack_instances.perform
143
160
  end
144
161
 
145
- def self.print_tagged(tagged_array, audit_results, class_type, environment)
146
- title = "There are currently some tagged #{class_type}s in #{environment}:\n"
162
+ def self.print_tagged(tagged_array, output_options)
163
+ title = "There are currently some tagged #{output_options[:class_type]}s in #{output_options[:environment]}:\n"
147
164
  slack_instances = NotifySlack.new(title, options[:config_json])
148
165
 
149
166
  tagged_array.each do |tagged|
150
- type = tagged.type
167
+ type = output_options[:zone_output] ? tagged.type : print_without_zone(tagged.type)
151
168
  count = tagged.count
152
169
  color, rgb, prefix = color_chooser(tagged)
153
170
 
@@ -163,11 +180,26 @@ module SportNginAwsAuditor
163
180
  slack_instances.perform
164
181
  end
165
182
 
166
- def self.print_retired_ris(retired_ris, class_type, environment)
167
- message = "The following reserved #{class_type}s have recently expired in #{environment}:\n"
183
+ def self.print_retired_ris(audit_results, output_options)
184
+ retired_ris = audit_results.retired_ris
185
+ message = "The following reserved #{output_options[:class_type]}s have recently expired in #{output_options[:environment]}:\n"
168
186
 
169
187
  retired_ris.each do |ri|
170
- name = ri.to_s
188
+ if ri.availability_zone.nil?
189
+ # if ri.to_s = 'Linux VPC t2.small'...
190
+ my_match = ri.to_s.match(/(\w*\s*\w*\s{1})\s*(\s*\S*)/)
191
+
192
+ # then platform = 'Linux VPC '...
193
+ platform = my_match[1] if my_match
194
+
195
+ # and size = 't2.small'
196
+ size = my_match[2] if my_match
197
+
198
+ name = platform << audit_results.region << ' ' << size
199
+ else
200
+ name = ri.to_s
201
+ end
202
+
171
203
  count = ri.count
172
204
  expiration_date = ri.expiration_date
173
205
  message << "*#{name}* (#{count}) on *#{expiration_date}*\n"
@@ -177,8 +209,9 @@ module SportNginAwsAuditor
177
209
  slack_retired_ris.perform
178
210
  end
179
211
 
180
- def self.print_retired_tags(retired_tags, class_type, environment)
181
- message = "The following #{class_type} tags have recently expired in #{environment}:\n"
212
+ def self.print_retired_tags(audit_results, output_options)
213
+ retired_tags = audit_results.retired_tags
214
+ message = "The following #{output_options[:class_type]} tags have recently expired in #{output_options[:environment]}:\n"
182
215
 
183
216
  retired_tags.each do |tag|
184
217
  if tag.reason
@@ -192,6 +225,10 @@ module SportNginAwsAuditor
192
225
  slack_retired_tags.perform
193
226
  end
194
227
 
228
+ def self.print_without_zone(type)
229
+ type.sub(/(-\d\w)/, '')
230
+ end
231
+
195
232
  def self.color_chooser(instance)
196
233
  if instance.tagged?
197
234
  return "blue", "#0000CC", "TAGGED -"
@@ -1,3 +1,3 @@
1
1
  module SportNginAwsAuditor
2
- VERSION = "3.9.0"
2
+ VERSION = "3.10.0"
3
3
  end
@@ -4,7 +4,7 @@ module SportNginAwsAuditor
4
4
  describe AuditData do
5
5
  before :each do
6
6
  @instance = double('instance')
7
- @instance1 = double('ec2_instance1')
7
+ @instance1 = double('ec2_instance1', availability_zone: 'us-east-1b')
8
8
  @instance2 = double('ec2_instance2')
9
9
  @instance3 = double('ec2_instance3')
10
10
  @instance4 = double('ec2_instance4')
@@ -14,10 +14,10 @@ module SportNginAwsAuditor
14
14
  allow(SportNginAwsAuditor::EC2Instance).to receive(:get_reserved_instances).and_return(@ec2_instances)
15
15
  allow(SportNginAwsAuditor::EC2Instance).to receive(:get_retired_tags).and_return([])
16
16
  allow(SportNginAwsAuditor::EC2Instance).to receive(:filter_instances_with_tags).and_return([])
17
- allow(SportNginAwsAuditor::EC2Instance).to receive(:filter_instance_without_tags).and_return(@ec2_instances)
17
+ allow(SportNginAwsAuditor::EC2Instance).to receive(:filter_instances_without_tags).and_return(@ec2_instances)
18
18
  allow(SportNginAwsAuditor::EC2Instance).to receive(:instance_count_hash).and_return({'instance1' => 1,
19
19
  'instance2' => 1})
20
- allow(SportNginAwsAuditor::EC2Instance).to receive(:add_instances_with_tag_to_hash).and_return({'instance1' => 1,
20
+ allow(SportNginAwsAuditor::EC2Instance).to receive(:apply_tagged_instances).and_return({'instance1' => 1,
21
21
  'instance2' => 1})
22
22
  allow(SportNginAwsAuditor::EC2Instance).to receive(:compare).and_return({'instance1' => 1,
23
23
  'instance2' => 1})
@@ -137,5 +137,13 @@ module SportNginAwsAuditor
137
137
  expect(result3).to eq(@retired_ris)
138
138
  end
139
139
  end
140
+
141
+ context '#gather_region' do
142
+ it 'should gather the region from an instance' do
143
+ audit_results = AuditData.new(false, false, "EC2Instance", "no-reserved-instance")
144
+ audit_results.gather_region(@ec2_instances)
145
+ expect(audit_results.region).to eq('us-east')
146
+ end
147
+ end
140
148
  end
141
149
  end
@@ -82,6 +82,7 @@ module SportNginAwsAuditor
82
82
  state: "active",
83
83
  availability_zone: "us-east-1b",
84
84
  instance_count: 4,
85
+ scope: 'Availability Zone',
85
86
  class: "Aws::EC2::Types::ReservedInstances")
86
87
  reserved_ec2_instance2 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisalsofake",
87
88
  instance_type: "t2.small",
@@ -89,6 +90,7 @@ module SportNginAwsAuditor
89
90
  state: "active",
90
91
  availability_zone: "us-east-1b",
91
92
  instance_count: 2,
93
+ scope: 'Availability Zone',
92
94
  class: "Aws::EC2::Types::ReservedInstances")
93
95
  reserved_ec2_instances = double('reserved_ec2_instances', reserved_instances: [reserved_ec2_instance1, reserved_ec2_instance2])
94
96
  ec2_client = double('ec2_client', describe_reserved_instances: reserved_ec2_instances)
@@ -134,6 +136,7 @@ module SportNginAwsAuditor
134
136
  state: "retired",
135
137
  availability_zone: "us-east-1b",
136
138
  instance_count: 4,
139
+ scope: 'Availability Zone',
137
140
  class: "Aws::EC2::Types::ReservedInstances",
138
141
  end: @time - 86400)
139
142
  retired_reserved_ec2_instance2 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisalsofake",
@@ -142,6 +145,7 @@ module SportNginAwsAuditor
142
145
  state: "retired",
143
146
  availability_zone: "us-east-1b",
144
147
  instance_count: 2,
148
+ scope: 'Availability Zone',
145
149
  class: "Aws::EC2::Types::ReservedInstances",
146
150
  end: @time - 86400)
147
151
  reserved_ec2_instance1 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisalsofake",
@@ -150,6 +154,7 @@ module SportNginAwsAuditor
150
154
  state: "active",
151
155
  availability_zone: "us-east-1b",
152
156
  instance_count: 2,
157
+ scope: 'Availability Zone',
153
158
  class: "Aws::EC2::Types::ReservedInstances")
154
159
  reserved_ec2_instances = double('reserved_ec2_instances', reserved_instances: [retired_reserved_ec2_instance1,
155
160
  retired_reserved_ec2_instance2,
@@ -0,0 +1,167 @@
1
+ require "sport_ngin_aws_auditor"
2
+
3
+ module SportNginAwsAuditor
4
+ describe InstanceHelper do
5
+ before :each do
6
+ @ec2_instance1 = double('ec2_instance', instance_id: "i-thisisfake",
7
+ instance_type: "t2.small",
8
+ vpc_id: "vpc-alsofake",
9
+ platform: "Linux VPC",
10
+ state: nil,
11
+ placement: nil,
12
+ tags: nil,
13
+ class: "Aws::EC2::Types::Instance",
14
+ key_name: 'Example-instance-01',
15
+ availability_zone: 'us-east-1b')
16
+ @ec2_instance2 = double('ec2_instance', instance_id: "i-thisisfake",
17
+ instance_type: "t2.medium",
18
+ vpc_id: "vpc-alsofake",
19
+ platform: "Windows",
20
+ state: nil,
21
+ placement: nil,
22
+ tags: nil,
23
+ class: "Aws::EC2::Types::Instance",
24
+ key_name: 'Example-instance-02',
25
+ availability_zone: 'us-east-1b')
26
+ @reserved_ec2_instance1 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisalsofake",
27
+ instance_type: "t2.small",
28
+ product_description: "Linux/UNIX (Amazon VPC)",
29
+ state: "active",
30
+ availability_zone: "us-east-1b",
31
+ instance_count: 2,
32
+ scope: 'Availability Zone',
33
+ class: "Aws::EC2::Types::ReservedInstances")
34
+ @reserved_ec2_instance2 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisfake!!",
35
+ instance_type: "t2.medium",
36
+ product_description: "Windows",
37
+ state: "active",
38
+ availability_zone: "us-east-1b",
39
+ instance_count: 4,
40
+ scope: 'Availability Zone',
41
+ class: "Aws::EC2::Types::ReservedInstances")
42
+ @region_reserved_ec2_instance1 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisalsofake",
43
+ instance_type: "t2.small",
44
+ product_description: "Linux/UNIX (Amazon VPC)",
45
+ state: "active",
46
+ availability_zone: nil,
47
+ instance_count: 2,
48
+ scope: 'Region',
49
+ class: "Aws::EC2::Types::ReservedInstances")
50
+ @region_reserved_ec2_instance2 = double('reserved_ec2_instance', reserved_instances_id: "12345-dfas-1234-asdf-thisisfake!!",
51
+ instance_type: "t2.medium",
52
+ product_description: "Windows",
53
+ state: "active",
54
+ availability_zone: nil,
55
+ instance_count: 4,
56
+ scope: 'Region',
57
+ class: "Aws::EC2::Types::ReservedInstances")
58
+ @ec2_instances = [@ec2_instance1, @ec2_instance2]
59
+ @reserved_instances = [@reserved_ec2_instance2, @reserved_ec2_instance1]
60
+ @region_reserved_instances = [@region_reserved_ec2_instance2, @region_reserved_ec2_instance1]
61
+ @all_reserved_instances = [@reserved_ec2_instance2, @reserved_ec2_instance1, @region_reserved_ec2_instance2, @region_reserved_ec2_instance1]
62
+ allow(SportNginAwsAuditor::EC2Instance).to receive(:get_instances).and_return(@ec2_instances)
63
+ allow(SportNginAwsAuditor::EC2Instance).to receive(:get_reserved_instances).and_return(@all_reserved_instances)
64
+ allow(SportNginAwsAuditor::EC2Instance).to receive(:get_retired_tags).and_return([])
65
+ allow(@ec2_instance1).to receive(:count).and_return(1)
66
+ allow(@ec2_instance2).to receive(:count).and_return(1)
67
+ allow(@ec2_instance1).to receive(:to_s).and_return('Linux VPC us-east-1b t2.small')
68
+ allow(@ec2_instance2).to receive(:to_s).and_return('Windows us-east-1b t2.medium')
69
+ allow(@ec2_instance1).to receive(:name).and_return(@ec2_instance1.key_name)
70
+ allow(@ec2_instance2).to receive(:name).and_return(@ec2_instance2.key_name)
71
+ allow(@ec2_instance1).to receive(:tag_reason).and_return(nil)
72
+ allow(@ec2_instance2).to receive(:tag_reason).and_return(nil)
73
+ allow(@ec2_instance1).to receive(:tag_value).and_return(nil)
74
+ allow(@ec2_instance2).to receive(:tag_value).and_return(nil)
75
+ allow(@reserved_ec2_instance1).to receive(:count).and_return(2)
76
+ allow(@reserved_ec2_instance2).to receive(:count).and_return(2)
77
+ allow(@reserved_ec2_instance1).to receive(:to_s).and_return('Linux VPC us-east-1b t2.small')
78
+ allow(@reserved_ec2_instance2).to receive(:to_s).and_return('Windows us-east-1b t2.medium')
79
+ allow(@region_reserved_ec2_instance1).to receive(:platform).and_return('Linux VPC')
80
+ allow(@region_reserved_ec2_instance1).to receive(:instance_type).and_return('t2.small')
81
+ allow(@region_reserved_ec2_instance1).to receive(:count).and_return(2)
82
+ allow(@region_reserved_ec2_instance2).to receive(:platform).and_return('Windows')
83
+ allow(@region_reserved_ec2_instance2).to receive(:instance_type).and_return('t2.medium')
84
+ allow(@region_reserved_ec2_instance2).to receive(:count).and_return(4)
85
+ allow(@region_reserved_ec2_instance1).to receive(:to_s).and_return('Linux VPC t2.small')
86
+ allow(@region_reserved_ec2_instance2).to receive(:to_s).and_return('Windows t2.medium')
87
+ end
88
+
89
+ context '#instance_count_hash' do
90
+ it 'should add the instances to the hash of differences' do
91
+ klass = SportNginAwsAuditor::EC2Instance
92
+ result = klass.instance_count_hash(@ec2_instances)
93
+ expect(result).to eq({'Linux VPC us-east-1b t2.small' => {count: 1, region_based: false}, 'Windows us-east-1b t2.medium' => {count: 1, region_based: false}})
94
+ end
95
+ end
96
+
97
+ context '#apply_tagged_instances' do
98
+ it 'should add the instances to the hash of differences' do
99
+ klass = SportNginAwsAuditor::EC2Instance
100
+ result = klass.apply_tagged_instances(@ec2_instances, {})
101
+ expect(result).to eq({'Linux VPC us-east-1b t2.small with tag (Example-instance-01)' => {count: 1, name: @ec2_instance1.key_name, tag_reason: nil, tag_value: nil, region_based: false},
102
+ 'Windows us-east-1b t2.medium with tag (Example-instance-02)' => {count: 1, name: @ec2_instance2.key_name, tag_reason: nil, tag_value: nil, region_based: false}})
103
+ end
104
+ end
105
+
106
+ context '#apply_region_ris' do
107
+ it 'should factor in the region based RIs into the counting when there is a mixture of region based and non region based' do
108
+ klass = SportNginAwsAuditor::EC2Instance
109
+ allow(@ec2_instance1).to receive(:count).and_return(5)
110
+ allow(@ec2_instance2).to receive(:count).and_return(5)
111
+ allow(@region_reserved_ec2_instance1).to receive(:count=)
112
+ allow(@region_reserved_ec2_instance2).to receive(:count=)
113
+ instance_hash = klass.instance_count_hash(@ec2_instances)
114
+ ris = klass.instance_count_hash(@reserved_instances)
115
+ differences = Hash.new()
116
+ instance_hash.keys.concat(ris.keys).uniq.each do |key|
117
+ instance_count = instance_hash.has_key?(key) ? instance_hash[key][:count] : 0
118
+ ris_count = ris.has_key?(key) ? ris[key][:count] : 0
119
+ differences[key] = {count: ris_count - instance_count, region_based: false}
120
+ end
121
+ result = klass.apply_region_ris(@region_reserved_instances, differences)
122
+ expect(differences).to eq({"Linux VPC us-east-1b t2.small"=>{count: 0, region_based: false}, "Windows us-east-1b t2.medium"=>{count: 0, region_based: false},
123
+ "Linux VPC t2.small" => {count: 2, region_based: true}, "Windows t2.medium" => {count: 4, region_based: true}})
124
+ end
125
+
126
+ it 'should factor in the region based RIs into the counting when there are no zone specific RIs' do
127
+ klass = SportNginAwsAuditor::EC2Instance
128
+ allow(@ec2_instance1).to receive(:count).and_return(-2)
129
+ allow(@ec2_instance2).to receive(:count).and_return(5)
130
+ allow(@region_reserved_ec2_instance1).to receive(:count=)
131
+ allow(@region_reserved_ec2_instance2).to receive(:count=)
132
+ instance_hash = klass.instance_count_hash(@ec2_instances)
133
+ result = klass.apply_region_ris(@region_reserved_instances, instance_hash)
134
+ expect(instance_hash).to eq({"Linux VPC us-east-1b t2.small"=>{count: 0, region_based: false}, "Windows us-east-1b t2.medium"=>{count: 5, region_based: false},
135
+ "Linux VPC t2.small" => {count: 2, region_based: true}, "Windows t2.medium" => {count: 4, region_based: true}})
136
+ end
137
+ end
138
+
139
+ context '#filter_ris_region_based' do
140
+ it 'should filter all of the region based RIs out of the entire RI list' do
141
+ klass = SportNginAwsAuditor::EC2Instance
142
+ result = klass.filter_ris_region_based(@all_reserved_instances)
143
+ expect(result).to eq(@region_reserved_instances)
144
+ end
145
+ end
146
+
147
+ context '#filter_ris_availability_zone' do
148
+ it 'should remove all of the region based RIs out of the entire RI list' do
149
+ klass = SportNginAwsAuditor::EC2Instance
150
+ result = klass.filter_ris_availability_zone(@all_reserved_instances)
151
+ expect(result).to eq(@reserved_instances)
152
+ end
153
+ end
154
+
155
+ context '#gather_instance_tag_date' do
156
+ it 'should remove all of the region based RIs out of the entire RI list' do
157
+ klass = SportNginAwsAuditor::EC2Instance
158
+ allow(@ec2_instance1).to receive(:no_reserved_instance_tag_value).and_return('08/29/1995')
159
+ result = klass.gather_instance_tag_date(@ec2_instance1)
160
+ date_hash = Date._strptime('08/29/1995', '%m/%d/%Y')
161
+ value = Date.new(date_hash[:year], date_hash[:mon], date_hash[:mday]) if date_hash
162
+ expect(result).to eq(value)
163
+ end
164
+ end
165
+
166
+ end
167
+ end
@@ -3,17 +3,17 @@ require "sport_ngin_aws_auditor"
3
3
  module SportNginAwsAuditor
4
4
  describe Instance do
5
5
  it "should make a reserved instance with proper attributes" do
6
- instance = Instance.new("Windows VPC us-east-1e m1.large", [4])
6
+ instance = Instance.new("Windows VPC m1.large", {count: 4, region_based: true}, 'us-east')
7
7
  expect(instance).to be_an_instance_of(Instance)
8
8
  expect(instance.category).to eq("reserved")
9
- expect(instance.type).to eq("Windows VPC us-east-1e m1.large")
9
+ expect(instance.type).to eq("Windows VPC us-east m1.large")
10
10
  expect(instance.count).to eq(4)
11
11
  expect(instance.tagged?).to eq(false)
12
12
  expect(instance.reserved?).to eq(true)
13
13
  end
14
14
 
15
15
  it "should make a running instance with proper attributes" do
16
- instance = Instance.new("Windows VPC us-east-1e m1.large", [-1])
16
+ instance = Instance.new("Windows VPC us-east-1e m1.large", {count: -1, region_based: false}, 'us-east')
17
17
  expect(instance).to be_an_instance_of(Instance)
18
18
  expect(instance.category).to eq("running")
19
19
  expect(instance.type).to eq("Windows VPC us-east-1e m1.large")
@@ -21,7 +21,7 @@ module SportNginAwsAuditor
21
21
  end
22
22
 
23
23
  it "should make an instance with a tag with proper attributes" do
24
- instance = Instance.new("Windows VPC us-east-1e m1.large with tag", [4, 'example-instance-name', 'This is an example', '09/12/2015'])
24
+ instance = Instance.new("Windows VPC us-east-1e m1.large with tag", {count: 4, name: 'example-instance-name', tag_reason: 'This is an example', tag_value: '09/12/2015', region_based: false}, 'us-east')
25
25
  expect(instance).to be_an_instance_of(Instance)
26
26
  expect(instance.category).to eq("tagged")
27
27
  expect(instance.type).to eq("Windows VPC us-east-1e m1.large")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sport_ngin_aws_auditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.9.0
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elliot Hursh
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-11-11 00:00:00.000000000 Z
13
+ date: 2016-11-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws-sdk
@@ -246,6 +246,7 @@ files:
246
246
  - spec/sport_ngin_aws_auditor/cache_instance_spec.rb
247
247
  - spec/sport_ngin_aws_auditor/config_spec.rb
248
248
  - spec/sport_ngin_aws_auditor/ec2_instance_spec.rb
249
+ - spec/sport_ngin_aws_auditor/instance_helper_spec.rb
249
250
  - spec/sport_ngin_aws_auditor/instance_spec.rb
250
251
  - spec/sport_ngin_aws_auditor/notify_slack_spec.rb
251
252
  - spec/sport_ngin_aws_auditor/rds_instance_spec.rb
@@ -282,6 +283,7 @@ test_files:
282
283
  - spec/sport_ngin_aws_auditor/cache_instance_spec.rb
283
284
  - spec/sport_ngin_aws_auditor/config_spec.rb
284
285
  - spec/sport_ngin_aws_auditor/ec2_instance_spec.rb
286
+ - spec/sport_ngin_aws_auditor/instance_helper_spec.rb
285
287
  - spec/sport_ngin_aws_auditor/instance_spec.rb
286
288
  - spec/sport_ngin_aws_auditor/notify_slack_spec.rb
287
289
  - spec/sport_ngin_aws_auditor/rds_instance_spec.rb