aws-eni 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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/