awsutils 2.2.1 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  require 'json'
2
2
  require 'net/http'
3
3
  require 'optimist'
4
- # require 'aws-sdk' # see the comment on `image_details` below
4
+ # require 'aws-sdk-ec2' # see the comment on `image_details` below
5
5
 
6
6
  module AwsUtils
7
7
  class Ec2LatestImage
@@ -9,9 +9,9 @@ module AwsUtils
9
9
  @releases ||= begin
10
10
  parsed_releases =
11
11
  if opts[:ownedbyme]
12
- fail 'AWS_OWNER_ID not defined' unless ENV['AWS_OWNER_ID']
12
+ raise 'AWS_OWNER_ID not defined' unless ENV['AWS_OWNER_ID']
13
13
 
14
- require 'aws-sdk'
14
+ require 'aws-sdk-ec2'
15
15
 
16
16
  ubuntu_images =
17
17
  connection.describe_images(owners: [ENV['AWS_OWNER_ID']]).images.select do |image|
@@ -31,7 +31,7 @@ module AwsUtils
31
31
  else
32
32
  resp = JSON.parse(
33
33
  Net::HTTP.get(
34
- URI("http://cloud-images.ubuntu.com/locator/ec2/releasesTable?_=#{(Time.now.to_f*1000).to_i}")
34
+ URI("http://cloud-images.ubuntu.com/locator/ec2/releasesTable?_=#{(Time.now.to_f * 1000).to_i}")
35
35
  ).sub(/\],\n\]/, "]\n]")
36
36
  )
37
37
  parse_releases_array(resp['aaData'])
@@ -39,8 +39,8 @@ module AwsUtils
39
39
 
40
40
  parsed_releases.select do |rel|
41
41
  rel[:region] == opts[:region] &&
42
- rel[:distro_version] == "#{opts[:release]}" &&
43
- %w(amd64 x86_64).include?(rel[:arch])
42
+ rel[:distro_version] == opts[:release].to_s &&
43
+ %w[amd64 x86_64].include?(rel[:arch])
44
44
  end
45
45
  end
46
46
  end
@@ -81,23 +81,20 @@ module AwsUtils
81
81
  # images_details.each_with_object({}) { |ami, m| m[ami.image_id] = ami }
82
82
  # end
83
83
  # end
84
-
85
- # rubocop:disable Metrics/MethodLength
86
84
  def parse_releases_array(releases)
87
85
  releases.map do |rel|
88
86
  {
89
- region: rel[0],
90
- distro_name: rel[1],
87
+ region: rel[0],
88
+ distro_name: rel[1],
91
89
  distro_version: rel[2],
92
- arch: rel[3],
93
- type: rel[4],
94
- release: rel[5],
95
- ami: parse_ami_link(rel[6]),
96
- aki: rel[7]
90
+ arch: rel[3],
91
+ type: rel[4],
92
+ release: rel[5],
93
+ ami: parse_ami_link(rel[6]),
94
+ aki: rel[7]
97
95
  }
98
96
  end
99
97
  end
100
- # rubocop:enable Metrics/MethodLength
101
98
 
102
99
  def parse_ami_link(link)
103
100
  link.match(/launchAmi=(ami-\w{8})/)[1]
@@ -1,404 +1,202 @@
1
- require 'rubygems'
2
1
  require 'optimist'
3
- require 'fog'
4
-
5
- gem 'fog', '>= 1.6.0'
6
-
7
- class Array
8
- def longest
9
- length = 0
10
- val = ''
11
- each do |a|
12
- len_new = a.length
13
- if len_new > length
14
- length = len_new
15
- val = a
16
- end
17
- end
18
- val
19
- end
20
- end
2
+ require 'aws-sdk-ec2'
3
+ require 'highline'
4
+ require 'colorize'
5
+
6
+ GROUPS_MAX_LENGTH = 96
21
7
 
22
8
  module AwsUtils
23
9
  class Ec2ListMachines
24
- def connect
25
- @connect = Fog::Compute.new(provider: 'AWS')
26
- if $DEBUG
27
- puts 'Inspect connection result:'
28
- puts connect.inspect
10
+ def run
11
+ servers_sorted = formatted_servers.sort_by { |server| server.fetch(opts[:sort].to_sym, '') }
12
+
13
+ # Clear all formatting
14
+ printf "\033[0m"
15
+
16
+ items = bold_header + servers_sorted.map do |server|
17
+ columns.keys.map do |col|
18
+ # Set an empty string here so that CSV ends up with the right number of cols even when
19
+ # a field is unset
20
+ server[col] || ''
21
+ end
22
+ end.flatten
23
+
24
+ if opts[:csv]
25
+ hl = HighLine::List.new items, cols: columns.count
26
+ hl.row_join_string = ','
27
+ puts hl.to_s
28
+ else
29
+ puts HighLine.new.list items, :uneven_columns_across, columns.count
29
30
  end
31
+ end
32
+
33
+ private
30
34
 
31
- @connect
35
+ def connect
36
+ @connect ||= Aws::EC2::Client.new
32
37
  end
33
38
 
34
- def get_servers(opts)
35
- static_ips = []
36
- connect.addresses.all.each do |eip|
37
- static_ips << eip.public_ip
39
+ def servers
40
+ return connect.describe_instances.reservations.map(&:instances).flatten unless opts[:search]
41
+
42
+ connect.describe_instances.reservations.map(&:instances).flatten.select do |i|
43
+ (name_tag = i.tags.find { |t| t.key == 'Name' }) &&
44
+ name_tag.value =~ /.*#{opts[:search]}.*/
38
45
  end
46
+ end
39
47
 
40
- group_map = {}
41
- connect.security_groups.map { |g| group_map[g.name] = g.group_id }
48
+ def include_terminated?
49
+ opts[:terminated] || opts[:state] == 'terminated'
50
+ end
42
51
 
43
- if opts[:search]
44
- servers = connect.servers.select do |s|
45
- s.tags['Name'] =~ /.*#{opts[:search]}.*/
46
- end
47
- else
48
- servers = connect.servers
49
- end
52
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
53
+ def include_server?(server)
54
+ return false if (opts[:state] && server.state.name != opts[:state]) ||
55
+ (opts[:type] && server.instance_type != opts[:type]) ||
56
+ (opts[:zone] && server.placement.availability_zone != opts[:zone]) ||
57
+ (server.state.name == 'terminated' && !include_terminated?)
50
58
 
51
- servers_a = []
52
-
53
- servers.each do |s|
54
- if (opts[:state].nil? || (s.state == opts[:state])) &&
55
- (
56
- (opts[:terminated] == true) ||
57
- (s.state != 'terminated') ||
58
- (opts[:state] == 'terminated')
59
- ) &&
60
- (opts[:flavor].nil? || (s.flavor_id == opts[:flavor])) &&
61
- (opts[:role].nil? || (s.tags['Role'] == opts[:role])) &&
62
- (opts[:zone].nil? || (s.availability_zone.to_s == opts[:zone]))
63
-
64
- if s.public_ip_address.nil?
65
- pub_ip = ''
66
- else
67
- pub_ip = s.public_ip_address
68
-
69
- static_ip = static_ips.include?(pub_ip) ? '(S)' : '(D)'
70
- end
59
+ true
60
+ end
61
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
62
+
63
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
64
+ def formatted_servers
65
+ static_ips = connect.describe_addresses.addresses.map(&:public_ip)
66
+
67
+ servers.select { |server| include_server? server }.map do |server|
68
+ o = {
69
+ date: server.launch_time.to_s,
70
+ az: server.placement.availability_zone,
71
+ id: server.instance_id,
72
+ subnet: [server.subnet_id, "(#{subnet_name[server.subnet_id]})"].join(' '),
73
+ priv_ip: server.private_ip_address,
74
+ type: server.instance_type,
75
+ vpc: server.vpc_id,
76
+ state: opts[:csv] ? server.state.name : colorize_state(server.state.name).bold
77
+ }
71
78
 
72
- role = s.tags['Role'] ? '-' : s.tags['Role']
79
+ if opts[:groups]
80
+ groups_string =
81
+ server.security_groupserver.map { |g| "#{g.group_id} (#{g.group_name})" }.join(', ')
73
82
 
74
- if s.groups.first =~ /-/
75
- group_to_insert = "#{group_map[s.groups.first]}/#{s.groups.first}"
76
- else
77
- group_to_insert = s.groups.first
83
+ # Shorten the groups string to a manageable length
84
+ unless (opts[:csv] || opts[:all_groups]) && groups_string.length > GROUPS_MAX_LENGTH
85
+ groups_string = groups_string[0..GROUPS_MAX_LENGTH] + '...'
78
86
  end
79
87
 
80
- created = s.tags['created_on'] ||= s.created_at
81
-
82
- servers_a << {
83
- name: s.tags['Name'].to_s,
84
- date: created.to_s,
85
- role: role.to_s,
86
- az: s.availability_zone.to_s,
87
- id: s.id,
88
- group: group_to_insert,
89
- pub_ip: pub_ip,
90
- static_ip: static_ip,
91
- flavor: s.flavor_id,
92
- state: s.state
93
- }
88
+ o[:groups] = groups_string
89
+ end
94
90
 
91
+ if server.vpc_id && opts[:vpc]
92
+ o[:vpc] = [server.vpc_id, "(#{vpc_name[server.vpc_id]})"].join(' ')
95
93
  end
96
- end
97
94
 
98
- servers_a
99
- end
95
+ if server.public_ip_address
96
+ static_ip = static_ips.include?(server.public_ip_address) ? '(S)' : '(D)'
97
+ o[:pub_ip] = [server.public_ip_address, static_ip].join(' ')
98
+ end
100
99
 
101
- def sort_servers(servers_a, sort_col)
102
- case sort_col
103
- when 'role'
104
- servers_a.sort_by { |a| a[:role] }
105
- when 'az'
106
- servers_a.sort_by { |a| a[:az] }
107
- when 'name'
108
- servers_a.sort_by { |a| a[:name] }
109
- when 'id'
110
- servers_a.sort_by { |a| a[:id] }
111
- when 'group'
112
- servers_a.sort_by { |a| a[:group] }
113
- when 'flavor'
114
- servers_a.sort_by { |a| a[:flavor] }
115
- when 'state'
116
- servers_a.sort_by { |a| a[:state] }
117
- when 'ip'
118
- servers_a.sort_by { |a| a[:pub_ip] }
119
- when 'static'
120
- servers_a.sort_by { |a| a[:static_ip] }
121
- when 'date'
122
- servers_a.sort_by { |a| a[:date] }
123
- else
124
- servers_a.sort_by { |a| a[:name] }
100
+ # Always include the name tag regardless of cli args (for searching)
101
+ (opts[:tags] | %w[tag_Name]).each do |tag|
102
+ next unless (k = server.tags.find { |t| t.key == tag })
103
+
104
+ o["tag_#{tag}".to_sym] = k.value
105
+ end
106
+
107
+ o
125
108
  end
126
109
  end
110
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
127
111
 
128
- def get_longest_server_name(servers_sorted)
129
- server_names = []
130
- servers_sorted.each do |server|
131
- server_names << server[:name]
132
- end
112
+ def vpc_name
113
+ @vpc_name ||= connect.describe_vpcs.vpcs.each_with_object({}) do |v, m|
114
+ next unless (tag = v.tags.find { |t| t.key == 'Name' })
133
115
 
134
- server_names.longest.length + 1
116
+ m[v.vpc_id] = tag.value
117
+ end
135
118
  end
136
119
 
137
- def print_headers(csv_opt, servers_sorted, longest_server_name, show_dates)
138
- if csv_opt == true
139
- if show_dates
140
- printf(
141
- "%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
142
- 'Name',
143
- 'Created',
144
- 'Role',
145
- 'AZ',
146
- 'ID',
147
- 'Sec Group',
148
- 'Public IP,(S/D)',
149
- 'Flavor',
150
- 'State'
151
- )
152
- else
153
- printf(
154
- "%s,%s,%s,%s,%s,%s,%s,%s\n",
155
- 'Name',
156
- 'Role',
157
- 'AZ',
158
- 'ID',
159
- 'Sec Group',
160
- 'Public IP,(S/D)',
161
- 'Flavor',
162
- 'State'
163
- )
164
- end
165
- else
166
- if show_dates
167
- printf(
168
- "\033[0;4m%-#{longest_server_name}s %-30s %-16s %-10s %-10s " \
169
- "%-24s %-19s %-10s %-7s\033[0m\n",
170
- 'Name',
171
- 'Created',
172
- 'Role',
173
- 'AZ',
174
- 'ID',
175
- 'Sec Group',
176
- 'Public IP (S/D)',
177
- 'Flavor',
178
- 'State'
179
- )
180
- else
181
- printf(
182
- "\033[0;4m%-#{longest_server_name}s %-16s %-10s %-10s %-24s " \
183
- "%-19s %-10s %-7s\033[0m\n",
184
- 'Name',
185
- 'Role',
186
- 'AZ',
187
- 'ID',
188
- 'Sec Group',
189
- 'Public IP (S/D)',
190
- 'Flavor',
191
- 'State'
192
- )
193
- end
120
+ def subnet_name
121
+ @subnet_name ||= connect.describe_subnets.subnets.each_with_object({}) do |s, m|
122
+ next unless (tag = s.tags.find { |t| t.key == 'Name' })
123
+
124
+ m[s.subnet_id] = tag.value
194
125
  end
195
126
  end
196
127
 
197
- def get_flavor_color(flavor)
198
- case flavor
199
- when 't1.micro'
200
- '1;33'
201
- when 'm1.large'
202
- '0;33'
203
- when 'm1.xlarge'
204
- '0;34'
205
- when 'm2.2xlarge'
206
- '0;35'
207
- when 'm2.4xlarge'
208
- '0;31'
209
- when 'm2.xlarge'
210
- '0;36'
211
- else
212
- '0'
213
- end
128
+ def columns
129
+ # This method also determines this display order
130
+
131
+ o = { id: 'ID' }
132
+
133
+ o.merge!(
134
+ az: 'AZ',
135
+ subnet: 'Subnet',
136
+ priv_ip: 'Private IP'
137
+ )
138
+
139
+ o[:groups] = 'Security Groups' if opts[:groups]
140
+ o[:vpc] = 'VPC' if opts[:vpc]
141
+ o[:created] = 'Created' if opts[:dates]
142
+
143
+ o.merge!(
144
+ type: 'Type',
145
+ state: 'State',
146
+ pub_ip: 'Public IP'
147
+ )
148
+
149
+ opts[:tags].each { |t| o["tag_#{t}".to_sym] = "Tag:#{t}" }
150
+
151
+ o
214
152
  end
215
153
 
216
- def get_state_color(state)
154
+ def colorize_state(state)
217
155
  case state
218
156
  when 'running'
219
- '1;32'
157
+ state.colorize :green
220
158
  when 'stopped'
221
- '1;31'
222
- when 'starting'
223
- '5;32'
224
- when 'stopping'
225
- '5;31'
159
+ state.colorize :red
160
+ when 'starting', 'stopping'
161
+ state.colorize :orange
226
162
  else
227
- '0'
163
+ state.disable_colorization = true
228
164
  end
229
165
  end
230
166
 
231
- def print_machine_list(servers_sorted, nocolor_opt, csv_opt, longest_server_name, show_dates)
232
- servers_sorted.each do |server|
233
- name = server[:name]
234
- date = server[:date]
235
- role = server[:role]
236
- az = server[:az]
237
- id = server[:id]
238
- group = server[:group]
239
- ip = server[:pub_ip]
240
- static_ip = server[:static_ip]
241
- flavor = server[:flavor]
242
- state = server[:state]
243
- fcolor = get_flavor_color(flavor)
244
- scolor = get_state_color(state)
245
-
246
- if (nocolor_opt == false) && (csv_opt == false)
247
- if show_dates
248
- printf(
249
- "\033[1m%-#{longest_server_name}s\033[0m %-30s %-16s %-10s %-10s " \
250
- "%-24s %-19s \033[#{fcolor}m%-11s\033[#{scolor}m%-7s\033[0m\n",
251
- name,
252
- date,
253
- role,
254
- az,
255
- id,
256
- group,
257
- "#{ip} #{static_ip}",
258
- flavor,
259
- state
260
- )
261
- else
262
- printf(
263
- "\033[1m%-#{longest_server_name}s\033[0m %-16s %-10s %-10s " \
264
- "%-24s %-19s \033[#{fcolor}m%-11s\033[#{scolor}m%-7s\033[0m\n",
265
- name,
266
- role,
267
- az,
268
- id,
269
- group,
270
- "#{ip} #{static_ip}",
271
- flavor,
272
- state
273
- )
274
- end
275
- elsif csv_opt == true
276
- if show_dates
277
- printf(
278
- "%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
279
- name,
280
- date,
281
- role,
282
- az,
283
- id,
284
- group,
285
- "#{ip},#{static_ip}",
286
- flavor,
287
- state
288
- )
289
- else
290
- printf(
291
- "%s,%s,%s,%s,%s,%s,%s,%s\n",
292
- name,
293
- role,
294
- az,
295
- id,
296
- group,
297
- "#{ip},#{static_ip}",
298
- flavor,
299
- state
300
- )
301
- end
167
+ def bold_header
168
+ columns.values.each_with_index.map do |v, i|
169
+ if i.zero?
170
+ "\033[1m#{v}"
171
+ elsif i == columns.count - 1
172
+ "#{v}\033[0m"
302
173
  else
303
- if show_dates
304
- printf(
305
- "%-#{longest_server_name}s %-20s %-16s %-10s %-10s %-20s %-19s %-11s%-7s\n",
306
- name,
307
- date,
308
- role,
309
- az,
310
- id,
311
- group,
312
- "#{ip} #{static_ip}",
313
- flavor,
314
- state
315
- )
316
- else
317
- printf(
318
- "%-#{longest_server_name}s %-16s %-10s %-10s %-20s %-19s %-11s%-7s\n",
319
- name,
320
- role,
321
- az,
322
- id,
323
- group,
324
- "#{ip} #{static_ip}",
325
- flavor,
326
- state
327
- )
328
- end
174
+ v
329
175
  end
330
176
  end
331
177
  end
332
178
 
333
- def print_counts(servers_sorted, opts_csv)
334
- flavor_data = {}
335
- connect.flavors.each do |f|
336
- flavor_data[f.id] = {
337
- 'cores' => f.cores,
338
- 'disk' => f.disk,
339
- 'ram' => f.ram
340
- }
341
- end
342
- total_cores = 0
343
- total_disk = 0.0
344
- total_ram = 0.0
345
-
346
- servers_sorted.each do |s|
347
- total_cores += flavor_data[s[:flavor]]['cores']
348
- total_disk += flavor_data[s[:flavor]]['disk'].to_f
349
- total_ram += flavor_data[s[:flavor]]['ram'].to_f
350
- end
351
-
352
- if opts_csv
353
- puts "total_instances,#{servers_sorted.count}"
354
- puts "total_cores,#{total_cores}"
355
- puts "total_disk,#{total_disk}"
356
- puts "total_ram,#{total_ram}"
357
- else
358
- puts "\nTotals"
359
- puts "\tInstances: #{servers_sorted.count}"
360
- puts "\tCores: #{total_cores}"
361
- printf("\tInstance storage: %.2f TB\n", total_disk / 1024)
362
- printf("\tRAM: %.2f GB\n", total_ram / 1024)
363
- end
364
- end
365
-
366
- def list_instances(opts)
367
- servers = get_servers(opts)
368
- servers_sorted = sort_servers(servers, opts[:sort])
369
- longest_server_name = get_longest_server_name(servers_sorted)
370
- print_headers(opts[:csv], servers_sorted, longest_server_name, opts[:dates])
371
- print_machine_list(
372
- servers_sorted,
373
- opts[:nocolor],
374
- opts[:csv],
375
- longest_server_name,
376
- opts[:dates]
377
- )
179
+ def opts
180
+ @opts ||= begin
181
+ opts = Optimist.options do
182
+ opt :sort, 'Sort order', short: 's', default: 'tag_Name'
183
+ opt :tags, 'Tags to display', short: 'T', default: %w[Name]
184
+ opt :groups, 'Display Security Groups', default: false
185
+ opt :state, 'State', short: 'S', type: String
186
+ opt :type, 'Type', short: 'F', type: String
187
+ opt :zone, 'Availability Zone', short: 'Z', type: String
188
+ opt :csv, 'Output in CSV Format', short: 'C', default: false
189
+ opt :dates, 'Show creation timestamp', short: 'd', default: false
190
+ opt :terminated, 'Show terminated instances', short: 't', default: false
191
+ opt :nocolor, 'No color', short: 'c'
192
+ opt :vpc, 'Show VPC', default: true
193
+ opt :all_groups, 'Display full groups lists', default: false
194
+ end
378
195
 
379
- print_counts(servers_sorted, opts[:csv])
380
- end
196
+ opts[:search] = ARGV[0] unless ARGV.empty?
381
197
 
382
- def parse_opts
383
- opts = Optimist.options do
384
- opt :sort, 'Sort order', short: 's', type: String
385
- opt :state, 'State', short: 'S', type: String
386
- opt :flavor, 'Flavor', short: 'F', type: String
387
- opt :role, 'Role', short: 'r', type: String
388
- opt :zone, 'Availability Zone', short: 'Z', type: String
389
- opt :csv, 'Output in CSV Format', short: 'C', default: false
390
- opt :dates, 'Show creation timestamp', short: 'd', default: false
391
- opt :terminated, 'Show terminated instances', short: 't', default: false
392
- opt :nocolor, 'No color', short: 'c'
198
+ opts
393
199
  end
394
-
395
- opts[:search] = ARGV[0] unless ARGV[0].empty?
396
-
397
- opts
398
- end
399
-
400
- def initialize
401
- list_instances(parse_opts)
402
200
  rescue Interrupt
403
201
  puts 'Interrupted by user (SIGINT, Ctrl+C, etc.)'
404
202
  end