awsutils 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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