aws-eni 0.1.2 → 0.1.3
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 +4 -4
- data/bin/aws-eni +29 -15
- data/lib/aws-eni.rb +174 -59
- data/lib/aws-eni/ifconfig.rb +153 -73
- data/lib/aws-eni/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 613d8b521f5ea3db65f0420e90dfe188c6b42e53
|
4
|
+
data.tar.gz: 0a1eebe7c2b86a128a282f9edd8d61d5c0c39d2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 092fd301932d4521a4c7b1fda6c1d04f21160d9585bd5e0b1c11e5385a87b97b21bb33bd3a55001fafa3da4e2b39d67b12b5cd759c7f00aa2064ce9dfe733edd
|
7
|
+
data.tar.gz: c10cb8481dc1f9a544e3ebedb674bd434d4e30f1a9cdc1e02d022d2195016f5f16813919a387a82e27b4aa206d29b092cbbb51bc568e1a4f3ba7e135007f976c
|
data/bin/aws-eni
CHANGED
@@ -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[:
|
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[:
|
255
|
-
id = interface[:
|
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[:
|
259
|
-
puts "device #{device[:
|
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[:
|
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[:
|
290
|
+
puts "interface #{device[:interface_id]} detached from #{device[:device_name]} and deleted"
|
291
291
|
else
|
292
|
-
puts "interface #{device[:
|
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[:
|
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
|
444
|
+
desc 'Release unassigned Elastic IP addresses'
|
431
445
|
long_desc %{
|
432
|
-
Release one
|
433
|
-
|
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
|
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
|
data/lib/aws-eni.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
104
|
+
device.exists? && interface_status(device.interface_id) == 'in-use'
|
106
105
|
end
|
107
106
|
end
|
108
|
-
|
109
|
-
|
107
|
+
device.configure if do_config
|
108
|
+
device.enable if do_enable
|
110
109
|
{
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
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: [
|
134
|
+
values: [interface_id]
|
135
135
|
}])
|
136
|
-
|
137
|
-
raise UnknownInterfaceError, "Interface attachment could not be located" unless
|
136
|
+
interface = response[:network_interfaces].first
|
137
|
+
raise UnknownInterfaceError, "Interface attachment could not be located" unless interface
|
138
138
|
|
139
|
-
|
140
|
-
|
139
|
+
device.disable
|
140
|
+
device.deconfigure
|
141
141
|
client.detach_network_interface(
|
142
|
-
attachment_id:
|
142
|
+
attachment_id: interface[:attachment][:attachment_id],
|
143
143
|
force: true
|
144
144
|
)
|
145
|
-
created_by_us =
|
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
|
-
!
|
150
|
+
!device.exists? && interface_status(interface[:network_interface_id]) == 'available'
|
151
151
|
end
|
152
152
|
end
|
153
|
-
client.delete_network_interface(network_interface_id:
|
153
|
+
client.delete_network_interface(network_interface_id: interface[:network_interface_id]) if do_delete
|
154
154
|
{
|
155
|
-
|
156
|
-
name
|
157
|
-
device_number:
|
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:
|
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
|
-
|
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(
|
211
|
-
|
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:
|
214
|
-
|
215
|
-
|
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
|
-
|
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:
|
238
|
-
device_name:
|
239
|
-
interface_id:
|
240
|
-
public_ip:
|
241
|
-
allocation_id:
|
242
|
-
association_id:
|
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
|
-
|
335
|
+
eip = client.allocate_address(domain: 'vpc')
|
264
336
|
{
|
265
|
-
public_ip:
|
266
|
-
allocation_id:
|
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
|
272
|
-
|
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:
|
275
|
-
allocation_id:
|
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?
|
data/lib/aws-eni/ifconfig.rb
CHANGED
@@ -13,10 +13,23 @@ module Aws
|
|
13
13
|
|
14
14
|
# Array-like accessor to automatically instantiate our class
|
15
15
|
def [](index)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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(
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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
|
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
|
-
|
86
|
-
puts
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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 =
|
165
|
-
|
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
|
-
|
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
|
-
|
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? &&
|
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
|
-
|
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/#{
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
296
|
+
cmd("addr flush dev eth0 secondary")
|
255
297
|
else
|
256
|
-
|
257
|
-
|
298
|
+
cmd("rule list").lines.grep(/^([0-9]+):.*lookup #{route_table}/) do
|
299
|
+
cmd("rule delete pref #{$1}")
|
258
300
|
end
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
269
|
-
|
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
|
279
|
-
|
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
|
-
#
|
287
|
-
def
|
288
|
-
if
|
289
|
-
|
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
|
-
|
292
|
-
|
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
|
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
|
data/lib/aws-eni/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2015-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gli
|