aws-eni 0.2.1 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe0e076295a17d9cc4c63a7227f83be289fcb4b3
4
- data.tar.gz: 51dfdb49ec4bba211e5105f3eafed74fc210cdcc
3
+ metadata.gz: 31f890a936163bc23d0c7bec943936be043fda58
4
+ data.tar.gz: 4e5d5ac127109e761af68f26ce33caff51a00121
5
5
  SHA512:
6
- metadata.gz: aa9fd08058dfda15e1c473108105d37e6aaf1c64a322fe249a1c5f469b494399895395f2df2c18d12df7a1d5cbac8811393693e48d032d5824c0b70fc6880b72
7
- data.tar.gz: 7da3b4bd53b7944f744e02c76bc9dc7a691cde2b76ecb708dba83006045e21a3dd30e4c8f35cc0f149762079e4d94f4a9c732e38c2bfb4e54a0fe7c886ccdfae
6
+ metadata.gz: f71962db0b6c220aef90fbc6cf3c4c57d2100b90177e7a34f2a33fd78f5c67bd262c2b373269f70299c325d0a5aef1891f5a79cf0a222580ca720290957f69ea
7
+ data.tar.gz: 62990ff085b4609f33745d40571ec86ba3ef1b00cc568a84ba8eea2c0b5f3534d90a7960a96d673f6b77b73c0ab38a22aff9fbeb9e56cb1d58467833d0f46d2f
data/bin/aws-eni CHANGED
@@ -25,7 +25,7 @@ switch [:V,:verbose], negatable: false
25
25
 
26
26
  pre do |opt|
27
27
  Aws::ENI.timeout 90
28
- Aws::ENI::IFconfig.verbose = opt[:verbose]
28
+ Aws::ENI.verbose opt[:verbose]
29
29
  true
30
30
  end
31
31
 
@@ -164,8 +164,8 @@ command [:enable,:up] do |c|
164
164
  c.action do |global,opts,args|
165
165
  help_now! "Incorrect number of arguments" unless args.count.between?(0,1)
166
166
  args.delete('all')
167
- Aws::ENI::IFconfig.filter(args.first).each(&:enable)
168
- puts "interfaces enabled"
167
+ enabled = Aws::ENI::enable(args.first)
168
+ puts "#{enabled} interfaces enabled"
169
169
  end
170
170
  end
171
171
 
@@ -183,14 +183,8 @@ command [:disable,:down] do |c|
183
183
  c.action do |global,opts,args|
184
184
  help_now! "Incorrect number of arguments" unless args.count.between?(0,1)
185
185
  args.delete('all')
186
- Aws::ENI::IFconfig.filter(args.first).each do |dev|
187
- if dev.name == 'eth0'
188
- warn 'skipping eth0'
189
- else
190
- dev.disable
191
- puts "interface #{dev.name} disabled"
192
- end
193
- end
186
+ disabled = Aws::ENI::disable(args.first)
187
+ puts "#{disabled} interfaces disabled"
194
188
  end
195
189
  end
196
190
 
@@ -250,12 +244,21 @@ command [:attach] do |c|
250
244
  else
251
245
  args.delete('new')
252
246
  params = parse_args args, :subnet_id, :security_groups, :primary_ip
253
- Aws::ENI.assert_ifconfig_access if config
247
+ Aws::ENI.assert_interface_access if config
254
248
  interface = Aws::ENI.create_interface(params)
255
249
  puts "interface #{interface[:interface_id]} created on #{interface[:subnet_id]}"
256
250
  id = interface[:interface_id]
251
+ new_interface = true
252
+ end
253
+ begin
254
+ device = Aws::ENI.attach_interface(id, enable: config, configure: config, block: opts[:block])
255
+ rescue Aws::ENI::Errors::ClientOperationError => e
256
+ warn e.message
257
+ if new_interface && Aws::ENI.clean_interfaces(id, safe_mode: false)
258
+ puts "interface #{id} deleted"
259
+ end
260
+ exit_now! "attachment failed"
257
261
  end
258
- device = Aws::ENI.attach_interface(id, enable: config, configure: config, block: opts[:block])
259
262
  puts "interface #{device[:interface_id]} attached to #{device[:device_name]}"
260
263
  puts "device #{device[:device_name]} enabled and configured" if config
261
264
  end
@@ -295,7 +298,7 @@ command [:detach] do |c|
295
298
  device[:public_ips].each do |address|
296
299
  if device[:released]
297
300
  puts "EIP #{address[:public_ip]} (#{address[:allocation_id]}) dissociated and released"
298
- else
301
+ elsif device[:deleted]
299
302
  puts "EIP #{address[:public_ip]} (#{address[:allocation_id]}) dissociated"
300
303
  end
301
304
  end
@@ -366,7 +369,7 @@ command [:assign] do |c|
366
369
  help_now! "Missing argument" if device.nil?
367
370
 
368
371
  params.merge! configure: !opts[:noconfig], block: opts[:block]
369
- Aws::ENI.assert_ifconfig_access if params[:configure]
372
+ Aws::ENI.assert_interface_access if params[:configure]
370
373
 
371
374
  assignment = Aws::ENI.assign_secondary_ip(device, params)
372
375
  puts "IP #{assignment[:private_ip]} assigned to #{assignment[:device_name]} (#{assignment[:interface_id]})"
@@ -498,8 +501,8 @@ command [:test] do |c|
498
501
  c.action do |global,opts,args|
499
502
  help_now! "Too many arguments" if args.count > 1
500
503
 
501
- print 'IFconfig permissions test... '
502
- if Aws::ENI.can_modify_ifconfig?
504
+ print 'Interface permissions test... '
505
+ if Aws::ENI.has_interface_access?
503
506
  puts 'success!'
504
507
  else
505
508
  puts 'failed'
@@ -507,7 +510,7 @@ command [:test] do |c|
507
510
  end
508
511
 
509
512
  print 'AWS EC2 permissions test... '
510
- if Aws::ENI.can_access_ec2?
513
+ if Aws::ENI.has_client_access?
511
514
  puts 'success!'
512
515
  else
513
516
  puts 'failed'
@@ -519,10 +522,14 @@ end
519
522
 
520
523
  # error handling
521
524
 
522
- on_error do |exception|
523
- if Aws::ENI::PermissionError === exception
524
- warn 'error: This action requires super-user privileges (try sudo)'
525
- false
525
+ on_error do |error|
526
+ case error
527
+ when Aws::ENI::Errors::InterfacePermissionError
528
+ warn 'Error: This action requires super-user privileges (try sudo)'
529
+ when Aws::ENI::Errors::ClientPermissionError
530
+ warn 'Error: Insufficient EC2 operation privileges (check AWS IAM policy)'
531
+ when Aws::ENI::Errors::ServiceError
532
+ warn "Error: #{error.message}"
526
533
  else
527
534
  true
528
535
  end
data/lib/aws-eni.rb CHANGED
@@ -1,30 +1,29 @@
1
1
  require 'time'
2
- require 'aws-sdk'
3
2
  require 'aws-eni/version'
4
3
  require 'aws-eni/errors'
5
4
  require 'aws-eni/meta'
6
- require 'aws-eni/ifconfig'
5
+ require 'aws-eni/client'
6
+ require 'aws-eni/interface'
7
7
 
8
8
  module Aws
9
9
  module ENI
10
10
  extend self
11
11
 
12
12
  def environment
13
- @environment ||= {}.tap do |e|
14
- hwaddr = IFconfig['eth0'].hwaddr
15
- Meta.open_connection do |conn|
16
- e[:instance_id] = Meta.http_get(conn, 'instance-id')
17
- e[:availability_zone] = Meta.http_get(conn, 'placement/availability-zone')
18
- e[:region] = e[:availability_zone].sub(/(.*)[a-z]/,'\1')
19
- e[:vpc_id] = Meta.http_get(conn, "network/interfaces/macs/#{hwaddr}/vpc-id")
20
- e[:vpc_cidr] = Meta.http_get(conn, "network/interfaces/macs/#{hwaddr}/vpc-ipv4-cidr-block")
21
- end
22
- unless e[:vpc_id]
23
- raise EnvironmentError, "Unable to detect VPC settings, library incompatible with EC2-Classic"
24
- end
25
- end.freeze
26
- rescue ConnectionFailed
27
- raise EnvironmentError, "Unable to load EC2 meta-data"
13
+ @environment ||= Meta.connection do
14
+ hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
15
+ {
16
+ instance_id: Meta.instance('instance-id'),
17
+ availability_zone: Meta.instance('placement/availability-zone'),
18
+ region: Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1'),
19
+ vpc_id: Meta.interface(hwaddr, 'vpc-id'),
20
+ vpc_cidr: Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
21
+ }.freeze
22
+ end
23
+ rescue Errors::MetaNotFound
24
+ raise Errors::EnvironmentError, 'Unable to detect VPC, library incompatible with EC2-Classic'
25
+ rescue Errors::MetaConnectionFailed
26
+ raise Errors::EnvironmentError, 'Unable to load EC2 meta-data'
28
27
  end
29
28
 
30
29
  def owner_tag(new_owner = nil)
@@ -37,40 +36,54 @@ module Aws
37
36
  @timeout ||= 30
38
37
  end
39
38
 
40
- def client
41
- @client ||= Aws::EC2::Client.new(region: environment[:region])
39
+ def verbose(set_verbose = nil)
40
+ unless set_verbose.nil?
41
+ @verbose = !!set_verbose
42
+ Interface.verbose = @verbose
43
+ end
44
+ @verbose
42
45
  end
43
46
 
44
47
  # return our internal model of this instance's network configuration on AWS
45
48
  def list(filter = nil)
46
- IFconfig.filter(filter).map(&:to_h) if environment
49
+ Interface.filter(filter).map(&:to_h) if environment
47
50
  end
48
51
 
49
52
  # sync local machine's network interface config with the EC2 meta-data
50
53
  # pass dry_run option to check whether configuration is out of sync without
51
54
  # modifying it
52
55
  def configure(filter = nil, options = {})
53
- IFconfig.configure(filter, options) if environment
56
+ Interface.configure(filter, options) if environment
54
57
  end
55
58
 
56
59
  # clear local machine's network interface config
57
60
  def deconfigure(filter = nil)
58
- IFconfig.deconfigure(filter) if environment
61
+ Interface.deconfigure(filter) if environment
62
+ end
63
+
64
+ # enable one or more network interfaces
65
+ def enable(filter = nil)
66
+ Interface.filter(filter).select{ |dev| !dev.enabled? }.each(&:enable).count
67
+ end
68
+
69
+ # disable one or more network interfaces
70
+ def disable(filter = nil)
71
+ Interface.filter(filter).select{ |dev| dev.enabled? && dev.name != 'eth0' }.each(&:disable).count
59
72
  end
60
73
 
61
74
  # create network interface
62
75
  def create_interface(options = {})
63
76
  timestamp = Time.now.xmlschema
64
77
  params = {}
65
- params[:subnet_id] = options[:subnet_id] || IFconfig.first.subnet_id
78
+ params[:subnet_id] = options[:subnet_id] || Interface.first.subnet_id
66
79
  params[:private_ip_address] = options[:primary_ip] if options[:primary_ip]
67
80
  params[:groups] = [*options[:security_groups]] if options[:security_groups]
68
81
  params[:description] = "generated by #{owner_tag} from #{environment[:instance_id]} on #{timestamp}"
69
82
 
70
- response = client.create_network_interface(params)
71
- wait_for 'the interface to be created', rescue: Aws::EC2::Errors::ServiceError do
72
- if interface_status(response[:network_interface][:network_interface_id]) == 'available'
73
- client.create_tags(resources: [response[:network_interface][:network_interface_id]], tags: [
83
+ interface = Client.create_network_interface(params)[:network_interface]
84
+ wait_for 'the interface to be created', rescue: Errors::UnknownInterface do
85
+ if Client.describe_interface(interface[:network_interface_id])
86
+ Client.create_tags(resources: [interface[:network_interface_id]], tags: [
74
87
  { key: 'created by', value: owner_tag },
75
88
  { key: 'created on', value: timestamp },
76
89
  { key: 'created from', value: environment[:instance_id] }
@@ -78,9 +91,9 @@ module Aws
78
91
  end
79
92
  end
80
93
  {
81
- interface_id: response[:network_interface][:network_interface_id],
82
- subnet_id: response[:network_interface][:subnet_id],
83
- api_response: response[:network_interface]
94
+ interface_id: interface[:network_interface_id],
95
+ subnet_id: interface[:subnet_id],
96
+ api_response: interface
84
97
  }
85
98
  end
86
99
 
@@ -88,19 +101,23 @@ module Aws
88
101
  def attach_interface(id, options = {})
89
102
  do_enable = true unless options[:enable] == false
90
103
  do_config = true unless options[:configure] == false
91
- assert_ifconfig_access if do_config || do_enable
104
+ assert_interface_access if do_config || do_enable
92
105
 
93
- device = IFconfig[options[:device_number] || options[:name]].assert(exists: false)
106
+ device = Interface[options[:device_number] || options[:name]].assert(exists: false)
94
107
 
95
- response = client.attach_network_interface(
96
- network_interface_id: id,
97
- instance_id: environment[:instance_id],
98
- device_index: device.device_number
99
- )
108
+ begin
109
+ response = Client.attach_network_interface(
110
+ network_interface_id: id,
111
+ instance_id: environment[:instance_id],
112
+ device_index: device.device_number
113
+ )
114
+ rescue EC2::Errors::AttachmentLimitExceeded
115
+ raise Errors::ClientOperationError, "Unable to attach #{id} to #{device.name} (attachment limit exceeded)"
116
+ end
100
117
 
101
118
  if options[:block] || do_config || do_enable
102
- wait_for 'the interface to attach', rescue: ConnectionFailed do
103
- device.exists? && interface_status(device.interface_id) == 'in-use'
119
+ wait_for 'the interface to attach', rescue: Errors::MetaNotFound do
120
+ device.exists? && Client.interface_attached(device.interface_id)
104
121
  end
105
122
  end
106
123
  device.configure if do_config
@@ -117,30 +134,25 @@ module Aws
117
134
 
118
135
  # detach network interface
119
136
  def detach_interface(id, options = {})
120
- device = IFconfig[id].assert(
137
+ device = Interface[id].assert(
121
138
  exists: true,
122
139
  device_name: options[:device_name],
123
140
  interface_id: options[:interface_id],
124
141
  device_number: options[:device_number]
125
142
  )
143
+ interface = Client.describe_interface(device.interface_id)
144
+
126
145
  if device.name == 'eth0'
127
- raise InvalidParameterError, "For safety, interface eth0 cannot be detached."
146
+ raise Errors::InvalidInterface, 'For safety, interface eth0 cannot be detached.'
128
147
  end
129
- interface_id = device.interface_id
130
148
 
131
- response = client.describe_network_interfaces(filters: [{
132
- name: 'attachment.instance-id',
133
- values: [environment[:instance_id]]
134
- },{
135
- name: 'network-interface-id',
136
- values: [interface_id]
137
- }])
138
- interface = response[:network_interfaces].first
139
- raise UnknownInterfaceError, "Interface attachment could not be located" unless interface
149
+ unless interface[:attachment] && interface[:attachment][:instance_id] == environment[:instance_id]
150
+ raise Errors::UnknownInterface, "Interface #{interface_id} is not attached to this machine"
151
+ end
140
152
 
141
153
  device.disable
142
154
  device.deconfigure
143
- client.detach_network_interface(
155
+ Client.detach_network_interface(
144
156
  attachment_id: interface[:attachment][:attachment_id],
145
157
  force: true
146
158
  )
@@ -161,12 +173,12 @@ module Aws
161
173
 
162
174
  if options[:block] || do_delete
163
175
  wait_for 'the interface to detach', interval: 0.3 do
164
- !device.exists? && interface_status(interface[:network_interface_id]) == 'available'
176
+ !device.exists? && !Client.interface_attached(interface[:network_interface_id])
165
177
  end
166
178
  end
167
- client.delete_network_interface(network_interface_id: interface[:network_interface_id]) if do_delete
179
+ Client.delete_network_interface(network_interface_id: interface[:network_interface_id]) if do_delete
168
180
  {
169
- interface_id: interface_id,
181
+ interface_id: interface[:network_interface_id],
170
182
  device_name: device.name,
171
183
  device_number: device.device_number,
172
184
  created_by_us: created_by_us,
@@ -196,14 +208,14 @@ module Aws
196
208
  when /^#{environment[:region]}[a-z]$/
197
209
  filters << { name: 'availability-zone', values: [filter] }
198
210
  else
199
- raise InvalidParameterError, "Unknown resource filter: #{filter}"
211
+ raise Errors::InvalidInput, "Unknown interface attribute: #{filter}"
200
212
  end
201
213
  end
202
214
  if safe_mode
203
215
  filters << { name: 'tag:created by', values: [owner_tag] }
204
216
  end
205
217
 
206
- descriptions = client.describe_network_interfaces(filters: filters)
218
+ descriptions = Client.describe_network_interfaces(filters: filters)
207
219
  interfaces = descriptions[:network_interfaces].select do |interface|
208
220
  created_recently = interface.tag_set.any? do |tag|
209
221
  begin
@@ -221,7 +233,7 @@ module Aws
221
233
  dissociate_elastic_ip(assoc[:allocation_id], release: true) if do_release
222
234
  end
223
235
  end
224
- client.delete_network_interface(network_interface_id: interface[:network_interface_id])
236
+ Client.delete_network_interface(network_interface_id: interface[:network_interface_id])
225
237
  true
226
238
  end
227
239
  end
@@ -235,37 +247,37 @@ module Aws
235
247
 
236
248
  # add new private ip using the AWS api and add it to our local ip config
237
249
  def assign_secondary_ip(id, options = {})
238
- device = IFconfig[id].assert(
250
+ device = Interface[id].assert(
239
251
  exists: true,
240
252
  device_name: options[:device_name],
241
253
  interface_id: options[:interface_id],
242
254
  device_number: options[:device_number]
243
255
  )
244
256
  interface_id = device.interface_id
245
- current_ips = interface_ips(interface_id)
257
+ current_ips = Client.interface_private_ips(interface_id)
246
258
  new_ip = options[:private_ip]
247
259
 
248
260
  if new_ip
249
261
  if current_ips.include?(new_ip)
250
- raise InvalidParameterError, "IP #{new_ip} already assigned to #{device.name}"
262
+ raise Errors::ClientOperationError, "IP #{new_ip} already assigned to #{device.name}"
251
263
  end
252
- client.assign_private_ip_addresses(
264
+ Client.assign_private_ip_addresses(
253
265
  network_interface_id: interface_id,
254
266
  private_ip_addresses: [new_ip],
255
267
  allow_reassignment: false
256
268
  )
257
269
  wait_for 'private ip address to be assigned' do
258
- interface_ips(interface_id).include?(new_ip) ||
270
+ Client.interface_private_ips(interface_id).include?(new_ip) ||
259
271
  device.local_ips.include?(new_ip)
260
272
  end
261
273
  else
262
- client.assign_private_ip_addresses(
274
+ Client.assign_private_ip_addresses(
263
275
  network_interface_id: interface_id,
264
276
  secondary_private_ip_address_count: 1,
265
277
  allow_reassignment: false
266
278
  )
267
279
  wait_for 'new private ip address to be assigned' do
268
- new_ips = interface_ips(interface_id) - current_ips
280
+ new_ips = Client.interface_private_ips(interface_id) - current_ips
269
281
  new_ips = device.local_ips - current_ips if new_ips.empty?
270
282
  new_ip = new_ips.first if new_ips
271
283
  end
@@ -273,8 +285,8 @@ module Aws
273
285
 
274
286
  unless options[:configure] == false
275
287
  device.add_alias(new_ip)
276
- if options[:block] && !IFconfig.test(new_ip, target: device.gateway)
277
- raise TimeoutError, "Timed out waiting for ip address to become active"
288
+ if options[:block] && !Interface.test(new_ip, target: device.gateway)
289
+ raise Errors::TimeoutError, 'Timed out waiting for IP address to become active'
278
290
  end
279
291
  end
280
292
  {
@@ -291,22 +303,20 @@ module Aws
291
303
  do_release = !!options[:release]
292
304
 
293
305
  find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
294
- device = IFconfig[find].assert(
306
+ device = Interface[find].assert(
295
307
  exists: true,
296
308
  device_name: options[:device_name],
297
309
  interface_id: options[:interface_id],
298
310
  device_number: options[:device_number]
299
311
  )
300
312
 
301
- resp = client.describe_network_interfaces(network_interface_ids: [device.interface_id])
302
- interface = resp[:network_interfaces].first
303
- raise UnknownInterfaceError, "Interface attachment could not be located" unless interface
313
+ interface = Client.describe_interface(device.interface_id)
304
314
 
305
315
  unless addr_info = interface[:private_ip_addresses].find { |addr| addr[:private_ip_address] == private_ip }
306
- raise InvalidParameterError, "IP #{private_ip} not found on #{device.name}"
316
+ raise Errors::ClientOperationError, "IP #{private_ip} not found on #{device.name}"
307
317
  end
308
318
  if addr_info[:primary]
309
- raise InvalidParameterError, "The primary IP address of an interface cannot be unassigned"
319
+ raise Errors::ClientOperationError, 'The primary IP address of an interface cannot be unassigned'
310
320
  end
311
321
 
312
322
  if assoc = addr_info[:association]
@@ -314,7 +324,7 @@ module Aws
314
324
  end
315
325
 
316
326
  device.remove_alias(private_ip)
317
- client.unassign_private_ip_addresses(
327
+ Client.unassign_private_ip_addresses(
318
328
  network_interface_id: interface[:network_interface_id],
319
329
  private_ip_addresses: [private_ip]
320
330
  )
@@ -331,10 +341,10 @@ module Aws
331
341
 
332
342
  # associate a private ip with an elastic ip through the AWS api
333
343
  def associate_elastic_ip(private_ip, options = {})
334
- raise MissingParameterError, "You must specify a private ip address" unless private_ip
344
+ raise Errors::MissingInput, 'You must specify a private IP address' unless private_ip
335
345
 
336
346
  find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
337
- device = IFconfig[find].assert(
347
+ device = Interface[find].assert(
338
348
  exists: true,
339
349
  private_ip: private_ip,
340
350
  device_name: options[:device_name],
@@ -344,27 +354,27 @@ module Aws
344
354
  options[:public_ip] ||= options[:allocation_id]
345
355
 
346
356
  if public_ip = device.public_ips[private_ip]
347
- raise InvalidParameterError, "IP #{private_ip} already has an associated EIP (#{public_ip})"
357
+ raise Errors::ClientOperationError, "IP #{private_ip} already has an associated EIP (#{public_ip})"
348
358
  end
349
359
 
350
360
  if options[:public_ip]
351
- eip = describe_address(options[:public_ip])
361
+ eip = Client.describe_address(options[:public_ip])
352
362
  if options[:allocation_id] && eip[:allocation_id] != options[:allocation_id]
353
- raise InvalidParameterError, "EIP #{eip[:public_ip]} (#{eip[:allocation_id]}) does not match #{options[:allocation_id]}"
363
+ raise Errors::InvalidAddress, "EIP #{eip[:public_ip]} (#{eip[:allocation_id]}) does not match #{options[:allocation_id]}"
354
364
  end
355
365
  else
356
366
  eip = allocate_elastic_ip
357
367
  end
358
368
 
359
- resp = client.associate_address(
369
+ resp = Client.associate_address(
360
370
  network_interface_id: device.interface_id,
361
371
  allocation_id: eip[:allocation_id],
362
372
  private_ip_address: private_ip,
363
373
  allow_reassociation: false
364
374
  )
365
375
 
366
- if options[:block] && !IFconfig.test(private_ip)
367
- raise TimeoutError, "Timed out waiting for ip address to become active"
376
+ if options[:block] && !Interface.test(private_ip)
377
+ raise Errors::TimeoutError, 'Timed out waiting for ip address to become active'
368
378
  end
369
379
  {
370
380
  private_ip: private_ip,
@@ -383,7 +393,7 @@ module Aws
383
393
 
384
394
  # assert device attributes if we've specified a device
385
395
  if find = options[:device_name] || options[:device_number]
386
- device = IFconfig[find].assert(
396
+ device = Interface[find].assert(
387
397
  device_name: options[:device_name],
388
398
  device_number: options[:device_number],
389
399
  interface_id: options[:interface_id],
@@ -393,36 +403,36 @@ module Aws
393
403
  end
394
404
 
395
405
  # get our address info
396
- eip = describe_address(address)
397
- device ||= IFconfig.find { |dev| dev.interface_id == eip[:network_interface_id] }
406
+ eip = Client.describe_address(address)
407
+ device ||= Interface.find { |dev| dev.interface_id == eip[:network_interface_id] }
398
408
 
399
409
  # assert eip attributes if options provided
400
410
  if options[:private_ip] && eip[:private_ip_address] != options[:private_ip]
401
- raise InvalidParameterError, "#{address} is not associated with IP #{options[:private_ip]}"
411
+ raise Errors::InvalidAddress, "#{address} is not associated with IP #{options[:private_ip]}"
402
412
  end
403
413
  if options[:public_ip] && eip[:public_ip] != options[:public_ip]
404
- raise InvalidParameterError, "#{address} is not associated with public IP #{options[:public_ip]}"
414
+ raise Errors::InvalidAddress, "#{address} is not associated with public IP #{options[:public_ip]}"
405
415
  end
406
416
  if options[:allocation_id] && eip[:allocation_id] != options[:allocation_id]
407
- raise InvalidParameterError, "#{address} is not associated with allocation ID #{options[:allocation_id]}"
417
+ raise Errors::InvalidAddress, "#{address} is not associated with allocation ID #{options[:allocation_id]}"
408
418
  end
409
419
  if options[:association_id] && eip[:association_id] != options[:association_id]
410
- raise InvalidParameterError, "#{address} is not associated with association ID #{options[:association_id]}"
420
+ raise Errors::InvalidAddress, "#{address} is not associated with association ID #{options[:association_id]}"
411
421
  end
412
422
  if options[:interface_id] && eip[:network_interface_id] != options[:interface_id]
413
- raise InvalidParameterError, "#{address} is not associated with interface ID #{options[:interface_id]}"
423
+ raise Errors::InvalidAddress, "#{address} is not associated with interface ID #{options[:interface_id]}"
414
424
  end
415
425
 
416
426
  if device
417
427
  if device.name == 'eth0' && device.local_ips.first == eip[:private_ip_address]
418
- raise InvalidParameterError, "For safety, a public address cannot be dissociated from the primary IP on eth0"
428
+ raise Errors::ClientOperationError, 'For safety, a public address cannot be dissociated from the primary IP on eth0'
419
429
  end
420
- elsif interface_status(eip[:network_interface_id]) != 'available'
421
- raise InvalidParameterError, "#{address} is associated with an interface attached to another machine"
430
+ elsif Client.interface_attached(eip[:network_interface_id])
431
+ raise Errors::ClientOperationError, "#{address} is associated with an interface attached to another machine"
422
432
  end
423
433
 
424
- client.disassociate_address(association_id: eip[:association_id])
425
- client.release_address(allocation_id: eip[:allocation_id]) if do_release
434
+ Client.disassociate_address(association_id: eip[:association_id])
435
+ Client.release_address(allocation_id: eip[:allocation_id]) if do_release
426
436
  {
427
437
  private_ip: eip[:private_ip_address],
428
438
  device_name: device && device.name,
@@ -436,7 +446,7 @@ module Aws
436
446
 
437
447
  # allocate a new elastic ip address
438
448
  def allocate_elastic_ip
439
- eip = client.allocate_address(domain: 'vpc')
449
+ eip = Client.allocate_address(domain: 'vpc')
440
450
  {
441
451
  public_ip: eip[:public_ip],
442
452
  allocation_id: eip[:allocation_id]
@@ -445,127 +455,41 @@ module Aws
445
455
 
446
456
  # release the specified elastic ip address
447
457
  def release_elastic_ip(ip)
448
- eip = describe_address(ip)
458
+ eip = Client.describe_address(ip)
449
459
  if eip[:association_id]
450
- raise AWSPermissionError, "Elastic IP #{eip[:public_ip]} (#{eip[:allocation_id]}) is currently in use"
460
+ raise Errors::ClientOperationError, "Elastic IP #{eip[:public_ip]} (#{eip[:allocation_id]}) is currently in use"
451
461
  end
452
- client.release_address(allocation_id: eip[:allocation_id])
462
+ Client.release_address(allocation_id: eip[:allocation_id])
453
463
  {
454
464
  public_ip: eip[:public_ip],
455
465
  allocation_id: eip[:allocation_id]
456
466
  }
457
467
  end
458
468
 
459
- # test whether we have permission to modify our local configuration
460
- def can_modify_ifconfig?
461
- IFconfig.mutable?
469
+ # test whether we have permission to modify our machine's interface configuration
470
+ def has_interface_access?
471
+ Interface.mutable?
462
472
  end
463
473
 
464
- def assert_ifconfig_access
465
- raise PermissionError, 'Insufficient user priveleges (try sudo)' unless can_modify_ifconfig?
474
+ # throw exception if we cannot modify our machine's interface configuration
475
+ def assert_interface_access
476
+ raise Errors::InterfacePermissionError, 'Insufficient user priveleges to configure network interfaces' unless has_interface_access?
466
477
  end
467
478
 
468
- # test whether we have the appropriate permissions within our AWS access
469
- # credentials to perform all possible API calls
470
- def can_access_ec2?
471
- client = self.client
472
- test_methods = {
473
- describe_network_interfaces: nil,
474
- create_network_interface: {
475
- subnet_id: 'subnet-abcd1234'
476
- },
477
- attach_network_interface: {
478
- network_interface_id: 'eni-abcd1234',
479
- instance_id: 'i-abcd1234',
480
- device_index: 0
481
- },
482
- detach_network_interface: {
483
- attachment_id: 'eni-attach-abcd1234'
484
- },
485
- delete_network_interface: {
486
- network_interface_id: 'eni-abcd1234'
487
- },
488
- create_tags: {
489
- resources: ['eni-abcd1234'],
490
- tags: []
491
- },
492
- describe_addresses: nil,
493
- allocate_address: nil,
494
- release_address: {
495
- dry_run: true,
496
- allocation_id: 'eipalloc-no_exist'
497
- },
498
- associate_address: {
499
- allocation_id: 'eipalloc-no_exist',
500
- network_interface_id: 'eni-abcd1234'
501
- }
502
- # has no dry_run method
503
- # assign_private_ip_addresses: {
504
- # network_interface_id: 'eni-abcd1234'
505
- # }
506
- }
507
- test_methods.each do |method, params|
508
- begin
509
- params ||= {}
510
- params[:dry_run] = true
511
- client.public_send(method, params)
512
- raise Error, "Unexpected behavior while testing AWS API access"
513
- rescue Aws::EC2::Errors::DryRunOperation
514
- # success
515
- rescue Aws::EC2::Errors::InvalidAllocationIDNotFound
516
- # release_address does not properly support dry_run
517
- rescue Aws::EC2::Errors::UnauthorizedOperation
518
- return false
519
- end
520
- end
521
- true
479
+ # test whether we have permission to perform all necessary EC2 operations
480
+ # within our given AWS access credentials
481
+ def has_client_access?
482
+ Client.has_access?
522
483
  end
523
484
 
524
- def assert_ec2_access
525
- raise AWSPermissionError, 'Insufficient AWS API access' unless can_access_ec2?
485
+ # throw exception if we do not have permissions to perform all needed EC2
486
+ # operations with our given AWS credentials
487
+ def assert_client_access
488
+ raise Errors::ClientPermissionError, 'Insufficient access to EC2 operations for network interface modification' unless has_client_access?
526
489
  end
527
490
 
528
491
  private
529
492
 
530
- # use either an ip address or allocation id
531
- def describe_address(address)
532
- filter_by = case address
533
- when /^eipalloc-/
534
- 'allocation-id'
535
- when /^eipassoc-/
536
- 'association-id'
537
- else
538
- if IPAddr.new(environment[:vpc_cidr]) === IPAddr.new(address)
539
- 'private-ip-address'
540
- else
541
- 'public-ip'
542
- end
543
- end
544
- resp = client.describe_addresses(filters: [
545
- { name: 'domain', values: ['vpc'] },
546
- { name: filter_by, values: [address] }
547
- ])
548
- raise InvalidParameterError, "IP #{address} could not be located" if resp[:addresses].empty?
549
- resp[:addresses].first
550
- end
551
-
552
- def interface_ips(id)
553
- resp = client.describe_network_interfaces(network_interface_ids: [id])
554
- interface = resp[:network_interfaces].first
555
- if interface && interface[:private_ip_addresses]
556
- primary = interface[:private_ip_addresses].find { |ip| ip[:primary] }
557
- interface[:private_ip_addresses].map { |ip| ip[:private_ip_address] }.tap do |ips|
558
- # ensure primary ip is first in the list
559
- ips.unshift(*ips.delete(primary[:private_ip_address])) if primary
560
- end
561
- end
562
- end
563
-
564
- def interface_status(id)
565
- resp = client.describe_network_interfaces(network_interface_ids: [id])
566
- resp[:network_interfaces].first[:status] unless resp[:network_interfaces].empty?
567
- end
568
-
569
493
  def wait_for(task, options = {}, &block)
570
494
  errors = [*options[:rescue]]
571
495
  timeout = options[:timeout] || self.timeout
@@ -574,13 +498,12 @@ module Aws
574
498
  until timeout < 0
575
499
  begin
576
500
  break if block.call
577
- rescue Exception => e
578
- raise unless errors.any? { |error| error === e }
501
+ rescue *errors => e
579
502
  end
580
503
  sleep interval
581
504
  timeout -= interval
582
505
  end
583
- raise TimeoutError, "Timed out waiting for #{task}" unless timeout > 0
506
+ raise Errors::TimeoutError, "Timed out waiting for #{task}" unless timeout > 0
584
507
  end
585
508
  end
586
509
  end
@@ -0,0 +1,152 @@
1
+ require 'aws-sdk'
2
+ require 'aws-eni/errors'
3
+ require 'aws-eni/meta'
4
+
5
+ module Aws
6
+ module ENI
7
+ module Client
8
+ extend self
9
+
10
+ # determine the region from instance metadata
11
+ def region
12
+ Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1')
13
+ rescue Errors::MetaConnectionFailed
14
+ raise Errors::EnvironmentError, 'Unable to load EC2 meta-data'
15
+ end
16
+
17
+ # determine the vpc cidr block from instance metadata
18
+ def vpc_cidr
19
+ hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
20
+ Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
21
+ rescue Errors::MetaConnectionFailed, Errors::MetaNotFound
22
+ raise Errors::EnvironmentError, 'Unable to load EC2 meta-data'
23
+ end
24
+
25
+ # lazy-load our ec2 client
26
+ def client
27
+ @client ||= EC2::Client.new(region: region)
28
+ rescue StandardError => e
29
+ raise Errors::EnvironmentError, 'Unable to initialize EC2 client'
30
+ end
31
+
32
+ # pass along method calls to our lazy-loaded api client
33
+ def method_missing(method, *args)
34
+ client.public_send(method, *args)
35
+ rescue EC2::Errors::UnauthorizedOperation => e
36
+ raise Errors::ClientPermissionError, "Operation not permitted: #{e.message}"
37
+ rescue EC2::Errors::ServiceError => e
38
+ error = e.class.to_s.gsub(/^.*::/, '')
39
+ raise Errors::ClientOperationError, "EC2 service error (#{error}: #{e.message})"
40
+ end
41
+
42
+ # retrieve a single interface resource
43
+ def describe_interface(id)
44
+ resp = describe_network_interfaces(network_interface_ids: [id])
45
+ raise Errors::UnknownInterface, "Interface #{id} could not be located" if resp[:network_interfaces].empty?
46
+ resp[:network_interfaces].first
47
+ end
48
+
49
+ # retrieve a single address resource by public ip, associated private ip,
50
+ # allocation id, or association id
51
+ def describe_address(address)
52
+ filter_by = case address
53
+ when /^eipalloc-/
54
+ 'allocation-id'
55
+ when /^eipassoc-/
56
+ 'association-id'
57
+ else
58
+ if IPAddr.new(vpc_cidr) === IPAddr.new(address)
59
+ 'private-ip-address'
60
+ else
61
+ 'public-ip'
62
+ end
63
+ end
64
+ resp = describe_addresses(filters: [
65
+ { name: 'domain', values: ['vpc'] },
66
+ { name: filter_by, values: [address] }
67
+ ])
68
+ raise Errors::UnknownAddress, "IP #{address} could not be located" if resp[:addresses].empty?
69
+ resp[:addresses].first
70
+ end
71
+
72
+ # retrieve an array of private ips associated with the given interface
73
+ def interface_private_ips(id)
74
+ interface = describe_interface(id)
75
+ if interface[:private_ip_addresses]
76
+ primary = interface[:private_ip_addresses].find { |ip| ip[:primary] }
77
+ interface[:private_ip_addresses].map { |ip| ip[:private_ip_address] }.tap do |ips|
78
+ # ensure primary ip is first in the list
79
+ ips.unshift(*ips.delete(primary[:private_ip_address])) if primary
80
+ end
81
+ end
82
+ end
83
+
84
+ # determine whether a given interface is attached or free
85
+ def interface_attached(id)
86
+ describe_interface(id)[:status] == 'in-use'
87
+ end
88
+
89
+ # test whether we have the appropriate permissions within our AWS access
90
+ # credentials to perform all possible API calls
91
+ def has_access?
92
+ test_methods = {
93
+ describe_network_interfaces: {},
94
+ create_network_interface: {
95
+ subnet_id: 'subnet-abcd1234'
96
+ },
97
+ attach_network_interface: {
98
+ network_interface_id: 'eni-abcd1234',
99
+ instance_id: 'i-abcd1234',
100
+ device_index: 0
101
+ },
102
+ detach_network_interface: {
103
+ attachment_id: 'eni-attach-abcd1234'
104
+ },
105
+ delete_network_interface: {
106
+ network_interface_id: 'eni-abcd1234'
107
+ },
108
+ create_tags: {
109
+ resources: ['eni-abcd1234'],
110
+ tags: []
111
+ },
112
+ describe_addresses: {},
113
+ allocate_address: {},
114
+ release_address: {
115
+ allocation_id: 'eipalloc-abcd1234'
116
+ },
117
+ associate_address: {
118
+ allocation_id: 'eipalloc-abcd1234',
119
+ network_interface_id: 'eni-abcd1234'
120
+ },
121
+ disassociate_address: {
122
+ association_id: 'eipassoc-abcd1234'
123
+ }
124
+ # these have no dry_run method
125
+ # assign_private_ip_addresses: {
126
+ # network_interface_id: 'eni-abcd1234'
127
+ # }
128
+ # unassign_private_ip_addresses: {
129
+ # network_interface_id: 'eni-abcd1234',
130
+ # private_ip_addresses: ['0.0.0.0']
131
+ # }
132
+ }
133
+ test_methods.all? do |method, params|
134
+ begin
135
+ client.public_send(method, params.merge(dry_run: true))
136
+ rescue EC2::Errors::DryRunOperation
137
+ true
138
+ rescue EC2::Errors::InvalidAllocationIDNotFound
139
+ # release_address does not properly support dry_run
140
+ true
141
+ rescue EC2::Errors::UnauthorizedOperation
142
+ false
143
+ rescue
144
+ raise Errors::ClientOperationError, 'Unexpected behavior while testing EC2 client permissions'
145
+ else
146
+ raise Errors::ClientOperationError, 'Unexpected behavior while testing EC2 client permissions'
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -1,16 +1,29 @@
1
-
2
1
  module Aws
3
2
  module ENI
4
- class Error < RuntimeError; end
5
- class TimeoutError < Error; end
6
- class MissingParameterError < Error; end
7
- class InvalidParameterError < Error; end
8
- class UnknownInterfaceError < Error; end
9
- class EnvironmentError < Error; end
10
- class CommandError < Error; end
11
- class PermissionError < CommandError; end
12
- class AWSPermissionError < Error; end
13
- class BadResponse < Error; end
14
- class ConnectionFailed < Error; end
3
+ module Errors
4
+ class ServiceError < RuntimeError; end
5
+
6
+ class EnvironmentError < ServiceError; end
7
+
8
+ class MetaNotFound < ServiceError; end
9
+ class MetaBadResponse < ServiceError; end
10
+ class MetaConnectionFailed < ServiceError; end
11
+
12
+ class InterfaceOperationError < ServiceError; end
13
+ class InterfacePermissionError < InterfaceOperationError; end
14
+
15
+ class ClientOperationError < ServiceError; end
16
+ class ClientPermissionError < ClientOperationError; end
17
+
18
+ class MissingInput < ServiceError; end
19
+ class InvalidInput < ServiceError; end
20
+ class TimeoutError < ServiceError; end
21
+
22
+ class UnknownInterface < ServiceError; end
23
+ class InvalidInterface < ServiceError; end
24
+
25
+ class UnknownAddress < ServiceError; end
26
+ class InvalidAddress < ServiceError; end
27
+ end
15
28
  end
16
29
  end
@@ -4,7 +4,7 @@ require 'aws-eni/errors'
4
4
 
5
5
  module Aws
6
6
  module ENI
7
- class IFconfig
7
+ class Interface
8
8
 
9
9
  class << self
10
10
  include Enumerable
@@ -28,7 +28,7 @@ module Aws
28
28
  when /^[0-9\.]+$/
29
29
  find { |dev| dev.has_ip?(index) }
30
30
  end.tap do |dev|
31
- raise UnknownInterfaceError, "No interface found matching #{index}" unless dev
31
+ raise Errors::UnknownInterface, "No interface found matching #{index}" unless dev
32
32
  end
33
33
  end
34
34
 
@@ -76,11 +76,11 @@ module Aws
76
76
  when nil
77
77
  to_a
78
78
  when /^eni-/, /^eth[0-9]+$/, /^[0-9a-f:]+$/i, /^[0-9\.]+$/
79
- self[filter].to_a
79
+ [*self[filter]]
80
80
  when /^subnet-/
81
81
  select { |dev| dev.subnet_id == filter }
82
82
  end.tap do |devs|
83
- raise UnknownInterfaceError, "No interface found matching #{filter}" if devs.nil? || devs.empty?
83
+ raise Errors::UnknownInterface, "No interface found matching #{filter}" if devs.nil? || devs.empty?
84
84
  end
85
85
  end
86
86
 
@@ -88,7 +88,7 @@ module Aws
88
88
  def mutable?
89
89
  cmd('link set dev eth0') # innocuous command
90
90
  true
91
- rescue PermissionError
91
+ rescue Errors::InterfacePermissionError
92
92
  false
93
93
  end
94
94
 
@@ -98,10 +98,10 @@ module Aws
98
98
  options[:errors] = true
99
99
  begin
100
100
  exec("/sbin/ip #{command}", options)
101
- rescue CommandError => e
101
+ rescue Errors::InterfaceOperationError => e
102
102
  case e.message
103
103
  when /operation not permitted/i
104
- raise PermissionError, "Operation not permitted"
104
+ raise Errors::InterfacePermissionError, "Operation not permitted"
105
105
  else
106
106
  raise if errors
107
107
  end
@@ -128,7 +128,7 @@ module Aws
128
128
  else
129
129
  error = e.read
130
130
  warn "Warning: #{error}" if verbose
131
- raise CommandError, error if errors
131
+ raise Errors::InterfaceOperationError, error if errors
132
132
  end
133
133
  end
134
134
  output
@@ -139,7 +139,7 @@ module Aws
139
139
 
140
140
  def initialize(name, auto_config = true)
141
141
  unless name =~ /^eth([0-9]+)$/
142
- raise UnknownInterfaceError, "Invalid interface: #{name}"
142
+ raise Errors::InvalidInterface, "Invalid interface: #{name}"
143
143
  end
144
144
  @name = name
145
145
  @device_number = $1.to_i
@@ -153,7 +153,7 @@ module Aws
153
153
  exists? && IO.read("/sys/class/net/#{name}/address").strip
154
154
  rescue Errno::ENOENT
155
155
  end.tap do |address|
156
- raise UnknownInterfaceError, "Unknown interface: #{name}" unless address
156
+ raise Errors::UnknownInterface, "Interface #{name} not found on this machine" unless address
157
157
  end
158
158
  end
159
159
 
@@ -167,15 +167,14 @@ module Aws
167
167
  # Validate and return basic interface metadata
168
168
  def info
169
169
  hwaddr = self.hwaddr
170
- unless @meta_cache && hwaddr == @meta_cache[:hwaddr]
171
- dev_path = "network/interfaces/macs/#{hwaddr}"
172
- Meta.open_connection do |conn|
173
- raise BadResponse unless Meta.http_get(conn, "#{dev_path}/")
174
- @meta_cache = {
175
- hwaddr: hwaddr,
176
- interface_id: Meta.http_get(conn, "#{dev_path}/interface-id"),
177
- subnet_id: Meta.http_get(conn, "#{dev_path}/subnet-id"),
178
- subnet_cidr: Meta.http_get(conn, "#{dev_path}/subnet-ipv4-cidr-block")
170
+ unless @meta_cache && @meta_cache[:hwaddr] == hwaddr
171
+ @meta_cache = Meta.connection do
172
+ raise MetaBadResponse unless Meta.interface(hwaddr, '')
173
+ {
174
+ hwaddr: hwaddr,
175
+ interface_id: Meta.interface(hwaddr, 'interface-id'),
176
+ subnet_id: Meta.interface(hwaddr, 'subnet-id'),
177
+ subnet_cidr: Meta.interface(hwaddr, 'subnet-ipv4-cidr-block')
179
178
  }.freeze
180
179
  end
181
180
  end
@@ -210,17 +209,15 @@ module Aws
210
209
  end
211
210
 
212
211
  def public_ips
213
- ip_assoc = {}
214
- dev_path = "network/interfaces/macs/#{hwaddr}"
215
- Meta.open_connection do |conn|
216
- # return an array of configured ip addresses (primary + secondary)
217
- Meta.http_get(conn, "#{dev_path}/ipv4-associations/").to_s.each_line do |public_ip|
218
- public_ip.strip!
219
- local_ip = Meta.http_get(conn, "#{dev_path}/ipv4-associations/#{public_ip}")
220
- ip_assoc[local_ip] = public_ip
212
+ hwaddr = self.hwaddr
213
+ Hash[
214
+ Meta.connection do
215
+ Meta.interface(hwaddr, 'ipv4-associations/', not_found: '', cache: false).lines.map do |public_ip|
216
+ public_ip.strip!
217
+ [ Meta.interface(hwaddr, "ipv4-associations/#{public_ip}", cache: false), public_ip ]
218
+ end
221
219
  end
222
- end
223
- ip_assoc
220
+ ]
224
221
  end
225
222
 
226
223
  # Enable our interface and create necessary routes
@@ -245,7 +242,7 @@ module Aws
245
242
  changes = 0
246
243
  prefix = self.prefix # prevent exists? check on each use
247
244
 
248
- meta_ips = Meta.get("network/interfaces/macs/#{hwaddr}/local-ipv4s").lines.map(&:strip)
245
+ meta_ips = Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip)
249
246
  local_primary, *local_aliases = local_ips
250
247
  meta_primary, *meta_aliases = meta_ips
251
248
 
@@ -364,14 +361,14 @@ module Aws
364
361
  when :ip, :has_ip
365
362
  "Interface #{name} does not have IP #{val}" unless has_ip? val
366
363
  when :public_ip
367
- "Interface #{name} does not have private IP #{val}" unless public_ips.include? val
364
+ "Interface #{name} does not have public IP #{val}" unless public_ips.has_value? val
368
365
  when :local_ip, :private_ip
369
366
  "Interface #{name} does not have private IP #{val}" unless local_ips.include? val
370
367
  else
371
368
  "Unknown attribute: #{attr}"
372
369
  end
373
370
  end
374
- raise UnknownInterfaceError, error if error
371
+ raise Errors::UnknownInterface, error if error
375
372
  self
376
373
  end
377
374
 
data/lib/aws-eni/meta.rb CHANGED
@@ -6,42 +6,64 @@ module Aws
6
6
  module ENI
7
7
  module Meta
8
8
 
9
- # EC2 instance meta-data connection settings
10
- HOST = '169.254.169.254'
11
- PORT = '80'
12
- BASE = '/latest/meta-data/'
13
-
14
9
  # These are the errors we trap when attempting to talk to the instance
15
- # metadata service. Any of these imply the service is not present, no
10
+ # metadata service. Any of these imply the service is not present, not
16
11
  # responding or some other non-recoverable error.
17
12
  FAILURES = [
18
13
  Errno::EHOSTUNREACH,
19
14
  Errno::ECONNREFUSED,
20
15
  Errno::EHOSTDOWN,
21
16
  Errno::ENETUNREACH,
22
- SocketError,
23
17
  Timeout::Error,
24
- BadResponse,
18
+ SocketError,
19
+ Errors::MetaBadResponse,
25
20
  ]
26
21
 
27
- # Open connection and run a single GET request on the instance metadata
28
- # endpoint. Same options as open_connection.
22
+ # Perform a GET request on an open HTTP connection to the EC2 instance
23
+ # meta-data and return the body of any 200 response.
29
24
  def self.get(path, options = {})
30
- open_connection options do |conn|
31
- http_get(conn, path)
25
+ @cache ||= {}
26
+ if @cache[path] && options[:cache] != false
27
+ @cache[path]
28
+ else
29
+ connection(options) do |http|
30
+ response = http.request(Net::HTTP::Get.new(path))
31
+ case response.code.to_i
32
+ when 200
33
+ @cache[path] = response.body
34
+ when 404
35
+ raise Errors::MetaNotFound unless options[:not_found]
36
+ options[:not_found]
37
+ else
38
+ raise Errors::MetaBadResponse
39
+ end
40
+ end
32
41
  end
33
42
  end
34
43
 
44
+ # Perform a GET request on the instance metadata and return the body of
45
+ # any 200 response.
46
+ def self.instance(path, options = {})
47
+ get("/latest/meta-data/#{path}", options)
48
+ end
49
+
50
+ # Perform a GET request on the interface metadata and return the body of
51
+ # any 200 response.
52
+ def self.interface(hwaddr, path, options = {})
53
+ instance("network/interfaces/macs/#{hwaddr}/#{path}", options)
54
+ end
55
+
35
56
  # Open a connection and attempt to execute the block `retries` times.
36
57
  # Can specify open and read timeouts in addition to the number of retries.
37
- def self.open_connection(options = {})
58
+ def self.connection(options = {})
59
+ return yield(@open_connection) if @open_connection
38
60
  retries = options[:retries] || 5
39
61
  failed_attempts = 0
40
62
  begin
41
- http = Net::HTTP.new(HOST, PORT, nil)
63
+ http = Net::HTTP.new('169.254.169.254', '80', nil)
42
64
  http.open_timeout = options[:open_timeout] || 5
43
65
  http.read_timeout = options[:read_timeout] || 5
44
- http.start
66
+ @open_connection = http.start
45
67
  yield(http).tap { http.finish }
46
68
  rescue *FAILURES => e
47
69
  if failed_attempts < retries
@@ -50,22 +72,10 @@ module Aws
50
72
  failed_attempts += 1
51
73
  retry
52
74
  else
53
- raise ConnectionFailed, "Connection failed after #{retries} retries."
75
+ raise Errors::MetaConnectionFailed, "EC2 Metadata request failed after #{retries} retries."
54
76
  end
55
- end
56
- end
57
-
58
- # Perform a GET request on an open connection to the instance metadata
59
- # endpoint and return the body of any 200 response.
60
- def self.http_get(connection, path)
61
- response = connection.request(Net::HTTP::Get.new(BASE + path))
62
- case response.code.to_i
63
- when 200
64
- response.body
65
- when 404
66
- nil
67
- else
68
- raise BadResponse
77
+ ensure
78
+ @open_connection = nil
69
79
  end
70
80
  end
71
81
  end
@@ -1,5 +1,5 @@
1
1
  module Aws
2
2
  module ENI
3
- VERSION = "0.2.1"
3
+ VERSION = "0.2.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-eni
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Greiling
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-02 00:00:00.000000000 Z
11
+ date: 2015-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gli
@@ -83,8 +83,9 @@ files:
83
83
  - aws-eni.gemspec
84
84
  - bin/aws-eni
85
85
  - lib/aws-eni.rb
86
+ - lib/aws-eni/client.rb
86
87
  - lib/aws-eni/errors.rb
87
- - lib/aws-eni/ifconfig.rb
88
+ - lib/aws-eni/interface.rb
88
89
  - lib/aws-eni/meta.rb
89
90
  - lib/aws-eni/version.rb
90
91
  homepage: http://pixelcog.com/