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 +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
|