sport_ngin_aws_auditor 3.11.3 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  require 'highline/import'
2
2
  require 'colorize'
3
+ require 'aws-sdk'
3
4
  require_relative "../notify_slack"
4
5
  require_relative "../instance"
5
6
  require_relative "../audit_data"
@@ -10,71 +11,87 @@ module SportNginAwsAuditor
10
11
  extend AWSWrapper
11
12
 
12
13
  class << self
13
- attr_accessor :options
14
+ attr_accessor :options, :audit_results
14
15
  end
15
16
 
16
- def self.execute(environment, options=nil, global_options=nil)
17
- aws(environment, global_options[:aws_roles])
18
- @options = options
19
- slack = options[:slack]
20
- no_selection = !(options[:ec2] || options[:rds] || options[:cache])
17
+ #################### EXECUTION ####################
21
18
 
22
- if options[:no_tag]
23
- tag_name = nil
24
- else
25
- tag_name = options[:tag]
26
- end
19
+ def self.execute(environment, options, global_options)
20
+ aws(environment, global_options)
21
+ collect_options(environment, options, global_options)
22
+ print_title
23
+ @regions.each { |region| audit_region(region) }
24
+ reset_credentials
25
+ end
27
26
 
28
- ignore_instances_regexes = []
29
- if options[:ignore_instances_patterns]
30
- ignore_instances_patterns = options[:ignore_instances_patterns].split(', ')
31
- ignore_instances_patterns.each do |r|
32
- ignore_instances_regexes << Regexp.new(r)
33
- end
34
- end
35
-
36
- zone_output = options[:zone_output]
27
+ def self.audit_region(region)
28
+ @region_previously_printed = false
29
+ @message = ""
30
+ @instance_types.each { |type| audit_instance_type(type, region) }
31
+ add_region_to_message(region) unless @message == "" || @region_previously_printed || @slack
32
+ print_message unless @slack
33
+ end
37
34
 
38
- cycle = [["EC2Instance", options[:ec2]],
39
- ["RDSInstance", options[:rds]],
40
- ["CacheInstance", options[:cache]]]
35
+ def self.audit_instance_type(type, region)
36
+ @class = type.first
37
+ @audit_results = AuditData.new({:instances => options[:instances], :reserved => options[:reserved],
38
+ :class => type.first.to_s, :tag_name => @tag_name,
39
+ :regexes => @ignore_instances_regexes, :region => region})
40
+ @audit_results.gather_data
41
41
 
42
- if !slack
43
- print "Gathering info, please wait..."; print "\r"
44
- else
45
- puts "Condensed results from this audit will print into Slack instead of directly to an output."
46
- end
47
-
48
- cycle.each do |c|
49
- audit_results = AuditData.new(options[:instances], options[:reserved], c.first, tag_name, ignore_instances_regexes)
50
- audit_results.gather_data
51
- output_options = {:slack => slack, :class_type => c.first,
52
- :environment => environment, :zone_output => zone_output}
53
- print_data(audit_results, output_options) if (c.last || no_selection)
42
+ unless @audit_results.data.empty?
43
+ add_instance_type_to_message(type)
44
+ print_audit_results(region) if (type.last || @no_selection)
54
45
  end
55
46
  end
56
47
 
57
- def self.print_data(audit_results, output_options)
58
- audit_results.data.sort_by! { |instance| [instance.category, instance.type] }
48
+ def self.print_audit_results(region)
49
+ @audit_results.data.sort_by! { |instance| [instance.category, instance.type] }
59
50
 
60
- if output_options[:slack]
61
- print_to_slack(audit_results, output_options)
51
+ if @slack
52
+ print_to_slack(region)
62
53
  elsif options[:reserved] || options[:instances]
63
- puts header(output_options[:class_type])
64
- audit_results.data.each{ |instance| say "<%= color('#{instance.type}: #{instance.count}', :white) %>" }
54
+ @audit_results.data.each{ |instance| @message << "#{instance.type}: #{instance.count}\n".colorize(:color => :white) }
65
55
  else
66
- puts header(output_options[:class_type])
67
- audit_results.data.each{ |instance| colorize(instance, output_options[:zone_output]) }
56
+ print_to_terminal
57
+ end
58
+ end
59
+
60
+ #################### PRINTING DATA TO TERMINAL ####################
61
+
62
+ def self.print_to_terminal
63
+ say_instances
64
+ say_retired_ris unless @audit_results.retired_ris.empty?
65
+ say_retired_tags unless @audit_results.retired_tags.empty?
66
+ end
68
67
 
69
- say_retired_ris(audit_results, output_options) unless audit_results.retired_ris.empty?
70
- say_retired_tags(audit_results, output_options) unless audit_results.retired_tags.empty?
68
+ def self.say_instances
69
+ @audit_results.data.each do |instance|
70
+ name = !@zone_output && (instance.tagged? || instance.running?) ? print_without_zone(instance.type) : instance.type
71
+ count = instance.count
72
+ color, rgb, prefix = color_chooser({:instance => instance, :retired_ri => false, :retired_tag => false})
73
+
74
+ if instance.tagged?
75
+ if instance.reason
76
+ description = "#{prefix} #{name}: (expiring on #{instance.tag_value} because #{instance.reason})\n"
77
+ else
78
+ description = "#{prefix} #{name}: (expiring on #{instance.tag_value})\n"
79
+ end
80
+ elsif instance.ignored?
81
+ description = "#{prefix} #{name}\n"
82
+ else
83
+ description = "#{prefix} #{name}: #{count}\n"
84
+ end
85
+
86
+ @message << description.colorize(:color => color)
71
87
  end
72
88
  end
73
89
 
74
- def self.say_retired_ris(audit_results, output_options)
75
- retired_ris = audit_results.retired_ris
76
- say "The following reserved #{output_options[:class_type]}Instances have recently expired in #{output_options[:environment]}:"
90
+ def self.say_retired_ris
91
+ retired_ris = @audit_results.retired_ris
92
+
77
93
  retired_ris.each do |ri|
94
+ color, rgb, prefix = color_chooser({:instance => ri, :retired_ri => true, :retired_tag => false})
78
95
  if ri.availability_zone.nil?
79
96
  # if ri.to_s = 'Linux VPC t2.small'...
80
97
  my_match = ri.to_s.match(/(\w*\s*\w*\s{1})\s*(\s*\S*)/)
@@ -85,118 +102,75 @@ module SportNginAwsAuditor
85
102
  # and size = 't2.small'
86
103
  size = my_match[2] if my_match
87
104
 
88
- n = "#{platform}#{audit_results.region} #{size}"
89
- say "#{n} (#{ri.count}) on #{ri.expiration_date}"
105
+ n = "#{platform}#{@audit_results.region} #{size}"
90
106
  else
91
- say "#{ri.to_s} (#{ri.count}) on #{ri.expiration_date}"
107
+ n = ri.to_s
92
108
  end
109
+
110
+ @message << "#{prefix} #{n} (#{ri.count}) on #{ri.expiration_date}\n".colorize(:color => color)
93
111
  end
94
112
  end
95
113
 
96
- def self.say_retired_tags(audit_results, output_options)
97
- retired_tags = audit_results.retired_tags
98
- say "The following #{output_options[:class_type]}Instance tags have recently expired in #{output_options[:environment]}:"
114
+ def self.say_retired_tags
115
+ retired_tags = @audit_results.retired_tags
116
+
99
117
  retired_tags.each do |tag|
118
+ color, rgb, prefix = color_chooser({:instance => tag, :retired_ri => false, :retired_tag => true})
100
119
  if tag.reason
101
- say "#{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value} because #{tag.reason}"
120
+ description ="#{prefix} #{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value} because #{tag.reason}\n"
102
121
  else
103
- say "#{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value}"
122
+ description = "#{prefix} #{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value}"
104
123
  end
105
- end
106
- end
107
124
 
108
- def self.colorize(instance, zone_output=nil)
109
- name = !zone_output && (instance.tagged? || instance.running?) ? print_without_zone(instance.type) : instance.type
110
- count = instance.count
111
- color, rgb, prefix = color_chooser(instance)
112
-
113
- if instance.tagged?
114
- if instance.reason
115
- puts "#{prefix} #{name}: (expiring on #{instance.tag_value} because #{instance.reason})".blue
116
- else
117
- say "<%= color('#{prefix} #{name}: (expiring on #{instance.tag_value})', :#{color}) %>"
118
- end
119
- elsif instance.ignored?
120
- say "<%= color('#{prefix} #{name}', :#{color}) %>"
121
- else
122
- say "<%= color('#{prefix} #{name}: #{count}', :#{color}) %>"
125
+ @message << description.colorize(:color => color)
123
126
  end
124
127
  end
125
128
 
126
- def self.print_to_slack(audit_results, output_options)
127
- discrepancy_array = []
128
- tagged_ignored_array = []
129
-
130
- audit_results.data.each do |instance|
131
- unless instance.matched? || instance.tagged? || instance.ignored?
132
- discrepancy_array.push(instance)
133
- end
134
- end
135
-
136
- unless discrepancy_array.empty?
137
- print_discrepancies(discrepancy_array, output_options)
138
- end
129
+ #################### PRINTING DATA TO SLACK ####################
139
130
 
140
- audit_results.data.each do |instance|
141
- if instance.tagged? || instance.ignored?
142
- tagged_ignored_array.push(instance)
143
- end
144
- end
131
+ def self.print_to_slack(region)
132
+ @slack_message = NotifySlack.new(@message, @options[:config_json])
145
133
 
146
- unless tagged_ignored_array.empty?
147
- print_tagged(tagged_ignored_array, output_options)
148
- end
134
+ print_instances
135
+ print_retired_ris unless @audit_results.retired_ris.empty?
136
+ print_retired_tags unless @audit_results.retired_tags.empty?
149
137
 
150
- print_retired_ris(audit_results, output_options) unless audit_results.retired_ris.empty?
151
- print_retired_tags(audit_results, output_options) unless audit_results.retired_tags.empty?
138
+ add_region_to_message(region) unless @region_previously_printed
139
+ print_message
140
+ @region_previously_printed = true
141
+ @message = ""
152
142
  end
153
143
 
154
- def self.print_discrepancies(discrepancy_array, output_options)
155
- title = "Some #{output_options[:class_type]} discrepancies for #{output_options[:environment]} exist:\n"
156
- slack_instances = NotifySlack.new(title, options[:config_json])
144
+ def self.print_instances
145
+ data_array = @audit_results.data.reject { |data| data.matched? }
157
146
 
158
- discrepancy_array.each do |discrepancy|
159
- type = !output_options[:zone_output] && discrepancy.running? ? print_without_zone(discrepancy.type) : discrepancy.type
160
- count = discrepancy.count
161
- color, rgb, prefix = color_chooser(discrepancy)
162
-
163
- unless discrepancy.tagged?
164
- text = "#{prefix} #{type}: #{count}"
165
- slack_instances.attachments.push({"color" => rgb, "text" => text, "mrkdwn_in" => ["text"]})
166
- end
167
- end
168
-
169
- slack_instances.perform
170
- end
171
-
172
- def self.print_tagged(tagged_ignored_array, output_options)
173
- title = "There are currently some tagged or ignored #{output_options[:class_type]}s in #{output_options[:environment]}:\n"
174
- slack_instances = NotifySlack.new(title, options[:config_json])
175
-
176
- tagged_ignored_array.each do |tagged_or_ignored|
177
- type = output_options[:zone_output] ? tagged_or_ignored.type : print_without_zone(tagged_or_ignored.type)
178
- count = tagged_or_ignored.count
179
- color, rgb, prefix = color_chooser(tagged_or_ignored)
180
-
181
- if tagged_or_ignored.tagged?
182
- if tagged_or_ignored.reason
183
- text = "#{prefix} #{tagged_or_ignored.name}: (expiring on #{tagged_or_ignored.tag_value} because #{tagged_or_ignored.reason})"
147
+ if data_array.empty?
148
+ @slack_message.attachments.push({"color" => "#32CD32", "text" => "All RIs are properly matched here!", "mrkdwn_in" => ["text"]})
149
+ else
150
+ data_array.each do |data|
151
+ type = !@zone_output && (data.tagged? || data.running?) ? print_without_zone(data.type) : data.type
152
+ count = data.count
153
+ color, rgb, prefix = color_chooser({:instance => data, :retired_ri => false, :retired_tag => false})
154
+
155
+ if data.tagged?
156
+ if data.reason
157
+ text = "#{prefix} #{data.name}: (expiring on #{data.tag_value} because #{data.reason})"
158
+ else
159
+ text = "#{prefix} #{data.name}: (expiring on #{data.tag_value})"
160
+ end
161
+ elsif data.ignored?
162
+ text = "#{prefix} #{data.name}"
184
163
  else
185
- text = "#{prefix} #{tagged_or_ignored.name}: (expiring on #{tagged_or_ignored.tag_value})"
164
+ text = "#{prefix} #{type}: #{count}"
186
165
  end
187
- elsif tagged_or_ignored.ignored?
188
- text = "#{prefix} #{tagged_or_ignored.name}"
189
- end
190
166
 
191
- slack_instances.attachments.push({"color" => rgb, "text" => text, "mrkdwn_in" => ["text"]})
167
+ @slack_message.attachments.push({"color" => rgb, "text" => text, "mrkdwn_in" => ["text"]})
168
+ end
192
169
  end
193
-
194
- slack_instances.perform
195
170
  end
196
171
 
197
- def self.print_retired_ris(audit_results, output_options)
198
- retired_ris = audit_results.retired_ris
199
- message = "The following reserved #{output_options[:class_type]}s have recently expired in #{output_options[:environment]}:\n"
172
+ def self.print_retired_ris
173
+ retired_ris = @audit_results.retired_ris
200
174
 
201
175
  retired_ris.each do |ri|
202
176
  if ri.availability_zone.nil?
@@ -209,64 +183,129 @@ module SportNginAwsAuditor
209
183
  # and size = 't2.small'
210
184
  size = my_match[2] if my_match
211
185
 
212
- name = "#{platform}#{audit_results.region} #{size}"
186
+ name = "#{platform}#{@audit_results.region} #{size}"
213
187
  else
214
188
  name = ri.to_s
215
189
  end
216
190
 
217
191
  count = ri.count
192
+ color, rgb, prefix = color_chooser({:instance => ri, :retired_ri => true, :retired_tag => false})
218
193
  expiration_date = ri.expiration_date
219
- message << "*#{name}* (#{count}) on *#{expiration_date}*\n"
194
+ text = "#{prefix} #{name} (#{count}) on #{expiration_date}"
195
+
196
+ @slack_message.attachments.push({"color" => rgb, "text" => text, "mrkdwn_in" => ["text"]})
220
197
  end
221
-
222
- slack_retired_ris = NotifySlack.new(message, options[:config_json])
223
- slack_retired_ris.perform
224
198
  end
225
199
 
226
- def self.print_retired_tags(audit_results, output_options)
227
- retired_tags = audit_results.retired_tags
228
- message = "The following #{output_options[:class_type]} tags have recently expired in #{output_options[:environment]}:\n"
200
+ def self.print_retired_tags
201
+ retired_tags = @audit_results.retired_tags
229
202
 
230
203
  retired_tags.each do |tag|
204
+ color, rgb, prefix = color_chooser({:instance => tag, :retired_ri => false, :retired_tag => true})
205
+
231
206
  if tag.reason
232
- message << "*#{tag.instance_name}* (#{tag.instance_type}) retired on *#{tag.value}* because #{tag.reason}\n"
207
+ text = "#{prefix} #{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value} because #{tag.reason}"
233
208
  else
234
- message << "*#{tag.instance_name}* (#{tag.instance_type}) retired on *#{tag.value}*\n"
209
+ text = "#{prefix} #{tag.instance_name} (#{tag.instance_type}) retired on #{tag.value}"
235
210
  end
211
+
212
+ @slack_message.attachments.push({"color" => rgb, "text" => text, "mrkdwn_in" => ["text"]})
236
213
  end
214
+ end
215
+
216
+ #################### OTHER HELPFUL METHODS ####################
237
217
 
238
- slack_retired_tags = NotifySlack.new(message, options[:config_json])
239
- slack_retired_tags.perform
218
+ def self.gather_regions
219
+ ec2 = Aws::EC2::Client.new(region: 'us-east-1')
220
+ regions = ec2.describe_regions[:regions]
221
+ us_regions = regions.select { |region| region.region_name.include?("us") }
222
+ us_regions.collect { |r| r.region_name }
240
223
  end
241
224
 
242
- def self.print_without_zone(type)
243
- type.sub(/(-\d\w)/, '')
225
+ def self.collect_options(environment, options, global_options)
226
+ @options = options
227
+ @display_name = global_options[:display] || environment
228
+ @slack = options[:slack]
229
+ @no_selection = !(options[:ec2] || options[:rds] || options[:cache])
230
+ @zone_output = options[:zone_output]
231
+ @regions = (global_options[:region].split(', ') if global_options[:region]) || gather_regions
232
+
233
+ if options[:no_tag]
234
+ @tag_name = nil
235
+ else
236
+ @tag_name = options[:tag]
237
+ end
238
+
239
+ @ignore_instances_regexes = []
240
+ if options[:ignore_instances_patterns]
241
+ options[:ignore_instances_patterns].split(', ').each do |r|
242
+ @ignore_instances_regexes << Regexp.new(r)
243
+ end
244
+ end
245
+
246
+ @instance_types = [["EC2Instance", options[:ec2]],
247
+ ["RDSInstance", options[:rds]],
248
+ ["CacheInstance", options[:cache]]]
249
+ end
250
+
251
+ def self.print_title
252
+ if @slack
253
+ puts "Condensed results from this audit will print into Slack instead of directly to an output."
254
+ NotifySlack.new("_AWS AUDIT FOR #{@display_name}_", @options[:config_json]).perform
255
+ else
256
+ puts "AWS AUDIT FOR #{@display_name}".colorize(:color => :yellow, :background => :red).underline
257
+ puts
258
+ end
259
+ end
260
+
261
+ def self.print_message
262
+ unless @message == ""
263
+ if @slack
264
+ @slack_message.perform
265
+ else
266
+ puts @message
267
+ end
268
+ end
244
269
  end
245
270
 
246
- def self.color_chooser(instance)
247
- if instance.tagged?
248
- return "blue", "#0000CC", "TAGGED -"
249
- elsif instance.ignored?
250
- return "blue", "#0000CC", "IGNORED -"
251
- elsif instance.running?
252
- return "yellow", "#FFD700", "MISSING RI -"
253
- elsif instance.matched?
254
- return "green", "#32CD32", "MATCHED RI -"
255
- elsif instance.reserved?
256
- return "red", "#BF1616", "UNUSED RI -"
271
+ def self.add_region_to_message(region)
272
+ if @slack
273
+ @message.prepend("_REGION: *_#{region}_*_\n")
274
+ @slack_message.text = @message
275
+ else
276
+ @message.prepend("REGION: #{region}\n".colorize(:color => :magenta).underline)
257
277
  end
258
278
  end
259
279
 
260
- def self.header(type, length = 50)
261
- type.upcase!.slice! "INSTANCE"
262
- half_length = (length - type.length)/2.0 - 1
263
- [
264
- "*" * length,
265
- "*" * half_length.floor + " #{type} " + "*" * half_length.ceil,
266
- "*" * length
267
- ].join("\n")
280
+ def self.add_instance_type_to_message(type)
281
+ if @slack
282
+ @message << "*#{type.first}s*\n"
283
+ else
284
+ @message << "#{type.first}s\n".underline
285
+ end
268
286
  end
269
287
 
288
+ def self.print_without_zone(type)
289
+ type.sub(/(-\d\w)/, '')
290
+ end
291
+
292
+ def self.color_chooser(data)
293
+ if data[:retired_ri]
294
+ return :light_black, "#595959", "RETIRED RI -"
295
+ elsif data[:retired_tag]
296
+ return :light_black, "#595959", "RETIRED TAG -"
297
+ elsif data[:instance].tagged?
298
+ return :blue, "#0000CC", "TAGGED -"
299
+ elsif data[:instance].ignored?
300
+ return :blue, "#0000CC", "IGNORED -"
301
+ elsif data[:instance].running?
302
+ return :yellow, "#FFD700", "MISSING RI -"
303
+ elsif data[:instance].matched?
304
+ return :green, "#32CD32", "MATCHED RI -"
305
+ elsif data[:instance].reserved?
306
+ return :red, "#BF1616", "UNUSED RI -"
307
+ end
308
+ end
270
309
  end
271
310
  end
272
311
  end
@@ -3,19 +3,32 @@ module SportNginAwsAuditor
3
3
  class Inspect
4
4
  extend AWSWrapper
5
5
  extend OpsWorksWrapper
6
+ extend EC2Wrapper
7
+ extend RDSWrapper
8
+ extend CacheWrapper
6
9
 
7
10
  def self.execute(environment, options=nil, global_options=nil)
8
- aws(environment, global_options[:aws_roles])
11
+ aws(environment, global_options)
12
+ region = (global_options[:region].split(', ') if global_options[:region]) || 'us-east-1'
9
13
  no_selection = options.values.uniq == [false]
10
- output("EC2Instance") if options[:ec2] || no_selection
11
- output("RDSInstance") if options[:rds] || no_selection
12
- output("CacheInstance") if options[:cache] || no_selection
14
+ output("EC2Instance", region) if options[:ec2] || no_selection
15
+ output("RDSInstance", region) if options[:rds] || no_selection
16
+ output("CacheInstance", region) if options[:cache] || no_selection
13
17
  end
14
18
 
15
- def self.output(class_type)
19
+ def self.output(class_type, region)
16
20
  klass = SportNginAwsAuditor.const_get(class_type)
21
+
22
+ if class_type == "EC2Instance"
23
+ client = EC2Wrapper.ec2(region)
24
+ elsif class_type == "RDSInstance"
25
+ client = RDSWrapper.rds(region)
26
+ elsif class_type == "CacheInstance"
27
+ client = CacheWrapper.cache(region)
28
+ end
29
+
17
30
  print "Gathering info, please wait..."; print "\r"
18
- instances = class_type == "EC2Instance" ? klass.bucketize : klass.instance_hash
31
+ instances = class_type == "EC2Instance" ? klass.bucketize(client) : klass.instance_hash(client)
19
32
  say "<%= color('#{header(class_type)}', :white) %>"
20
33
  instances.each do |key, value|
21
34
  pretty_print(key, klass.instance_count_hash(Array(value)))
@@ -36,7 +49,7 @@ module SportNginAwsAuditor
36
49
  puts "======================================="
37
50
  puts "#{title}"
38
51
  puts "======================================="
39
- body.each{ |key, value| say "<%= color('#{key}: #{value}', :white) %>" }
52
+ body.each{ |key, value| say "<%= color('#{key}: #{value[:count]}', :white) %>" }
40
53
  puts "\n"
41
54
  end
42
55
  end
@@ -1,3 +1,3 @@
1
1
  module SportNginAwsAuditor
2
- VERSION = "3.11.3"
2
+ VERSION = "4.0.0"
3
3
  end