aws-eni 0.1.2 → 0.1.3

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: 3dd624009ce2d87a86fa1e33188a38f0be254464
4
- data.tar.gz: b1b958d4b5b71a7431cd19cff6b8e595ba0db1ef
3
+ metadata.gz: 613d8b521f5ea3db65f0420e90dfe188c6b42e53
4
+ data.tar.gz: 0a1eebe7c2b86a128a282f9edd8d61d5c0c39d2c
5
5
  SHA512:
6
- metadata.gz: 209b4e24a7b5934a67beab03852f11565ed997296ce9063af45d87803c7cb0e169590dfb1a5d71b6e19207281a04306aa6f09ca38436f90012ee302fc59724ea
7
- data.tar.gz: bf9452d5c381ff04bab980f0c1fa5b1738294a336330c090c58b759919f52ef839d46c90a8af0307db1051c4f96a92ff4bc3ac9d5c42b9ebdaefc0cbac3c50f3
6
+ metadata.gz: 092fd301932d4521a4c7b1fda6c1d04f21160d9585bd5e0b1c11e5385a87b97b21bb33bd3a55001fafa3da4e2b39d67b12b5cd759c7f00aa2064ce9dfe733edd
7
+ data.tar.gz: c10cb8481dc1f9a544e3ebedb674bd434d4e30f1a9cdc1e02d022d2195016f5f16813919a387a82e27b4aa206d29b092cbbb51bc568e1a4f3ba7e135007f976c
@@ -213,7 +213,7 @@ command [:create] do |c|
213
213
  args.delete('new')
214
214
  params = parse_args args, :subnet_id, :security_groups, :primary_ip
215
215
  interface = Aws::ENI.create_interface(params)
216
- puts "interface #{interface[:id]} created on #{interface[:subnet_id]}"
216
+ puts "interface #{interface[:interface_id]} created on #{interface[:subnet_id]}"
217
217
  end
218
218
  end
219
219
 
@@ -251,12 +251,12 @@ command [:attach] do |c|
251
251
  params = parse_args args, :subnet_id, :security_groups, :primary_ip
252
252
  Aws::ENI.assert_ifconfig_access if config
253
253
  interface = Aws::ENI.create_interface(params)
254
- puts "interface #{interface[:id]} created on #{interface[:subnet_id]}"
255
- id = interface[:id]
254
+ puts "interface #{interface[:interface_id]} created on #{interface[:subnet_id]}"
255
+ id = interface[:interface_id]
256
256
  end
257
257
  device = Aws::ENI.attach_interface(id, enable: config, configure: config, block: opts[:block])
258
- puts "interface #{device[:id]} attached to #{device[:name]}"
259
- puts "device #{device[:name]} enabled and configured" if opts[:config]
258
+ puts "interface #{device[:interface_id]} attached to #{device[:device_name]}"
259
+ puts "device #{device[:device_name]} enabled and configured" if config
260
260
  end
261
261
  end
262
262
 
@@ -283,13 +283,13 @@ command [:detach] do |c|
283
283
  params = parse_args args, :interface_id, :device_name
284
284
  params[:block] = opts[:block]
285
285
  params[:delete] = opts[:delete] unless opts[:delete] == :not_provided
286
- id = params[:interface_id] || params[:device_name]
286
+ id = params[:device_name] || params[:interface_id]
287
287
 
288
288
  device = Aws::ENI.detach_interface(id, params)
289
289
  if device[:deleted]
290
- puts "interface #{device[:id]} detached from #{device[:name]} and deleted"
290
+ puts "interface #{device[:interface_id]} detached from #{device[:device_name]} and deleted"
291
291
  else
292
- puts "interface #{device[:id]} detached from #{device[:name]}"
292
+ puts "interface #{device[:interface_id]} detached from #{device[:device_name]}"
293
293
  end
294
294
  end
295
295
  end
@@ -327,10 +327,20 @@ long_desc %{
327
327
  arg 'ip-address', :optional
328
328
  arg 'interface-id OR device-name'
329
329
  command [:assign] do |c|
330
+ c.desc 'Do not configure the interface after assignment'
331
+ c.switch [:n,'no-config'], negatable: false
332
+
333
+ c.desc 'Do not return until connection is verified'
334
+ c.switch [:b,:block], negatable: false
335
+
330
336
  c.action do |global,opts,args|
331
- help_now! "Missing argument" if args.empty?
332
337
  params = parse_args args, :interface_id, :device_name, :private_ip
333
- device = params[:interface_id] || params[:device_name]
338
+ device = params[:device_name] || params[:interface_id]
339
+ help_now! "Missing argument" if device.nil?
340
+
341
+ params.merge! configure: !opts['no-config'], block: opts[:block]
342
+ Aws::ENI.assert_ifconfig_access if params[:configure]
343
+
334
344
  assignment = Aws::ENI.assign_secondary_ip(device, params)
335
345
  puts "IP #{assignment[:private_ip]} assigned to #{assignment[:device_name]} (#{assignment[:interface_id]})"
336
346
  end
@@ -380,10 +390,14 @@ arg 'allocation-id', :optional
380
390
  arg 'interface-id', :optional
381
391
  arg 'device-name', :optional
382
392
  command [:associate] do |c|
393
+ c.desc 'Do not return until connection is verified'
394
+ c.switch [:b,:block], negatable: false
395
+
383
396
  c.action do |global,opts,args|
384
397
  help_now! "Missing argument" if args.empty?
385
398
  args.delete('new')
386
399
  params = parse_args args, :private_ip, :public_ip, :allocation_id, :interface_id, :device_name
400
+ params[:block] = opts[:block]
387
401
  assoc = Aws::ENI.associate_elastic_ip(params[:private_ip], params)
388
402
  puts "EIP #{assoc[:public_ip]} (#{assoc[:allocation_id]}) associated with #{assoc[:private_ip]} on #{assoc[:device_name]} (#{assoc[:interface_id]})"
389
403
  end
@@ -427,19 +441,19 @@ command [:allocate] do |c|
427
441
  end
428
442
  end
429
443
 
430
- desc 'Release one or more unassigned Elastic IP addresses'
444
+ desc 'Release unassigned Elastic IP addresses'
431
445
  long_desc %{
432
- Release one or more unassigned Elastic IP addresses. If IP address or
433
- allication, will release only that EIP, otherwise will clean all unassigned
434
- EIPs.
446
+ Release one unassigned Elastic IP addresses identified by its public IP or its
447
+ allocation ID
435
448
  }
436
449
  arg 'ip-address OR allocation-id'
437
450
  command [:release] do |c|
438
451
  c.action do |global,opts,args|
439
452
  help_now! "Missing argument" if args.empty?
453
+ help_now! "Too many arguments" if args.count > 1
440
454
  params = parse_args args, :public_ip, :allocation_id
441
455
  eip = params[:public_ip] || params[:allocation_id]
442
- release = Aws::ENI.release_elastic_ip(eip, params)
456
+ release = Aws::ENI.release_elastic_ip(eip)
443
457
  puts "EIP #{release[:public_ip]} (#{release[:allocation_id]}) released"
444
458
  end
445
459
  end
@@ -1,4 +1,5 @@
1
1
  require 'time'
2
+ require 'resolv'
2
3
  require 'aws-sdk'
3
4
  require 'aws-eni/version'
4
5
  require 'aws-eni/errors'
@@ -78,7 +79,7 @@ module Aws
78
79
  end
79
80
  end
80
81
  {
81
- id: response[:network_interface][:network_interface_id],
82
+ interface_id: response[:network_interface][:network_interface_id],
82
83
  subnet_id: response[:network_interface][:subnet_id],
83
84
  api_response: response[:network_interface]
84
85
  }
@@ -90,74 +91,73 @@ module Aws
90
91
  do_config = true unless options[:configure] == false
91
92
  assert_ifconfig_access if do_config || do_enable
92
93
 
93
- interface = IFconfig[options[:device_number] || options[:name]]
94
- raise InvalidParameterError, "Interface #{interface.name} is already in use" if interface.exists?
94
+ device = IFconfig[options[:device_number] || options[:name]].assert(exists: false)
95
95
 
96
- params = {}
97
- params[:network_interface_id] = id
98
- params[:instance_id] = environment[:instance_id]
99
- params[:device_index] = interface.device_number
100
-
101
- response = client.attach_network_interface(params)
96
+ response = client.attach_network_interface(
97
+ network_interface_id: id,
98
+ instance_id: environment[:instance_id],
99
+ device_index: device.device_number
100
+ )
102
101
 
103
102
  if options[:block] || do_config || do_enable
104
103
  wait_for 'the interface to attach', rescue: ConnectionFailed do
105
- interface.exists? && interface_status(interface.interface_id) == 'in-use'
104
+ device.exists? && interface_status(device.interface_id) == 'in-use'
106
105
  end
107
106
  end
108
- interface.configure if do_config
109
- interface.enable if do_enable
107
+ device.configure if do_config
108
+ device.enable if do_enable
110
109
  {
111
- id: interface.interface_id,
112
- name: interface.name,
113
- configured: options[:configure],
114
- api_response: response
110
+ interface_id: device.interface_id,
111
+ device_name: device.name,
112
+ device_number: device.device_number,
113
+ enabled: do_enable,
114
+ configured: do_config,
115
+ api_response: response
115
116
  }
116
117
  end
117
118
 
118
119
  # detach network interface
119
120
  def detach_interface(id, options = {})
120
- interface = IFconfig.filter(id).first
121
- raise InvalidParameterError, "Interface #{interface.name} does not exist" unless interface && interface.exists?
122
- if options[:name] && interface.name != options[:name]
123
- raise InvalidParameterError, "Interface #{interface.interface_id} not found on #{options[:name]}"
124
- end
125
- if options[:device_number] && interface.device_number != options[:device_number].to_i
126
- raise InvalidParameterError, "Interface #{interface.interface_id} not found at index #{options[:device_number]}"
127
- end
121
+ device = IFconfig[id].assert(
122
+ exists: true,
123
+ device_name: options[:device_name],
124
+ interface_id: options[:interface_id],
125
+ device_number: options[:device_number]
126
+ )
127
+ interface_id = device.interface_id
128
128
 
129
- description = client.describe_network_interfaces(filters: [{
129
+ response = client.describe_network_interfaces(filters: [{
130
130
  name: 'attachment.instance-id',
131
131
  values: [environment[:instance_id]]
132
132
  },{
133
133
  name: 'network-interface-id',
134
- values: [interface.interface_id]
134
+ values: [interface_id]
135
135
  }])
136
- description = description[:network_interfaces].first
137
- raise UnknownInterfaceError, "Interface attachment could not be located" unless description
136
+ interface = response[:network_interfaces].first
137
+ raise UnknownInterfaceError, "Interface attachment could not be located" unless interface
138
138
 
139
- interface.disable
140
- interface.deconfigure
139
+ device.disable
140
+ device.deconfigure
141
141
  client.detach_network_interface(
142
- attachment_id: description[:attachment][:attachment_id],
142
+ attachment_id: interface[:attachment][:attachment_id],
143
143
  force: true
144
144
  )
145
- created_by_us = description.tag_set.any? { |tag| tag.key == 'created by' && tag.value == owner_tag }
145
+ created_by_us = interface.tag_set.any? { |tag| tag.key == 'created by' && tag.value == owner_tag }
146
146
  do_delete = options[:delete] || options[:delete].nil? && created_by_us
147
147
 
148
148
  if options[:block] || do_delete
149
149
  wait_for 'the interface to detach', interval: 0.3 do
150
- !interface.exists? && interface_status(description[:network_interface_id]) == 'available'
150
+ !device.exists? && interface_status(interface[:network_interface_id]) == 'available'
151
151
  end
152
152
  end
153
- client.delete_network_interface(network_interface_id: description[:network_interface_id]) if do_delete
153
+ client.delete_network_interface(network_interface_id: interface[:network_interface_id]) if do_delete
154
154
  {
155
- id: description[:network_interface_id],
156
- name: "eth#{description[:attachment][:device_index]}",
157
- device_number: description[:attachment][:device_index],
155
+ interface_id: interface_id,
156
+ device_name: device.name,
157
+ device_number: device.device_number,
158
158
  created_by_us: created_by_us,
159
159
  deleted: do_delete,
160
- api_response: description
160
+ api_response: interface
161
161
  }
162
162
  end
163
163
 
@@ -187,14 +187,13 @@ module Aws
187
187
 
188
188
  descriptions = client.describe_network_interfaces(filters: filters)
189
189
  interfaces = descriptions[:network_interfaces].select do |interface|
190
- skip = safe_mode && interface.tag_set.any? do |tag|
190
+ unless safe_mode && interface.tag_set.any? do |tag|
191
191
  begin
192
192
  tag.key == 'created on' && Time.now - Time.parse(tag.value) < 60
193
193
  rescue ArgumentError
194
194
  false
195
195
  end
196
196
  end
197
- unless skip
198
197
  client.delete_network_interface(network_interface_id: interface[:network_interface_id])
199
198
  true
200
199
  end
@@ -207,12 +206,53 @@ module Aws
207
206
  end
208
207
 
209
208
  # add new private ip using the AWS api and add it to our local ip config
210
- def assign_secondary_ip(interface, options = {})
211
- raise NoMethodError, "assign_secondary_ip not yet implemented"
209
+ def assign_secondary_ip(id, options = {})
210
+ device = IFconfig[id].assert(
211
+ exists: true,
212
+ device_name: options[:device_name],
213
+ interface_id: options[:interface_id],
214
+ device_number: options[:device_number]
215
+ )
216
+ interface_id = device.interface_id
217
+ current_ips = interface_ips(interface_id)
218
+ new_ip = options[:private_ip]
219
+
220
+ if new_ip = options[:private_ip]
221
+ if current_ips.include?(new_ip)
222
+ raise InvalidParameterError, "IP #{new_ip} already assigned to #{device.name}"
223
+ end
224
+ client.assign_private_ip_addresses(
225
+ network_interface_id: interface_id,
226
+ private_ip_addresses: [new_ip],
227
+ allow_reassignment: false
228
+ )
229
+ wait_for 'private ip address to be assigned' do
230
+ interface_ips(interface_id).include?(new_ip)
231
+ end
232
+ else
233
+ client.assign_private_ip_addresses(
234
+ network_interface_id: interface_id,
235
+ secondary_private_ip_address_count: 1,
236
+ allow_reassignment: false
237
+ )
238
+ wait_for 'new private ip address to be assigned' do
239
+ new_ips = interface_ips(interface_id) - current_ips
240
+ new_ip = new_ips.first if new_ips
241
+ end
242
+ end
243
+
244
+ unless options[:configure] == false
245
+ device.add_alias(new_ip)
246
+ if options[:block] && !IFconfig.test(new_ip, target: device.gateway)
247
+ raise TimeoutError, "Timed out waiting for ip address to become active"
248
+ end
249
+ end
212
250
  {
213
- private_ip: '0.0.0.0',
214
- device_name: 'eth0',
215
- interface_id: 'eni-1a2b3c4d'
251
+ private_ip: new_ip,
252
+ interface_id: interface_id,
253
+ device_name: device.name,
254
+ device_number: device.device_number,
255
+ interface_ips: current_ips << new_ip
216
256
  }
217
257
  end
218
258
 
@@ -232,14 +272,46 @@ module Aws
232
272
 
233
273
  # associate a private ip with an elastic ip through the AWS api
234
274
  def associate_elastic_ip(private_ip, options = {})
235
- raise NoMethodError, "associate_elastic_ip not yet implemented"
275
+ find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
276
+ device = IFconfig[find].assert(
277
+ exists: true,
278
+ private_ip: private_ip,
279
+ device_name: options[:device_name],
280
+ interface_id: options[:interface_id],
281
+ device_number: options[:device_number]
282
+ )
283
+ options[:public_ip] ||= options[:allocation_id]
284
+
285
+ if public_ip = device.public_ips[private_ip]
286
+ raise InvalidParameterError, "IP #{private_ip} already has an associated EIP (#{public_ip})"
287
+ end
288
+
289
+ if options[:public_ip]
290
+ eip = describe_address(options[:public_ip])
291
+ if options[:allocation_id] && eip[:allocation_id] != options[:allocation_id]
292
+ raise InvalidParameterError, "EIP #{eip[:public_ip]} (#{eip[:allocation_id]}) does not match #{options[:allocation_id]}"
293
+ end
294
+ else
295
+ eip = allocate_elastic_ip
296
+ end
297
+
298
+ resp = client.associate_address(
299
+ network_interface_id: device.interface_id,
300
+ allocation_id: eip[:allocation_id],
301
+ private_ip_address: private_ip,
302
+ allow_reassociation: false
303
+ )
304
+
305
+ if options[:block] && !IFconfig.test(private_ip)
306
+ raise TimeoutError, "Timed out waiting for ip address to become active"
307
+ end
236
308
  {
237
- private_ip: '0.0.0.0',
238
- device_name: 'eth0',
239
- interface_id: 'eni-1a2b3c4d',
240
- public_ip: '0.0.0.0',
241
- allocation_id: 'eipalloc-1a2b3c4d',
242
- association_id: 'eipassoc-1a2b3c4d'
309
+ private_ip: private_ip,
310
+ device_name: device.name,
311
+ interface_id: device.interface_id,
312
+ public_ip: eip[:public_ip],
313
+ allocation_id: eip[:allocation_id],
314
+ association_id: resp[:association_id]
243
315
  }
244
316
  end
245
317
 
@@ -260,19 +332,23 @@ module Aws
260
332
 
261
333
  # allocate a new elastic ip address
262
334
  def allocate_elastic_ip
263
- raise NoMethodError, "allocate_elastic_ip not yet implemented"
335
+ eip = client.allocate_address(domain: 'vpc')
264
336
  {
265
- public_ip: '0.0.0.0',
266
- allocation_id: 'eipalloc-1a2b3c4d'
337
+ public_ip: eip[:public_ip],
338
+ allocation_id: eip[:allocation_id]
267
339
  }
268
340
  end
269
341
 
270
342
  # release the specified elastic ip address
271
- def release_elastic_ip(ip, options = {})
272
- raise NoMethodError, "release_elastic_ip not yet implemented"
343
+ def release_elastic_ip(ip)
344
+ eip = describe_address(ip)
345
+ if eip[:association_id]
346
+ raise AWSPermissionError, "Elastic IP #{eip[:public_ip]} (#{eip[:allocation_id]}) is currently in use"
347
+ end
348
+ client.release_address(allocation_id: eip[:allocation_id])
273
349
  {
274
- public_ip: '0.0.0.0',
275
- allocation_id: 'eipalloc-1a2b3c4d'
350
+ public_ip: eip[:public_ip],
351
+ allocation_id: eip[:allocation_id]
276
352
  }
277
353
  end
278
354
 
@@ -308,7 +384,21 @@ module Aws
308
384
  create_tags: {
309
385
  resources: ['eni-abcd1234'],
310
386
  tags: []
387
+ },
388
+ describe_addresses: nil,
389
+ allocate_address: nil,
390
+ release_address: {
391
+ dry_run: true,
392
+ allocation_id: 'eipalloc-no_exist'
393
+ },
394
+ associate_address: {
395
+ allocation_id: 'eipalloc-no_exist',
396
+ network_interface_id: 'eni-abcd1234'
311
397
  }
398
+ # has no dry_run method
399
+ # assign_private_ip_addresses: {
400
+ # network_interface_id: 'eni-abcd1234'
401
+ # }
312
402
  }
313
403
  test_methods.each do |method, params|
314
404
  begin
@@ -318,6 +408,8 @@ module Aws
318
408
  raise Error, "Unexpected behavior while testing AWS API access"
319
409
  rescue Aws::EC2::Errors::DryRunOperation
320
410
  # success
411
+ rescue Aws::EC2::Errors::InvalidAllocationIDNotFound
412
+ # release_address does not properly support dry_run
321
413
  rescue Aws::EC2::Errors::UnauthorizedOperation
322
414
  return false
323
415
  end
@@ -331,6 +423,29 @@ module Aws
331
423
 
332
424
  private
333
425
 
426
+ # use either an ip address or allocation id
427
+ def describe_address(address)
428
+ filter_by = address =~ Resolv::IPv4::Regex ? 'public-ip' : 'allocation-id'
429
+ resp = client.describe_addresses(filters: [
430
+ { name: 'domain', values: ['vpc'] },
431
+ { name: filter_by, values: [address] }
432
+ ])
433
+ raise InvalidParameterError, "IP #{address} could not be located" if resp[:addresses].empty?
434
+ resp[:addresses].first
435
+ end
436
+
437
+ def interface_ips(id)
438
+ resp = client.describe_network_interfaces(network_interface_ids: [id])
439
+ interface = resp[:network_interfaces].first
440
+ if interface && interface[:private_ip_addresses]
441
+ primary = interface[:private_ip_addresses].find { |ip| ip[:primary] }
442
+ interface[:private_ip_addresses].map { |ip| ip[:private_ip_address] }.tap do |ips|
443
+ # ensure primary ip is first in the list
444
+ ips.unshift(*ips.delete(primary[:private_ip_address])) if primary
445
+ end
446
+ end
447
+ end
448
+
334
449
  def interface_status(id)
335
450
  resp = client.describe_network_interfaces(network_interface_ids: [id])
336
451
  resp[:network_interfaces].first[:status] unless resp[:network_interfaces].empty?
@@ -13,10 +13,23 @@ module Aws
13
13
 
14
14
  # Array-like accessor to automatically instantiate our class
15
15
  def [](index)
16
- index = $1.to_i if index.to_s =~ /^(?:eth)?([0-9]+)$/
17
- index ||= next_available_index
18
- @instance_cache ||= []
19
- @instance_cache[index] ||= new("eth#{index}", false)
16
+ case index
17
+ when Integer
18
+ @instance_cache ||= []
19
+ @instance_cache[index] ||= new("eth#{index}", false)
20
+ when nil
21
+ self[next_available_index]
22
+ when /^(?:eth)?([0-9]+)$/
23
+ self[$1.to_i]
24
+ when /^eni-/
25
+ find { |dev| dev.instance_id == index }
26
+ when /^[0-9a-f:]+$/i
27
+ find { |dev| dev.hwaddr.casecmp(index) == 0 }
28
+ when /^[0-9\.]+$/
29
+ find { |dev| dev.has_ip?(index) }
30
+ end.tap do |dev|
31
+ raise UnknownInterfaceError, "No interface found matching #{index}" unless dev
32
+ end
20
33
  end
21
34
 
22
35
  # Purge and deconfigure non-existent interfaces from the cache
@@ -25,11 +38,6 @@ module Aws
25
38
  @instance_cache.map!{ |dev| dev if dev.exists? }
26
39
  end
27
40
 
28
- # Return array of available ethernet interfaces
29
- def existing
30
- Dir.entries("/sys/class/net/").grep(/^eth[0-9]+$/){ |name| self[name] }
31
- end
32
-
33
41
  # Return the next unused device index
34
42
  def next_available_index
35
43
  for index in 0..32 do
@@ -39,7 +47,7 @@ module Aws
39
47
 
40
48
  # Iterate over available ethernet interfaces (required for Enumerable)
41
49
  def each(&block)
42
- existing.each(&block)
50
+ Dir.entries("/sys/class/net/").grep(/^eth[0-9]+$/){ |name| self[name] }.each(&block)
43
51
  end
44
52
 
45
53
  # Return array of enabled interfaces
@@ -63,38 +71,65 @@ module Aws
63
71
 
64
72
  # Return an array of available interfaces identified by name, id,
65
73
  # hwaddr, or subnet id.
66
- def filter(match = nil)
67
- return existing unless match
68
- select{ |dev| dev.is?(match) }.tap do |result|
69
- raise UnknownInterfaceError, "No interface found matching \"#{match}\"" if result.empty?
74
+ def filter(filter = nil)
75
+ case filter
76
+ when nil
77
+ to_a
78
+ when /^eni-/, /^eth[0-9]+$/, /^[0-9a-f:]+$/i, /^[0-9\.]+$/
79
+ self[filter].to_a
80
+ when /^subnet-/
81
+ select { |dev| dev.subnet_id == filter }
82
+ end.tap do |devs|
83
+ raise UnknownInterfaceError, "No interface found matching #{filter}" if devs.nil? || devs.empty?
70
84
  end
71
85
  end
72
86
 
73
87
  # Test whether we have permission to run RTNETLINK commands
74
88
  def mutable?
75
- exec 'link set dev eth0' # random innocuous command
89
+ cmd('link set dev eth0') # innocuous command
76
90
  true
77
91
  rescue PermissionError
78
92
  false
79
93
  end
80
94
 
81
- # Execute a command
95
+ # Execute an 'ip' command
96
+ def cmd(command, options = {})
97
+ errors = options[:errors]
98
+ options[:errors] = true
99
+ begin
100
+ exec("/sbin/ip #{command}", options)
101
+ rescue CommandError => e
102
+ case e.message
103
+ when /operation not permitted/i
104
+ raise PermissionError, "Operation not permitted"
105
+ else
106
+ raise if errors
107
+ end
108
+ end
109
+ end
110
+
111
+ # Test connectivity from a given ip address
112
+ def test(ip, options = {})
113
+ timeout = Integer(options[:timeout] || 30)
114
+ target = options[:target] || '8.8.8.8'
115
+ !!exec("ping -w #{timeout} -c 1 -I #{ip} #{target}")
116
+ end
117
+
118
+ # Execute a command, returns output as string or nil on error
82
119
  def exec(command, options = {})
83
120
  output = nil
121
+ errors = options[:errors]
84
122
  verbose = self.verbose || options[:verbose]
85
- raise_errors = options[:raise_errors]
86
- puts "ip #{command}" if verbose
87
-
88
- Open3.popen3("/sbin/ip #{command}") do |i,o,e,t|
89
- unless t.value.success?
90
- error_msg = e.read
91
- if error_msg =~ /operation not permitted/i
92
- raise PermissionError, "Operation not permitted"
93
- end
94
- warn "Warning: #{error_msg}" if verbose
95
- raise CommandError, error_msg if raise_errors
123
+
124
+ puts command if verbose
125
+ Open3.popen3(command) do |i,o,e,t|
126
+ if t.value.success?
127
+ output = o.read
128
+ else
129
+ error = e.read
130
+ warn "Warning: #{error}" if verbose
131
+ raise CommandError, error if errors
96
132
  end
97
- output = o.read
98
133
  end
99
134
  output
100
135
  end
@@ -159,10 +194,18 @@ module Aws
159
194
  info[:subnet_cidr]
160
195
  end
161
196
 
197
+ def gateway
198
+ IPAddr.new(subnet_cidr).succ.to_s
199
+ end
200
+
201
+ def prefix
202
+ subnet_cidr.split('/').last.to_i
203
+ end
204
+
162
205
  # Return an array of configured ip addresses (primary + secondary)
163
206
  def local_ips
164
- list = exec("addr show dev #{name} primary") +
165
- exec("addr show dev #{name} secondary")
207
+ list = cmd("addr show dev #{name} primary") +
208
+ cmd("addr show dev #{name} secondary")
166
209
  list.lines.grep(/inet ([0-9\.]+)\/.* #{name}/i){ $1 }
167
210
  end
168
211
 
@@ -180,29 +223,29 @@ module Aws
180
223
  ip_assoc
181
224
  end
182
225
 
183
- # Enable our interface
226
+ # Enable our interface and create necessary routes
184
227
  def enable
185
- exec("link set dev #{name} up")
228
+ cmd("link set dev #{name} up")
229
+ cmd("route add default via #{gateway} dev #{name} table #{route_table}")
230
+ cmd("route flush cache")
186
231
  end
187
232
 
188
233
  # Disable our interface
189
234
  def disable
190
- exec("link set dev #{name} down")
235
+ cmd("link set dev #{name} down")
191
236
  end
192
237
 
193
238
  # Check whether our interface is enabled
194
239
  def enabled?
195
- exists? && exec("link show up").include?(name)
240
+ exists? && cmd("link show up").include?(name)
196
241
  end
197
242
 
198
243
  # Initialize a new interface config
199
244
  def configure(dry_run = false)
200
245
  changes = 0
201
- info = self.info
202
- prefix = info[:subnet_cidr].split('/').last.to_i
203
- gateway = IPAddr.new(info[:subnet_cidr]).succ.to_s
246
+ prefix = self.prefix # prevent exists? check on each use
204
247
 
205
- meta_ips = Meta.get("network/interfaces/macs/#{info[:hwaddr]}/local-ipv4s").lines.map(&:strip)
248
+ meta_ips = Meta.get("network/interfaces/macs/#{hwaddr}/local-ipv4s").lines.map(&:strip)
206
249
  local_primary, *local_aliases = local_ips
207
250
  meta_primary, *meta_aliases = meta_ips
208
251
 
@@ -210,35 +253,34 @@ module Aws
210
253
  if name != 'eth0' && local_primary != meta_primary
211
254
  unless dry_run
212
255
  deconfigure
213
- exec("addr add #{meta_primary}/#{prefix} brd + dev #{name}")
214
- exec("route add default via #{gateway} dev #{name} table #{route_table}")
215
- exec("route flush cache")
256
+ cmd("addr add #{meta_primary}/#{prefix} brd + dev #{name}")
216
257
  end
217
258
  changes += 1
218
259
  end
219
260
 
220
261
  # add missing secondary ips
221
262
  (meta_aliases - local_aliases).each do |ip|
222
- exec("addr add #{ip}/#{prefix} brd + dev #{name}") unless dry_run
263
+ cmd("addr add #{ip}/#{prefix} brd + dev #{name}") unless dry_run
223
264
  changes += 1
224
265
  end
225
266
 
226
267
  # remove extra secondary ips
227
268
  (local_aliases - meta_aliases).each do |ip|
228
- exec("addr del #{ip}/#{prefix} dev #{name}") unless dry_run
269
+ cmd("addr del #{ip}/#{prefix} dev #{name}") unless dry_run
229
270
  changes += 1
230
271
  end
231
272
 
273
+ # add and remove source-ip based rules
232
274
  unless name == 'eth0'
233
275
  rules_to_add = meta_ips || []
234
- exec("rule list").lines.grep(/^([0-9]+):.*\s([0-9\.]+)\s+lookup #{route_table}/) do
276
+ cmd("rule list").lines.grep(/^([0-9]+):.*\s([0-9\.]+)\s+lookup #{route_table}/) do
235
277
  unless rules_to_add.delete($2)
236
- exec("rule delete pref #{$1}") unless dry_run
278
+ cmd("rule delete pref #{$1}") unless dry_run
237
279
  changes += 1
238
280
  end
239
281
  end
240
282
  rules_to_add.each do |ip|
241
- exec("rule add from #{ip} lookup #{route_table}") unless dry_run
283
+ cmd("rule add from #{ip} lookup #{route_table}") unless dry_run
242
284
  changes += 1
243
285
  end
244
286
  end
@@ -251,67 +293,105 @@ module Aws
251
293
  def deconfigure
252
294
  # assume eth0 primary ip is managed by dhcp
253
295
  if name == 'eth0'
254
- exec("addr flush dev eth0 secondary")
296
+ cmd("addr flush dev eth0 secondary")
255
297
  else
256
- exec("rule list").lines.grep(/^([0-9]+):.*lookup #{route_table}/) do
257
- exec("rule delete pref #{$1}")
298
+ cmd("rule list").lines.grep(/^([0-9]+):.*lookup #{route_table}/) do
299
+ cmd("rule delete pref #{$1}")
258
300
  end
259
- exec("addr flush dev #{name}")
260
- exec("route flush table #{route_table}")
261
- exec("route flush cache")
301
+ cmd("addr flush dev #{name}")
302
+ cmd("route flush table #{route_table}")
303
+ cmd("route flush cache")
262
304
  end
263
305
  @clean = true
264
306
  end
265
307
 
266
308
  # Add a secondary ip to this interface
267
309
  def add_alias(ip)
268
- prefix = info[:subnet_cidr].split('/').last.to_i
269
- exec("addr add #{ip}/#{prefix} brd + dev #{name}")
270
-
271
- unless name == 'eth0' || exec("rule list") =~ /from #{ip} lookup #{route_table}/
272
- exec("rule add from #{ip} lookup #{route_table}")
310
+ cmd("addr add #{ip}/#{prefix} brd + dev #{name}")
311
+ unless name == 'eth0' || cmd("rule list").include?("from #{ip} lookup #{route_table}")
312
+ cmd("rule add from #{ip} lookup #{route_table}")
273
313
  end
274
314
  end
275
315
 
276
316
  # Remove a secondary ip from this interface
277
317
  def remove_alias
278
- prefix = info[:subnet_cidr].split('/').last.to_i
279
- exec("addr del #{ip}/#{prefix} dev #{name}")
280
-
281
- if name != 'eth0' && exec("rule list") =~ /([0-9]+):\s+from #{ip} lookup #{route_table}/
282
- exec("rule delete pref #{$1}")
318
+ cmd("addr del #{ip}/#{prefix} dev #{name}")
319
+ unless name == 'eth0' || !cmd("rule list").match(/([0-9]+):\s+from #{ip} lookup #{route_table}/)
320
+ cmd("rule delete pref #{$1}")
283
321
  end
284
322
  end
285
323
 
286
- # Identify this interface by one of its attributes
287
- def is?(match)
288
- if match == name
289
- true
324
+ # Return true if the ip address is associated with this interface
325
+ def has_ip?(ip_addr)
326
+ if IPAddr.new(subnet_cidr) === IPAddr.new(ip_addr)
327
+ # ip within subnet
328
+ local_ips.include? ip_addr
290
329
  else
291
- info = self.info
292
- match == info[:interface_id] || match == info[:hwaddr] || match == info[:subnet_id]
330
+ # ip outside subnet
331
+ public_ips.has_value? ip_addr
332
+ end
333
+ end
334
+
335
+ # Throw exception unless this interface matches the provided attributes
336
+ # else returns self
337
+ def assert(attr)
338
+ error = nil
339
+ attr.find do |attr,val|
340
+ next if val.nil?
341
+ error = case attr
342
+ when :exists
343
+ if val
344
+ "The specified interface does not exist." unless exists?
345
+ else
346
+ "Interface #{name} exists." if exists?
347
+ end
348
+ when :enabled
349
+ if val
350
+ "Interface #{name} is not enabled." unless enabled?
351
+ else
352
+ "Interface #{name} is not disabled." if enabled?
353
+ end
354
+ when :name, :device_name
355
+ "The specified interface does not match" unless name == val
356
+ when :index, :device_index, :device_number
357
+ "Interface #{name} is device number #{val}" unless device_number == val.to_i
358
+ when :hwaddr
359
+ "Interface #{name} does not match hwaddr #{val}" unless hwaddr == val
360
+ when :interface_id
361
+ "Interface #{name} does not have interface id #{val}" unless interface_id == val
362
+ when :subnet_id
363
+ "Interface #{name} does not have subnet id #{val}" unless subnet_id == val
364
+ when :ip, :has_ip
365
+ "Interface #{name} does not have IP #{val}" unless has_ip? val
366
+ when :public_ip
367
+ "Interface #{name} does not have private IP #{val}" unless public_ips.include? val
368
+ when :local_ip, :private_ip
369
+ "Interface #{name} does not have private IP #{val}" unless local_ips.include? val
370
+ else
371
+ "Unknown attribute: #{attr}"
372
+ end
293
373
  end
374
+ raise UnknownInterfaceError, error if error
375
+ self
294
376
  end
295
377
 
296
378
  # Return an array representation of our interface config, including public
297
379
  # ip associations and enabled status
298
380
  def to_h
299
- info.merge({
381
+ info.merge(
300
382
  name: name,
301
383
  device_number: device_number,
302
384
  route_table: route_table,
303
385
  local_ips: local_ips,
304
386
  public_ips: public_ips,
305
387
  enabled: enabled?
306
- })
388
+ )
307
389
  end
308
390
 
309
391
  private
310
392
 
311
393
  # Alias for static method
312
- def exec(command, options = {})
313
- self.class.exec(command, options)
314
- end
394
+ def cmd(*args) self.class.cmd(*args) end
315
395
  end
316
396
  end
317
397
  end
@@ -1,5 +1,5 @@
1
1
  module Aws
2
2
  module ENI
3
- VERSION = "0.1.2"
3
+ VERSION = "0.1.3"
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.1.2
4
+ version: 0.1.3
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-04-28 00:00:00.000000000 Z
11
+ date: 2015-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gli