aws-eni 0.3.1 → 0.4.0
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 +10 -3
- data/lib/aws-eni.rb +59 -24
- data/lib/aws-eni/interface.rb +34 -15
- data/lib/aws-eni/meta.rb +33 -22
- 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: 9e3020c6ae20a0d6a7027f065560e00c5764dbfb
|
4
|
+
data.tar.gz: f4827152a5efad857d3a8818e7f882a5a25e15ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd1bb10082ba681b5452411d8f2e75a39cc9b6d7227e3e8b996de89b5d619072c3295a40c63db98e0e11bb2c938e8c01c7b736fd5122f6bd4da45d896a65b435
|
7
|
+
data.tar.gz: 9c0e454b7797af337b50829ee4231ee9ee2f3f558343980023f4bb5078f6fb0c17fe0d81e5954473b90e67669e25adbb710ffc808143bb9e7b9eeb2a47e52ed4
|
data/bin/aws-eni
CHANGED
@@ -24,7 +24,7 @@ desc 'Display all system commands and warnings'
|
|
24
24
|
switch [:V,:verbose], negatable: false
|
25
25
|
|
26
26
|
pre do |opt|
|
27
|
-
Aws::ENI.timeout
|
27
|
+
Aws::ENI.timeout 120
|
28
28
|
Aws::ENI.verbose opt[:verbose]
|
29
29
|
true
|
30
30
|
end
|
@@ -398,11 +398,14 @@ command [:unassign] do |c|
|
|
398
398
|
c.desc 'Release any associated public IP address'
|
399
399
|
c.switch [:r,:release], negatable: false
|
400
400
|
|
401
|
+
c.desc 'Do not wait for new interface state to propogate'
|
402
|
+
c.switch [:n,:noblock], negatable: false
|
403
|
+
|
401
404
|
c.action do |global,opts,args|
|
402
405
|
params = parse_args args, :interface_id, :device_name, :private_ip
|
403
406
|
help_now! "Missing argument" unless params[:private_ip]
|
404
|
-
params[:release] = opts[:release]
|
405
407
|
|
408
|
+
params.merge! release: opts[:release], block: !opts[:noblock]
|
406
409
|
unassign = Aws::ENI.unassign_secondary_ip(params[:private_ip], params)
|
407
410
|
if unassign[:released]
|
408
411
|
puts "EIP #{unassign[:public_ip]} (#{unassign[:allocation_id]}) dissociated from #{unassign[:private_ip]} and released"
|
@@ -463,11 +466,15 @@ command [:dissociate] do |c|
|
|
463
466
|
c.desc 'Release the associated public IP address'
|
464
467
|
c.switch [:r,:release], negatable: false
|
465
468
|
|
469
|
+
c.desc 'Do not wait for new interface state to propogate'
|
470
|
+
c.switch [:noblock], negatable: false
|
471
|
+
|
466
472
|
c.action do |global,opts,args|
|
467
473
|
params = parse_args args, :private_ip, :public_ip, :allocation_id, :association_id, :interface_id, :device_name
|
468
|
-
params[:release] = opts[:release]
|
469
474
|
address = params[:private_ip] || params[:public_ip] || params[:association_id] || params[:allocation_id]
|
470
475
|
help_now! "Missing argument" unless address
|
476
|
+
|
477
|
+
params.merge! release: opts[:release], block: !opts[:noblock]
|
471
478
|
dissoc = Aws::ENI.dissociate_elastic_ip(address, params)
|
472
479
|
if dissoc[:released]
|
473
480
|
puts "EIP #{dissoc[:public_ip]} (#{dissoc[:allocation_id]}) dissociated from #{dissoc[:private_ip]} on #{dissoc[:device_name]} (#{dissoc[:interface_id]}) and released"
|
data/lib/aws-eni.rb
CHANGED
@@ -9,16 +9,21 @@ module Aws
|
|
9
9
|
module ENI
|
10
10
|
extend self
|
11
11
|
|
12
|
+
# class level thread-safe lock
|
13
|
+
@lock = Mutex.new
|
14
|
+
|
12
15
|
def environment
|
13
|
-
@
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
@lock.synchronize do
|
17
|
+
@environment ||= Meta.connection do
|
18
|
+
hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
|
19
|
+
{
|
20
|
+
instance_id: Meta.instance('instance-id'),
|
21
|
+
availability_zone: Meta.instance('placement/availability-zone'),
|
22
|
+
region: Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1'),
|
23
|
+
vpc_id: Meta.interface(hwaddr, 'vpc-id'),
|
24
|
+
vpc_cidr: Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
|
25
|
+
}.freeze
|
26
|
+
end
|
22
27
|
end
|
23
28
|
rescue Errors::MetaNotFound
|
24
29
|
raise Errors::EnvironmentError, 'Unable to detect VPC, library incompatible with EC2-Classic'
|
@@ -33,7 +38,7 @@ module Aws
|
|
33
38
|
|
34
39
|
def timeout(new_default = nil)
|
35
40
|
@timeout = new_default.to_i if new_default
|
36
|
-
@timeout ||=
|
41
|
+
@timeout ||= 120
|
37
42
|
end
|
38
43
|
|
39
44
|
def verbose(set_verbose = nil)
|
@@ -117,7 +122,7 @@ module Aws
|
|
117
122
|
end
|
118
123
|
|
119
124
|
if do_block || do_config || do_enable
|
120
|
-
wait_for 'the interface to attach', rescue: Errors::
|
125
|
+
wait_for 'the interface to attach', rescue: Errors::InvalidInterface do
|
121
126
|
device.exists? && Client.interface_attached(device.interface_id)
|
122
127
|
end
|
123
128
|
end
|
@@ -261,7 +266,7 @@ module Aws
|
|
261
266
|
new_ip = options[:private_ip]
|
262
267
|
|
263
268
|
if do_block && !device.enabled?
|
264
|
-
raise Errors::InvalidParameter, "Interface #{device.name} is not enabled (cannot
|
269
|
+
raise Errors::InvalidParameter, "Interface #{device.name} is not enabled (cannot test connection)"
|
265
270
|
end
|
266
271
|
|
267
272
|
if new_ip
|
@@ -273,29 +278,34 @@ module Aws
|
|
273
278
|
private_ip_addresses: [new_ip],
|
274
279
|
allow_reassignment: false
|
275
280
|
)
|
276
|
-
wait_for 'private ip address to be assigned' do
|
277
|
-
Client.interface_private_ips(interface_id).include?(new_ip) ||
|
278
|
-
device.local_ips.include?(new_ip)
|
279
|
-
end
|
280
281
|
else
|
281
282
|
Client.assign_private_ip_addresses(
|
282
283
|
network_interface_id: interface_id,
|
283
284
|
secondary_private_ip_address_count: 1,
|
284
285
|
allow_reassignment: false
|
285
286
|
)
|
286
|
-
wait_for 'new private
|
287
|
-
|
288
|
-
|
289
|
-
new_ip = new_ips.first if new_ips
|
287
|
+
wait_for 'new private IP address to be assigned' do
|
288
|
+
client_ips = Client.interface_private_ips(interface_id)
|
289
|
+
new_ip = (client_ips - current_ips).first || (device.meta_ips - current_ips).first
|
290
290
|
end
|
291
291
|
end
|
292
292
|
|
293
293
|
if do_config
|
294
294
|
device.add_alias(new_ip)
|
295
|
-
if do_block && !Interface.test(new_ip, target: device.gateway)
|
295
|
+
if do_block && !Interface.test(new_ip, target: device.gateway, timeout: timeout)
|
296
296
|
raise Errors::TimeoutError, 'Timed out waiting for IP address to become active'
|
297
297
|
end
|
298
298
|
end
|
299
|
+
|
300
|
+
if do_block
|
301
|
+
# ensure new state has propagated to avoid race conditions
|
302
|
+
wait_for 'new private IP address to appear in metadata' do
|
303
|
+
device.meta_ips.include?(new_ip)
|
304
|
+
end
|
305
|
+
wait_for 'new private IP address to appear in EC2 resource' do
|
306
|
+
Client.interface_private_ips(interface_id).include?(new_ip)
|
307
|
+
end
|
308
|
+
end
|
299
309
|
{
|
300
310
|
private_ip: new_ip,
|
301
311
|
interface_id: interface_id,
|
@@ -308,6 +318,7 @@ module Aws
|
|
308
318
|
# remove a private ip using the AWS api and remove it from local config
|
309
319
|
def unassign_secondary_ip(private_ip, options = {})
|
310
320
|
do_release = !!options[:release]
|
321
|
+
do_block = options[:block] != false
|
311
322
|
|
312
323
|
find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
|
313
324
|
device = Interface[find].assert(
|
@@ -335,6 +346,16 @@ module Aws
|
|
335
346
|
network_interface_id: interface[:network_interface_id],
|
336
347
|
private_ip_addresses: [private_ip]
|
337
348
|
)
|
349
|
+
|
350
|
+
if do_block
|
351
|
+
# ensure new state has propagated to avoid race conditions
|
352
|
+
wait_for 'private IP address to be removed from metadata' do
|
353
|
+
!device.meta_ips.include?(private_ip)
|
354
|
+
end
|
355
|
+
wait_for 'private IP address to be removed from EC2 resource' do
|
356
|
+
!Client.interface_private_ips(device.interface_id).include?(private_ip)
|
357
|
+
end
|
358
|
+
end
|
338
359
|
{
|
339
360
|
private_ip: private_ip,
|
340
361
|
device_name: device.name,
|
@@ -402,8 +423,14 @@ module Aws
|
|
402
423
|
allow_reassociation: false
|
403
424
|
)
|
404
425
|
|
405
|
-
if do_block
|
406
|
-
|
426
|
+
if do_block
|
427
|
+
# ensure new state has propagated to avoid race conditions
|
428
|
+
wait_for 'public IP address to appear in metadata' do
|
429
|
+
device.public_ips.has_value?(eip[:public_ip])
|
430
|
+
end
|
431
|
+
if !Interface.test(private_ip, timeout: timeout)
|
432
|
+
raise Errors::TimeoutError, 'Timed out waiting for IP address to become active'
|
433
|
+
end
|
407
434
|
end
|
408
435
|
{
|
409
436
|
private_ip: private_ip,
|
@@ -420,6 +447,7 @@ module Aws
|
|
420
447
|
# optionally release the public ip
|
421
448
|
def dissociate_elastic_ip(address, options = {})
|
422
449
|
do_release = !!options[:release]
|
450
|
+
do_block = options[:block] != false
|
423
451
|
|
424
452
|
# assert device attributes if we've specified a device
|
425
453
|
if find = options[:device_name] || options[:device_number]
|
@@ -463,6 +491,13 @@ module Aws
|
|
463
491
|
|
464
492
|
Client.disassociate_address(association_id: eip[:association_id])
|
465
493
|
Client.release_address(allocation_id: eip[:allocation_id]) if do_release
|
494
|
+
|
495
|
+
if device && do_block
|
496
|
+
# ensure new state has propagated to avoid race conditions
|
497
|
+
wait_for 'public IP address to be removed from metadata' do
|
498
|
+
!device.public_ips.has_value?(eip[:public_ip])
|
499
|
+
end
|
500
|
+
end
|
466
501
|
{
|
467
502
|
private_ip: eip[:private_ip_address],
|
468
503
|
device_name: device && device.name,
|
@@ -578,7 +613,7 @@ module Aws
|
|
578
613
|
def wait_for(task, options = {}, &block)
|
579
614
|
errors = [*options[:rescue]]
|
580
615
|
timeout = options[:timeout] || self.timeout
|
581
|
-
interval = options[:interval] || 0.
|
616
|
+
interval = options[:interval] || 0.3
|
582
617
|
|
583
618
|
until timeout < 0
|
584
619
|
begin
|
data/lib/aws-eni/interface.rb
CHANGED
@@ -6,6 +6,9 @@ module Aws
|
|
6
6
|
module ENI
|
7
7
|
class Interface
|
8
8
|
|
9
|
+
# Class level thread-safe lock
|
10
|
+
@lock = Mutex.new
|
11
|
+
|
9
12
|
class << self
|
10
13
|
include Enumerable
|
11
14
|
|
@@ -15,8 +18,10 @@ module Aws
|
|
15
18
|
def [](index)
|
16
19
|
case index
|
17
20
|
when Integer
|
18
|
-
@
|
19
|
-
|
21
|
+
@lock.synchronize do
|
22
|
+
@instance_cache ||= []
|
23
|
+
@instance_cache[index] ||= new("eth#{index}", false)
|
24
|
+
end
|
20
25
|
when nil
|
21
26
|
self[next_available_index]
|
22
27
|
when /^(?:eth)?([0-9]+)$/
|
@@ -144,6 +149,7 @@ module Aws
|
|
144
149
|
@name = name
|
145
150
|
@device_number = $1.to_i
|
146
151
|
@route_table = @device_number + 10000
|
152
|
+
@lock = Mutex.new
|
147
153
|
configure if auto_config
|
148
154
|
end
|
149
155
|
|
@@ -166,19 +172,21 @@ module Aws
|
|
166
172
|
|
167
173
|
# Validate and return basic interface metadata
|
168
174
|
def info
|
169
|
-
|
170
|
-
|
171
|
-
@meta_cache
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
175
|
+
@lock.synchronize do
|
176
|
+
hwaddr = self.hwaddr
|
177
|
+
unless @meta_cache && @meta_cache[:hwaddr] == hwaddr
|
178
|
+
@meta_cache = Meta.connection do
|
179
|
+
raise Errors::MetaBadResponse unless Meta.interface(hwaddr, '', not_found: nil)
|
180
|
+
{
|
181
|
+
hwaddr: hwaddr,
|
182
|
+
interface_id: Meta.interface(hwaddr, 'interface-id'),
|
183
|
+
subnet_id: Meta.interface(hwaddr, 'subnet-id'),
|
184
|
+
subnet_cidr: Meta.interface(hwaddr, 'subnet-ipv4-cidr-block')
|
185
|
+
}.freeze
|
186
|
+
end
|
179
187
|
end
|
188
|
+
@meta_cache
|
180
189
|
end
|
181
|
-
@meta_cache
|
182
190
|
rescue Errors::MetaConnectionFailed
|
183
191
|
raise Errors::InvalidInterface, "Interface #{name} could not be found in the EC2 instance meta-data"
|
184
192
|
end
|
@@ -210,13 +218,25 @@ module Aws
|
|
210
218
|
list.lines.grep(/inet ([0-9\.]+)\/.* #{name}/i){ $1 }
|
211
219
|
end
|
212
220
|
|
221
|
+
# Return an array of ip addresses found in our instance metadata
|
222
|
+
def meta_ips
|
223
|
+
# hack to use cached hwaddr when available since this is often polled
|
224
|
+
# continuously for changes
|
225
|
+
hwaddr = (@meta_cache && @meta_cache[:hwaddr]) || hwaddr
|
226
|
+
Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Return a hash of local/public ip associations found in instance metadata
|
213
230
|
def public_ips
|
214
231
|
hwaddr = self.hwaddr
|
215
232
|
Hash[
|
216
233
|
Meta.connection do
|
217
234
|
Meta.interface(hwaddr, 'ipv4-associations/', not_found: '', cache: false).lines.map do |public_ip|
|
218
235
|
public_ip.strip!
|
219
|
-
|
236
|
+
unless private_ip = Meta.interface(hwaddr, "ipv4-associations/#{public_ip}", not_found: nil, cache: false)
|
237
|
+
raise Errors::MetaBadResponse
|
238
|
+
end
|
239
|
+
[ private_ip, public_ip ]
|
220
240
|
end
|
221
241
|
end
|
222
242
|
]
|
@@ -244,7 +264,6 @@ module Aws
|
|
244
264
|
changes = 0
|
245
265
|
prefix = self.prefix # prevent exists? check on each use
|
246
266
|
|
247
|
-
meta_ips = Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip)
|
248
267
|
local_primary, *local_aliases = local_ips
|
249
268
|
meta_primary, *meta_aliases = meta_ips
|
250
269
|
|
data/lib/aws-eni/meta.rb
CHANGED
@@ -5,6 +5,10 @@ require 'aws-eni/errors'
|
|
5
5
|
module Aws
|
6
6
|
module ENI
|
7
7
|
module Meta
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Per-thread open connection storage
|
11
|
+
@connections = {}
|
8
12
|
|
9
13
|
# These are the errors we trap when attempting to talk to the instance
|
10
14
|
# metadata service. Any of these imply the service is not present, not
|
@@ -21,7 +25,7 @@ module Aws
|
|
21
25
|
|
22
26
|
# Perform a GET request on an open HTTP connection to the EC2 instance
|
23
27
|
# meta-data and return the body of any 200 response.
|
24
|
-
def
|
28
|
+
def get(path, options = {})
|
25
29
|
@cache ||= {}
|
26
30
|
if @cache[path] && options[:cache] != false
|
27
31
|
@cache[path]
|
@@ -43,39 +47,46 @@ module Aws
|
|
43
47
|
|
44
48
|
# Perform a GET request on the instance metadata and return the body of
|
45
49
|
# any 200 response.
|
46
|
-
def
|
50
|
+
def instance(path, options = {})
|
47
51
|
get("/latest/meta-data/#{path}", options)
|
48
52
|
end
|
49
53
|
|
50
54
|
# Perform a GET request on the interface metadata and return the body of
|
51
55
|
# any 200 response.
|
52
|
-
def
|
56
|
+
def interface(hwaddr, path, options = {})
|
53
57
|
instance("network/interfaces/macs/#{hwaddr}/#{path}", options)
|
54
58
|
end
|
55
59
|
|
56
60
|
# Open a connection and attempt to execute the block `retries` times.
|
57
61
|
# Can specify open and read timeouts in addition to the number of retries.
|
58
|
-
def
|
59
|
-
|
62
|
+
def connection(options = {})
|
63
|
+
raise ArgumentError, 'Must be called with a block' unless block_given?
|
64
|
+
attempts = 0
|
60
65
|
retries = options[:retries] || 5
|
61
|
-
|
62
|
-
|
63
|
-
http
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
66
|
+
|
67
|
+
if (http = @connections[Thread.current]) && http.active?
|
68
|
+
yield(http)
|
69
|
+
else
|
70
|
+
begin
|
71
|
+
http = Net::HTTP.new('169.254.169.254', '80', nil)
|
72
|
+
http.open_timeout = options[:open_timeout] || 5
|
73
|
+
http.read_timeout = options[:read_timeout] || 5
|
74
|
+
|
75
|
+
@connections[Thread.current] = http.start
|
76
|
+
yield(http)
|
77
|
+
rescue *FAILURES => e
|
78
|
+
if attempts < retries
|
79
|
+
# increasing cooldown time on each attempt
|
80
|
+
Kernel.sleep(1.2 ** attempts)
|
81
|
+
attempts += 1
|
82
|
+
retry
|
83
|
+
else
|
84
|
+
raise Errors::MetaConnectionFailed, "EC2 Metadata request failed after #{retries} retries."
|
85
|
+
end
|
86
|
+
ensure
|
87
|
+
http = @connections.delete(Thread.current)
|
88
|
+
http.finish if http && http.active?
|
76
89
|
end
|
77
|
-
ensure
|
78
|
-
@open_connection = nil
|
79
90
|
end
|
80
91
|
end
|
81
92
|
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Greiling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gli
|