aws-eni 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/aws-eni +29 -22
- data/lib/aws-eni.rb +125 -202
- data/lib/aws-eni/client.rb +152 -0
- data/lib/aws-eni/errors.rb +25 -12
- data/lib/aws-eni/{ifconfig.rb → interface.rb} +29 -32
- data/lib/aws-eni/meta.rb +40 -30
- data/lib/aws-eni/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31f890a936163bc23d0c7bec943936be043fda58
|
4
|
+
data.tar.gz: 4e5d5ac127109e761af68f26ce33caff51a00121
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f71962db0b6c220aef90fbc6cf3c4c57d2100b90177e7a34f2a33fd78f5c67bd262c2b373269f70299c325d0a5aef1891f5a79cf0a222580ca720290957f69ea
|
7
|
+
data.tar.gz: 62990ff085b4609f33745d40571ec86ba3ef1b00cc568a84ba8eea2c0b5f3534d90a7960a96d673f6b77b73c0ab38a22aff9fbeb9e56cb1d58467833d0f46d2f
|
data/bin/aws-eni
CHANGED
@@ -25,7 +25,7 @@ switch [:V,:verbose], negatable: false
|
|
25
25
|
|
26
26
|
pre do |opt|
|
27
27
|
Aws::ENI.timeout 90
|
28
|
-
Aws::ENI
|
28
|
+
Aws::ENI.verbose opt[:verbose]
|
29
29
|
true
|
30
30
|
end
|
31
31
|
|
@@ -164,8 +164,8 @@ command [:enable,:up] do |c|
|
|
164
164
|
c.action do |global,opts,args|
|
165
165
|
help_now! "Incorrect number of arguments" unless args.count.between?(0,1)
|
166
166
|
args.delete('all')
|
167
|
-
Aws::ENI::
|
168
|
-
puts "interfaces enabled"
|
167
|
+
enabled = Aws::ENI::enable(args.first)
|
168
|
+
puts "#{enabled} interfaces enabled"
|
169
169
|
end
|
170
170
|
end
|
171
171
|
|
@@ -183,14 +183,8 @@ command [:disable,:down] do |c|
|
|
183
183
|
c.action do |global,opts,args|
|
184
184
|
help_now! "Incorrect number of arguments" unless args.count.between?(0,1)
|
185
185
|
args.delete('all')
|
186
|
-
Aws::ENI::
|
187
|
-
|
188
|
-
warn 'skipping eth0'
|
189
|
-
else
|
190
|
-
dev.disable
|
191
|
-
puts "interface #{dev.name} disabled"
|
192
|
-
end
|
193
|
-
end
|
186
|
+
disabled = Aws::ENI::disable(args.first)
|
187
|
+
puts "#{disabled} interfaces disabled"
|
194
188
|
end
|
195
189
|
end
|
196
190
|
|
@@ -250,12 +244,21 @@ command [:attach] do |c|
|
|
250
244
|
else
|
251
245
|
args.delete('new')
|
252
246
|
params = parse_args args, :subnet_id, :security_groups, :primary_ip
|
253
|
-
Aws::ENI.
|
247
|
+
Aws::ENI.assert_interface_access if config
|
254
248
|
interface = Aws::ENI.create_interface(params)
|
255
249
|
puts "interface #{interface[:interface_id]} created on #{interface[:subnet_id]}"
|
256
250
|
id = interface[:interface_id]
|
251
|
+
new_interface = true
|
252
|
+
end
|
253
|
+
begin
|
254
|
+
device = Aws::ENI.attach_interface(id, enable: config, configure: config, block: opts[:block])
|
255
|
+
rescue Aws::ENI::Errors::ClientOperationError => e
|
256
|
+
warn e.message
|
257
|
+
if new_interface && Aws::ENI.clean_interfaces(id, safe_mode: false)
|
258
|
+
puts "interface #{id} deleted"
|
259
|
+
end
|
260
|
+
exit_now! "attachment failed"
|
257
261
|
end
|
258
|
-
device = Aws::ENI.attach_interface(id, enable: config, configure: config, block: opts[:block])
|
259
262
|
puts "interface #{device[:interface_id]} attached to #{device[:device_name]}"
|
260
263
|
puts "device #{device[:device_name]} enabled and configured" if config
|
261
264
|
end
|
@@ -295,7 +298,7 @@ command [:detach] do |c|
|
|
295
298
|
device[:public_ips].each do |address|
|
296
299
|
if device[:released]
|
297
300
|
puts "EIP #{address[:public_ip]} (#{address[:allocation_id]}) dissociated and released"
|
298
|
-
|
301
|
+
elsif device[:deleted]
|
299
302
|
puts "EIP #{address[:public_ip]} (#{address[:allocation_id]}) dissociated"
|
300
303
|
end
|
301
304
|
end
|
@@ -366,7 +369,7 @@ command [:assign] do |c|
|
|
366
369
|
help_now! "Missing argument" if device.nil?
|
367
370
|
|
368
371
|
params.merge! configure: !opts[:noconfig], block: opts[:block]
|
369
|
-
Aws::ENI.
|
372
|
+
Aws::ENI.assert_interface_access if params[:configure]
|
370
373
|
|
371
374
|
assignment = Aws::ENI.assign_secondary_ip(device, params)
|
372
375
|
puts "IP #{assignment[:private_ip]} assigned to #{assignment[:device_name]} (#{assignment[:interface_id]})"
|
@@ -498,8 +501,8 @@ command [:test] do |c|
|
|
498
501
|
c.action do |global,opts,args|
|
499
502
|
help_now! "Too many arguments" if args.count > 1
|
500
503
|
|
501
|
-
print '
|
502
|
-
if Aws::ENI.
|
504
|
+
print 'Interface permissions test... '
|
505
|
+
if Aws::ENI.has_interface_access?
|
503
506
|
puts 'success!'
|
504
507
|
else
|
505
508
|
puts 'failed'
|
@@ -507,7 +510,7 @@ command [:test] do |c|
|
|
507
510
|
end
|
508
511
|
|
509
512
|
print 'AWS EC2 permissions test... '
|
510
|
-
if Aws::ENI.
|
513
|
+
if Aws::ENI.has_client_access?
|
511
514
|
puts 'success!'
|
512
515
|
else
|
513
516
|
puts 'failed'
|
@@ -519,10 +522,14 @@ end
|
|
519
522
|
|
520
523
|
# error handling
|
521
524
|
|
522
|
-
on_error do |
|
523
|
-
|
524
|
-
|
525
|
-
|
525
|
+
on_error do |error|
|
526
|
+
case error
|
527
|
+
when Aws::ENI::Errors::InterfacePermissionError
|
528
|
+
warn 'Error: This action requires super-user privileges (try sudo)'
|
529
|
+
when Aws::ENI::Errors::ClientPermissionError
|
530
|
+
warn 'Error: Insufficient EC2 operation privileges (check AWS IAM policy)'
|
531
|
+
when Aws::ENI::Errors::ServiceError
|
532
|
+
warn "Error: #{error.message}"
|
526
533
|
else
|
527
534
|
true
|
528
535
|
end
|
data/lib/aws-eni.rb
CHANGED
@@ -1,30 +1,29 @@
|
|
1
1
|
require 'time'
|
2
|
-
require 'aws-sdk'
|
3
2
|
require 'aws-eni/version'
|
4
3
|
require 'aws-eni/errors'
|
5
4
|
require 'aws-eni/meta'
|
6
|
-
require 'aws-eni/
|
5
|
+
require 'aws-eni/client'
|
6
|
+
require 'aws-eni/interface'
|
7
7
|
|
8
8
|
module Aws
|
9
9
|
module ENI
|
10
10
|
extend self
|
11
11
|
|
12
12
|
def environment
|
13
|
-
@environment ||=
|
14
|
-
hwaddr =
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
raise EnvironmentError, "Unable to load EC2 meta-data"
|
13
|
+
@environment ||= Meta.connection do
|
14
|
+
hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
|
15
|
+
{
|
16
|
+
instance_id: Meta.instance('instance-id'),
|
17
|
+
availability_zone: Meta.instance('placement/availability-zone'),
|
18
|
+
region: Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1'),
|
19
|
+
vpc_id: Meta.interface(hwaddr, 'vpc-id'),
|
20
|
+
vpc_cidr: Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
|
21
|
+
}.freeze
|
22
|
+
end
|
23
|
+
rescue Errors::MetaNotFound
|
24
|
+
raise Errors::EnvironmentError, 'Unable to detect VPC, library incompatible with EC2-Classic'
|
25
|
+
rescue Errors::MetaConnectionFailed
|
26
|
+
raise Errors::EnvironmentError, 'Unable to load EC2 meta-data'
|
28
27
|
end
|
29
28
|
|
30
29
|
def owner_tag(new_owner = nil)
|
@@ -37,40 +36,54 @@ module Aws
|
|
37
36
|
@timeout ||= 30
|
38
37
|
end
|
39
38
|
|
40
|
-
def
|
41
|
-
|
39
|
+
def verbose(set_verbose = nil)
|
40
|
+
unless set_verbose.nil?
|
41
|
+
@verbose = !!set_verbose
|
42
|
+
Interface.verbose = @verbose
|
43
|
+
end
|
44
|
+
@verbose
|
42
45
|
end
|
43
46
|
|
44
47
|
# return our internal model of this instance's network configuration on AWS
|
45
48
|
def list(filter = nil)
|
46
|
-
|
49
|
+
Interface.filter(filter).map(&:to_h) if environment
|
47
50
|
end
|
48
51
|
|
49
52
|
# sync local machine's network interface config with the EC2 meta-data
|
50
53
|
# pass dry_run option to check whether configuration is out of sync without
|
51
54
|
# modifying it
|
52
55
|
def configure(filter = nil, options = {})
|
53
|
-
|
56
|
+
Interface.configure(filter, options) if environment
|
54
57
|
end
|
55
58
|
|
56
59
|
# clear local machine's network interface config
|
57
60
|
def deconfigure(filter = nil)
|
58
|
-
|
61
|
+
Interface.deconfigure(filter) if environment
|
62
|
+
end
|
63
|
+
|
64
|
+
# enable one or more network interfaces
|
65
|
+
def enable(filter = nil)
|
66
|
+
Interface.filter(filter).select{ |dev| !dev.enabled? }.each(&:enable).count
|
67
|
+
end
|
68
|
+
|
69
|
+
# disable one or more network interfaces
|
70
|
+
def disable(filter = nil)
|
71
|
+
Interface.filter(filter).select{ |dev| dev.enabled? && dev.name != 'eth0' }.each(&:disable).count
|
59
72
|
end
|
60
73
|
|
61
74
|
# create network interface
|
62
75
|
def create_interface(options = {})
|
63
76
|
timestamp = Time.now.xmlschema
|
64
77
|
params = {}
|
65
|
-
params[:subnet_id] = options[:subnet_id] ||
|
78
|
+
params[:subnet_id] = options[:subnet_id] || Interface.first.subnet_id
|
66
79
|
params[:private_ip_address] = options[:primary_ip] if options[:primary_ip]
|
67
80
|
params[:groups] = [*options[:security_groups]] if options[:security_groups]
|
68
81
|
params[:description] = "generated by #{owner_tag} from #{environment[:instance_id]} on #{timestamp}"
|
69
82
|
|
70
|
-
|
71
|
-
wait_for 'the interface to be created', rescue:
|
72
|
-
if
|
73
|
-
|
83
|
+
interface = Client.create_network_interface(params)[:network_interface]
|
84
|
+
wait_for 'the interface to be created', rescue: Errors::UnknownInterface do
|
85
|
+
if Client.describe_interface(interface[:network_interface_id])
|
86
|
+
Client.create_tags(resources: [interface[:network_interface_id]], tags: [
|
74
87
|
{ key: 'created by', value: owner_tag },
|
75
88
|
{ key: 'created on', value: timestamp },
|
76
89
|
{ key: 'created from', value: environment[:instance_id] }
|
@@ -78,9 +91,9 @@ module Aws
|
|
78
91
|
end
|
79
92
|
end
|
80
93
|
{
|
81
|
-
interface_id:
|
82
|
-
subnet_id:
|
83
|
-
api_response:
|
94
|
+
interface_id: interface[:network_interface_id],
|
95
|
+
subnet_id: interface[:subnet_id],
|
96
|
+
api_response: interface
|
84
97
|
}
|
85
98
|
end
|
86
99
|
|
@@ -88,19 +101,23 @@ module Aws
|
|
88
101
|
def attach_interface(id, options = {})
|
89
102
|
do_enable = true unless options[:enable] == false
|
90
103
|
do_config = true unless options[:configure] == false
|
91
|
-
|
104
|
+
assert_interface_access if do_config || do_enable
|
92
105
|
|
93
|
-
device =
|
106
|
+
device = Interface[options[:device_number] || options[:name]].assert(exists: false)
|
94
107
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
108
|
+
begin
|
109
|
+
response = Client.attach_network_interface(
|
110
|
+
network_interface_id: id,
|
111
|
+
instance_id: environment[:instance_id],
|
112
|
+
device_index: device.device_number
|
113
|
+
)
|
114
|
+
rescue EC2::Errors::AttachmentLimitExceeded
|
115
|
+
raise Errors::ClientOperationError, "Unable to attach #{id} to #{device.name} (attachment limit exceeded)"
|
116
|
+
end
|
100
117
|
|
101
118
|
if options[:block] || do_config || do_enable
|
102
|
-
wait_for 'the interface to attach', rescue:
|
103
|
-
device.exists? &&
|
119
|
+
wait_for 'the interface to attach', rescue: Errors::MetaNotFound do
|
120
|
+
device.exists? && Client.interface_attached(device.interface_id)
|
104
121
|
end
|
105
122
|
end
|
106
123
|
device.configure if do_config
|
@@ -117,30 +134,25 @@ module Aws
|
|
117
134
|
|
118
135
|
# detach network interface
|
119
136
|
def detach_interface(id, options = {})
|
120
|
-
device =
|
137
|
+
device = Interface[id].assert(
|
121
138
|
exists: true,
|
122
139
|
device_name: options[:device_name],
|
123
140
|
interface_id: options[:interface_id],
|
124
141
|
device_number: options[:device_number]
|
125
142
|
)
|
143
|
+
interface = Client.describe_interface(device.interface_id)
|
144
|
+
|
126
145
|
if device.name == 'eth0'
|
127
|
-
raise
|
146
|
+
raise Errors::InvalidInterface, 'For safety, interface eth0 cannot be detached.'
|
128
147
|
end
|
129
|
-
interface_id = device.interface_id
|
130
148
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
},{
|
135
|
-
name: 'network-interface-id',
|
136
|
-
values: [interface_id]
|
137
|
-
}])
|
138
|
-
interface = response[:network_interfaces].first
|
139
|
-
raise UnknownInterfaceError, "Interface attachment could not be located" unless interface
|
149
|
+
unless interface[:attachment] && interface[:attachment][:instance_id] == environment[:instance_id]
|
150
|
+
raise Errors::UnknownInterface, "Interface #{interface_id} is not attached to this machine"
|
151
|
+
end
|
140
152
|
|
141
153
|
device.disable
|
142
154
|
device.deconfigure
|
143
|
-
|
155
|
+
Client.detach_network_interface(
|
144
156
|
attachment_id: interface[:attachment][:attachment_id],
|
145
157
|
force: true
|
146
158
|
)
|
@@ -161,12 +173,12 @@ module Aws
|
|
161
173
|
|
162
174
|
if options[:block] || do_delete
|
163
175
|
wait_for 'the interface to detach', interval: 0.3 do
|
164
|
-
!device.exists? &&
|
176
|
+
!device.exists? && !Client.interface_attached(interface[:network_interface_id])
|
165
177
|
end
|
166
178
|
end
|
167
|
-
|
179
|
+
Client.delete_network_interface(network_interface_id: interface[:network_interface_id]) if do_delete
|
168
180
|
{
|
169
|
-
interface_id:
|
181
|
+
interface_id: interface[:network_interface_id],
|
170
182
|
device_name: device.name,
|
171
183
|
device_number: device.device_number,
|
172
184
|
created_by_us: created_by_us,
|
@@ -196,14 +208,14 @@ module Aws
|
|
196
208
|
when /^#{environment[:region]}[a-z]$/
|
197
209
|
filters << { name: 'availability-zone', values: [filter] }
|
198
210
|
else
|
199
|
-
raise
|
211
|
+
raise Errors::InvalidInput, "Unknown interface attribute: #{filter}"
|
200
212
|
end
|
201
213
|
end
|
202
214
|
if safe_mode
|
203
215
|
filters << { name: 'tag:created by', values: [owner_tag] }
|
204
216
|
end
|
205
217
|
|
206
|
-
descriptions =
|
218
|
+
descriptions = Client.describe_network_interfaces(filters: filters)
|
207
219
|
interfaces = descriptions[:network_interfaces].select do |interface|
|
208
220
|
created_recently = interface.tag_set.any? do |tag|
|
209
221
|
begin
|
@@ -221,7 +233,7 @@ module Aws
|
|
221
233
|
dissociate_elastic_ip(assoc[:allocation_id], release: true) if do_release
|
222
234
|
end
|
223
235
|
end
|
224
|
-
|
236
|
+
Client.delete_network_interface(network_interface_id: interface[:network_interface_id])
|
225
237
|
true
|
226
238
|
end
|
227
239
|
end
|
@@ -235,37 +247,37 @@ module Aws
|
|
235
247
|
|
236
248
|
# add new private ip using the AWS api and add it to our local ip config
|
237
249
|
def assign_secondary_ip(id, options = {})
|
238
|
-
device =
|
250
|
+
device = Interface[id].assert(
|
239
251
|
exists: true,
|
240
252
|
device_name: options[:device_name],
|
241
253
|
interface_id: options[:interface_id],
|
242
254
|
device_number: options[:device_number]
|
243
255
|
)
|
244
256
|
interface_id = device.interface_id
|
245
|
-
current_ips =
|
257
|
+
current_ips = Client.interface_private_ips(interface_id)
|
246
258
|
new_ip = options[:private_ip]
|
247
259
|
|
248
260
|
if new_ip
|
249
261
|
if current_ips.include?(new_ip)
|
250
|
-
raise
|
262
|
+
raise Errors::ClientOperationError, "IP #{new_ip} already assigned to #{device.name}"
|
251
263
|
end
|
252
|
-
|
264
|
+
Client.assign_private_ip_addresses(
|
253
265
|
network_interface_id: interface_id,
|
254
266
|
private_ip_addresses: [new_ip],
|
255
267
|
allow_reassignment: false
|
256
268
|
)
|
257
269
|
wait_for 'private ip address to be assigned' do
|
258
|
-
|
270
|
+
Client.interface_private_ips(interface_id).include?(new_ip) ||
|
259
271
|
device.local_ips.include?(new_ip)
|
260
272
|
end
|
261
273
|
else
|
262
|
-
|
274
|
+
Client.assign_private_ip_addresses(
|
263
275
|
network_interface_id: interface_id,
|
264
276
|
secondary_private_ip_address_count: 1,
|
265
277
|
allow_reassignment: false
|
266
278
|
)
|
267
279
|
wait_for 'new private ip address to be assigned' do
|
268
|
-
new_ips =
|
280
|
+
new_ips = Client.interface_private_ips(interface_id) - current_ips
|
269
281
|
new_ips = device.local_ips - current_ips if new_ips.empty?
|
270
282
|
new_ip = new_ips.first if new_ips
|
271
283
|
end
|
@@ -273,8 +285,8 @@ module Aws
|
|
273
285
|
|
274
286
|
unless options[:configure] == false
|
275
287
|
device.add_alias(new_ip)
|
276
|
-
if options[:block] && !
|
277
|
-
raise TimeoutError,
|
288
|
+
if options[:block] && !Interface.test(new_ip, target: device.gateway)
|
289
|
+
raise Errors::TimeoutError, 'Timed out waiting for IP address to become active'
|
278
290
|
end
|
279
291
|
end
|
280
292
|
{
|
@@ -291,22 +303,20 @@ module Aws
|
|
291
303
|
do_release = !!options[:release]
|
292
304
|
|
293
305
|
find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
|
294
|
-
device =
|
306
|
+
device = Interface[find].assert(
|
295
307
|
exists: true,
|
296
308
|
device_name: options[:device_name],
|
297
309
|
interface_id: options[:interface_id],
|
298
310
|
device_number: options[:device_number]
|
299
311
|
)
|
300
312
|
|
301
|
-
|
302
|
-
interface = resp[:network_interfaces].first
|
303
|
-
raise UnknownInterfaceError, "Interface attachment could not be located" unless interface
|
313
|
+
interface = Client.describe_interface(device.interface_id)
|
304
314
|
|
305
315
|
unless addr_info = interface[:private_ip_addresses].find { |addr| addr[:private_ip_address] == private_ip }
|
306
|
-
raise
|
316
|
+
raise Errors::ClientOperationError, "IP #{private_ip} not found on #{device.name}"
|
307
317
|
end
|
308
318
|
if addr_info[:primary]
|
309
|
-
raise
|
319
|
+
raise Errors::ClientOperationError, 'The primary IP address of an interface cannot be unassigned'
|
310
320
|
end
|
311
321
|
|
312
322
|
if assoc = addr_info[:association]
|
@@ -314,7 +324,7 @@ module Aws
|
|
314
324
|
end
|
315
325
|
|
316
326
|
device.remove_alias(private_ip)
|
317
|
-
|
327
|
+
Client.unassign_private_ip_addresses(
|
318
328
|
network_interface_id: interface[:network_interface_id],
|
319
329
|
private_ip_addresses: [private_ip]
|
320
330
|
)
|
@@ -331,10 +341,10 @@ module Aws
|
|
331
341
|
|
332
342
|
# associate a private ip with an elastic ip through the AWS api
|
333
343
|
def associate_elastic_ip(private_ip, options = {})
|
334
|
-
raise
|
344
|
+
raise Errors::MissingInput, 'You must specify a private IP address' unless private_ip
|
335
345
|
|
336
346
|
find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
|
337
|
-
device =
|
347
|
+
device = Interface[find].assert(
|
338
348
|
exists: true,
|
339
349
|
private_ip: private_ip,
|
340
350
|
device_name: options[:device_name],
|
@@ -344,27 +354,27 @@ module Aws
|
|
344
354
|
options[:public_ip] ||= options[:allocation_id]
|
345
355
|
|
346
356
|
if public_ip = device.public_ips[private_ip]
|
347
|
-
raise
|
357
|
+
raise Errors::ClientOperationError, "IP #{private_ip} already has an associated EIP (#{public_ip})"
|
348
358
|
end
|
349
359
|
|
350
360
|
if options[:public_ip]
|
351
|
-
eip = describe_address(options[:public_ip])
|
361
|
+
eip = Client.describe_address(options[:public_ip])
|
352
362
|
if options[:allocation_id] && eip[:allocation_id] != options[:allocation_id]
|
353
|
-
raise
|
363
|
+
raise Errors::InvalidAddress, "EIP #{eip[:public_ip]} (#{eip[:allocation_id]}) does not match #{options[:allocation_id]}"
|
354
364
|
end
|
355
365
|
else
|
356
366
|
eip = allocate_elastic_ip
|
357
367
|
end
|
358
368
|
|
359
|
-
resp =
|
369
|
+
resp = Client.associate_address(
|
360
370
|
network_interface_id: device.interface_id,
|
361
371
|
allocation_id: eip[:allocation_id],
|
362
372
|
private_ip_address: private_ip,
|
363
373
|
allow_reassociation: false
|
364
374
|
)
|
365
375
|
|
366
|
-
if options[:block] && !
|
367
|
-
raise TimeoutError,
|
376
|
+
if options[:block] && !Interface.test(private_ip)
|
377
|
+
raise Errors::TimeoutError, 'Timed out waiting for ip address to become active'
|
368
378
|
end
|
369
379
|
{
|
370
380
|
private_ip: private_ip,
|
@@ -383,7 +393,7 @@ module Aws
|
|
383
393
|
|
384
394
|
# assert device attributes if we've specified a device
|
385
395
|
if find = options[:device_name] || options[:device_number]
|
386
|
-
device =
|
396
|
+
device = Interface[find].assert(
|
387
397
|
device_name: options[:device_name],
|
388
398
|
device_number: options[:device_number],
|
389
399
|
interface_id: options[:interface_id],
|
@@ -393,36 +403,36 @@ module Aws
|
|
393
403
|
end
|
394
404
|
|
395
405
|
# get our address info
|
396
|
-
eip = describe_address(address)
|
397
|
-
device ||=
|
406
|
+
eip = Client.describe_address(address)
|
407
|
+
device ||= Interface.find { |dev| dev.interface_id == eip[:network_interface_id] }
|
398
408
|
|
399
409
|
# assert eip attributes if options provided
|
400
410
|
if options[:private_ip] && eip[:private_ip_address] != options[:private_ip]
|
401
|
-
raise
|
411
|
+
raise Errors::InvalidAddress, "#{address} is not associated with IP #{options[:private_ip]}"
|
402
412
|
end
|
403
413
|
if options[:public_ip] && eip[:public_ip] != options[:public_ip]
|
404
|
-
raise
|
414
|
+
raise Errors::InvalidAddress, "#{address} is not associated with public IP #{options[:public_ip]}"
|
405
415
|
end
|
406
416
|
if options[:allocation_id] && eip[:allocation_id] != options[:allocation_id]
|
407
|
-
raise
|
417
|
+
raise Errors::InvalidAddress, "#{address} is not associated with allocation ID #{options[:allocation_id]}"
|
408
418
|
end
|
409
419
|
if options[:association_id] && eip[:association_id] != options[:association_id]
|
410
|
-
raise
|
420
|
+
raise Errors::InvalidAddress, "#{address} is not associated with association ID #{options[:association_id]}"
|
411
421
|
end
|
412
422
|
if options[:interface_id] && eip[:network_interface_id] != options[:interface_id]
|
413
|
-
raise
|
423
|
+
raise Errors::InvalidAddress, "#{address} is not associated with interface ID #{options[:interface_id]}"
|
414
424
|
end
|
415
425
|
|
416
426
|
if device
|
417
427
|
if device.name == 'eth0' && device.local_ips.first == eip[:private_ip_address]
|
418
|
-
raise
|
428
|
+
raise Errors::ClientOperationError, 'For safety, a public address cannot be dissociated from the primary IP on eth0'
|
419
429
|
end
|
420
|
-
elsif
|
421
|
-
raise
|
430
|
+
elsif Client.interface_attached(eip[:network_interface_id])
|
431
|
+
raise Errors::ClientOperationError, "#{address} is associated with an interface attached to another machine"
|
422
432
|
end
|
423
433
|
|
424
|
-
|
425
|
-
|
434
|
+
Client.disassociate_address(association_id: eip[:association_id])
|
435
|
+
Client.release_address(allocation_id: eip[:allocation_id]) if do_release
|
426
436
|
{
|
427
437
|
private_ip: eip[:private_ip_address],
|
428
438
|
device_name: device && device.name,
|
@@ -436,7 +446,7 @@ module Aws
|
|
436
446
|
|
437
447
|
# allocate a new elastic ip address
|
438
448
|
def allocate_elastic_ip
|
439
|
-
eip =
|
449
|
+
eip = Client.allocate_address(domain: 'vpc')
|
440
450
|
{
|
441
451
|
public_ip: eip[:public_ip],
|
442
452
|
allocation_id: eip[:allocation_id]
|
@@ -445,127 +455,41 @@ module Aws
|
|
445
455
|
|
446
456
|
# release the specified elastic ip address
|
447
457
|
def release_elastic_ip(ip)
|
448
|
-
eip = describe_address(ip)
|
458
|
+
eip = Client.describe_address(ip)
|
449
459
|
if eip[:association_id]
|
450
|
-
raise
|
460
|
+
raise Errors::ClientOperationError, "Elastic IP #{eip[:public_ip]} (#{eip[:allocation_id]}) is currently in use"
|
451
461
|
end
|
452
|
-
|
462
|
+
Client.release_address(allocation_id: eip[:allocation_id])
|
453
463
|
{
|
454
464
|
public_ip: eip[:public_ip],
|
455
465
|
allocation_id: eip[:allocation_id]
|
456
466
|
}
|
457
467
|
end
|
458
468
|
|
459
|
-
# test whether we have permission to modify our
|
460
|
-
def
|
461
|
-
|
469
|
+
# test whether we have permission to modify our machine's interface configuration
|
470
|
+
def has_interface_access?
|
471
|
+
Interface.mutable?
|
462
472
|
end
|
463
473
|
|
464
|
-
|
465
|
-
|
474
|
+
# throw exception if we cannot modify our machine's interface configuration
|
475
|
+
def assert_interface_access
|
476
|
+
raise Errors::InterfacePermissionError, 'Insufficient user priveleges to configure network interfaces' unless has_interface_access?
|
466
477
|
end
|
467
478
|
|
468
|
-
# test whether we have
|
469
|
-
#
|
470
|
-
def
|
471
|
-
|
472
|
-
test_methods = {
|
473
|
-
describe_network_interfaces: nil,
|
474
|
-
create_network_interface: {
|
475
|
-
subnet_id: 'subnet-abcd1234'
|
476
|
-
},
|
477
|
-
attach_network_interface: {
|
478
|
-
network_interface_id: 'eni-abcd1234',
|
479
|
-
instance_id: 'i-abcd1234',
|
480
|
-
device_index: 0
|
481
|
-
},
|
482
|
-
detach_network_interface: {
|
483
|
-
attachment_id: 'eni-attach-abcd1234'
|
484
|
-
},
|
485
|
-
delete_network_interface: {
|
486
|
-
network_interface_id: 'eni-abcd1234'
|
487
|
-
},
|
488
|
-
create_tags: {
|
489
|
-
resources: ['eni-abcd1234'],
|
490
|
-
tags: []
|
491
|
-
},
|
492
|
-
describe_addresses: nil,
|
493
|
-
allocate_address: nil,
|
494
|
-
release_address: {
|
495
|
-
dry_run: true,
|
496
|
-
allocation_id: 'eipalloc-no_exist'
|
497
|
-
},
|
498
|
-
associate_address: {
|
499
|
-
allocation_id: 'eipalloc-no_exist',
|
500
|
-
network_interface_id: 'eni-abcd1234'
|
501
|
-
}
|
502
|
-
# has no dry_run method
|
503
|
-
# assign_private_ip_addresses: {
|
504
|
-
# network_interface_id: 'eni-abcd1234'
|
505
|
-
# }
|
506
|
-
}
|
507
|
-
test_methods.each do |method, params|
|
508
|
-
begin
|
509
|
-
params ||= {}
|
510
|
-
params[:dry_run] = true
|
511
|
-
client.public_send(method, params)
|
512
|
-
raise Error, "Unexpected behavior while testing AWS API access"
|
513
|
-
rescue Aws::EC2::Errors::DryRunOperation
|
514
|
-
# success
|
515
|
-
rescue Aws::EC2::Errors::InvalidAllocationIDNotFound
|
516
|
-
# release_address does not properly support dry_run
|
517
|
-
rescue Aws::EC2::Errors::UnauthorizedOperation
|
518
|
-
return false
|
519
|
-
end
|
520
|
-
end
|
521
|
-
true
|
479
|
+
# test whether we have permission to perform all necessary EC2 operations
|
480
|
+
# within our given AWS access credentials
|
481
|
+
def has_client_access?
|
482
|
+
Client.has_access?
|
522
483
|
end
|
523
484
|
|
524
|
-
|
525
|
-
|
485
|
+
# throw exception if we do not have permissions to perform all needed EC2
|
486
|
+
# operations with our given AWS credentials
|
487
|
+
def assert_client_access
|
488
|
+
raise Errors::ClientPermissionError, 'Insufficient access to EC2 operations for network interface modification' unless has_client_access?
|
526
489
|
end
|
527
490
|
|
528
491
|
private
|
529
492
|
|
530
|
-
# use either an ip address or allocation id
|
531
|
-
def describe_address(address)
|
532
|
-
filter_by = case address
|
533
|
-
when /^eipalloc-/
|
534
|
-
'allocation-id'
|
535
|
-
when /^eipassoc-/
|
536
|
-
'association-id'
|
537
|
-
else
|
538
|
-
if IPAddr.new(environment[:vpc_cidr]) === IPAddr.new(address)
|
539
|
-
'private-ip-address'
|
540
|
-
else
|
541
|
-
'public-ip'
|
542
|
-
end
|
543
|
-
end
|
544
|
-
resp = client.describe_addresses(filters: [
|
545
|
-
{ name: 'domain', values: ['vpc'] },
|
546
|
-
{ name: filter_by, values: [address] }
|
547
|
-
])
|
548
|
-
raise InvalidParameterError, "IP #{address} could not be located" if resp[:addresses].empty?
|
549
|
-
resp[:addresses].first
|
550
|
-
end
|
551
|
-
|
552
|
-
def interface_ips(id)
|
553
|
-
resp = client.describe_network_interfaces(network_interface_ids: [id])
|
554
|
-
interface = resp[:network_interfaces].first
|
555
|
-
if interface && interface[:private_ip_addresses]
|
556
|
-
primary = interface[:private_ip_addresses].find { |ip| ip[:primary] }
|
557
|
-
interface[:private_ip_addresses].map { |ip| ip[:private_ip_address] }.tap do |ips|
|
558
|
-
# ensure primary ip is first in the list
|
559
|
-
ips.unshift(*ips.delete(primary[:private_ip_address])) if primary
|
560
|
-
end
|
561
|
-
end
|
562
|
-
end
|
563
|
-
|
564
|
-
def interface_status(id)
|
565
|
-
resp = client.describe_network_interfaces(network_interface_ids: [id])
|
566
|
-
resp[:network_interfaces].first[:status] unless resp[:network_interfaces].empty?
|
567
|
-
end
|
568
|
-
|
569
493
|
def wait_for(task, options = {}, &block)
|
570
494
|
errors = [*options[:rescue]]
|
571
495
|
timeout = options[:timeout] || self.timeout
|
@@ -574,13 +498,12 @@ module Aws
|
|
574
498
|
until timeout < 0
|
575
499
|
begin
|
576
500
|
break if block.call
|
577
|
-
rescue
|
578
|
-
raise unless errors.any? { |error| error === e }
|
501
|
+
rescue *errors => e
|
579
502
|
end
|
580
503
|
sleep interval
|
581
504
|
timeout -= interval
|
582
505
|
end
|
583
|
-
raise TimeoutError, "Timed out waiting for #{task}" unless timeout > 0
|
506
|
+
raise Errors::TimeoutError, "Timed out waiting for #{task}" unless timeout > 0
|
584
507
|
end
|
585
508
|
end
|
586
509
|
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'aws-eni/errors'
|
3
|
+
require 'aws-eni/meta'
|
4
|
+
|
5
|
+
module Aws
|
6
|
+
module ENI
|
7
|
+
module Client
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# determine the region from instance metadata
|
11
|
+
def region
|
12
|
+
Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1')
|
13
|
+
rescue Errors::MetaConnectionFailed
|
14
|
+
raise Errors::EnvironmentError, 'Unable to load EC2 meta-data'
|
15
|
+
end
|
16
|
+
|
17
|
+
# determine the vpc cidr block from instance metadata
|
18
|
+
def vpc_cidr
|
19
|
+
hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
|
20
|
+
Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
|
21
|
+
rescue Errors::MetaConnectionFailed, Errors::MetaNotFound
|
22
|
+
raise Errors::EnvironmentError, 'Unable to load EC2 meta-data'
|
23
|
+
end
|
24
|
+
|
25
|
+
# lazy-load our ec2 client
|
26
|
+
def client
|
27
|
+
@client ||= EC2::Client.new(region: region)
|
28
|
+
rescue StandardError => e
|
29
|
+
raise Errors::EnvironmentError, 'Unable to initialize EC2 client'
|
30
|
+
end
|
31
|
+
|
32
|
+
# pass along method calls to our lazy-loaded api client
|
33
|
+
def method_missing(method, *args)
|
34
|
+
client.public_send(method, *args)
|
35
|
+
rescue EC2::Errors::UnauthorizedOperation => e
|
36
|
+
raise Errors::ClientPermissionError, "Operation not permitted: #{e.message}"
|
37
|
+
rescue EC2::Errors::ServiceError => e
|
38
|
+
error = e.class.to_s.gsub(/^.*::/, '')
|
39
|
+
raise Errors::ClientOperationError, "EC2 service error (#{error}: #{e.message})"
|
40
|
+
end
|
41
|
+
|
42
|
+
# retrieve a single interface resource
|
43
|
+
def describe_interface(id)
|
44
|
+
resp = describe_network_interfaces(network_interface_ids: [id])
|
45
|
+
raise Errors::UnknownInterface, "Interface #{id} could not be located" if resp[:network_interfaces].empty?
|
46
|
+
resp[:network_interfaces].first
|
47
|
+
end
|
48
|
+
|
49
|
+
# retrieve a single address resource by public ip, associated private ip,
|
50
|
+
# allocation id, or association id
|
51
|
+
def describe_address(address)
|
52
|
+
filter_by = case address
|
53
|
+
when /^eipalloc-/
|
54
|
+
'allocation-id'
|
55
|
+
when /^eipassoc-/
|
56
|
+
'association-id'
|
57
|
+
else
|
58
|
+
if IPAddr.new(vpc_cidr) === IPAddr.new(address)
|
59
|
+
'private-ip-address'
|
60
|
+
else
|
61
|
+
'public-ip'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
resp = describe_addresses(filters: [
|
65
|
+
{ name: 'domain', values: ['vpc'] },
|
66
|
+
{ name: filter_by, values: [address] }
|
67
|
+
])
|
68
|
+
raise Errors::UnknownAddress, "IP #{address} could not be located" if resp[:addresses].empty?
|
69
|
+
resp[:addresses].first
|
70
|
+
end
|
71
|
+
|
72
|
+
# retrieve an array of private ips associated with the given interface
|
73
|
+
def interface_private_ips(id)
|
74
|
+
interface = describe_interface(id)
|
75
|
+
if interface[:private_ip_addresses]
|
76
|
+
primary = interface[:private_ip_addresses].find { |ip| ip[:primary] }
|
77
|
+
interface[:private_ip_addresses].map { |ip| ip[:private_ip_address] }.tap do |ips|
|
78
|
+
# ensure primary ip is first in the list
|
79
|
+
ips.unshift(*ips.delete(primary[:private_ip_address])) if primary
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# determine whether a given interface is attached or free
|
85
|
+
def interface_attached(id)
|
86
|
+
describe_interface(id)[:status] == 'in-use'
|
87
|
+
end
|
88
|
+
|
89
|
+
# test whether we have the appropriate permissions within our AWS access
|
90
|
+
# credentials to perform all possible API calls
|
91
|
+
def has_access?
|
92
|
+
test_methods = {
|
93
|
+
describe_network_interfaces: {},
|
94
|
+
create_network_interface: {
|
95
|
+
subnet_id: 'subnet-abcd1234'
|
96
|
+
},
|
97
|
+
attach_network_interface: {
|
98
|
+
network_interface_id: 'eni-abcd1234',
|
99
|
+
instance_id: 'i-abcd1234',
|
100
|
+
device_index: 0
|
101
|
+
},
|
102
|
+
detach_network_interface: {
|
103
|
+
attachment_id: 'eni-attach-abcd1234'
|
104
|
+
},
|
105
|
+
delete_network_interface: {
|
106
|
+
network_interface_id: 'eni-abcd1234'
|
107
|
+
},
|
108
|
+
create_tags: {
|
109
|
+
resources: ['eni-abcd1234'],
|
110
|
+
tags: []
|
111
|
+
},
|
112
|
+
describe_addresses: {},
|
113
|
+
allocate_address: {},
|
114
|
+
release_address: {
|
115
|
+
allocation_id: 'eipalloc-abcd1234'
|
116
|
+
},
|
117
|
+
associate_address: {
|
118
|
+
allocation_id: 'eipalloc-abcd1234',
|
119
|
+
network_interface_id: 'eni-abcd1234'
|
120
|
+
},
|
121
|
+
disassociate_address: {
|
122
|
+
association_id: 'eipassoc-abcd1234'
|
123
|
+
}
|
124
|
+
# these have no dry_run method
|
125
|
+
# assign_private_ip_addresses: {
|
126
|
+
# network_interface_id: 'eni-abcd1234'
|
127
|
+
# }
|
128
|
+
# unassign_private_ip_addresses: {
|
129
|
+
# network_interface_id: 'eni-abcd1234',
|
130
|
+
# private_ip_addresses: ['0.0.0.0']
|
131
|
+
# }
|
132
|
+
}
|
133
|
+
test_methods.all? do |method, params|
|
134
|
+
begin
|
135
|
+
client.public_send(method, params.merge(dry_run: true))
|
136
|
+
rescue EC2::Errors::DryRunOperation
|
137
|
+
true
|
138
|
+
rescue EC2::Errors::InvalidAllocationIDNotFound
|
139
|
+
# release_address does not properly support dry_run
|
140
|
+
true
|
141
|
+
rescue EC2::Errors::UnauthorizedOperation
|
142
|
+
false
|
143
|
+
rescue
|
144
|
+
raise Errors::ClientOperationError, 'Unexpected behavior while testing EC2 client permissions'
|
145
|
+
else
|
146
|
+
raise Errors::ClientOperationError, 'Unexpected behavior while testing EC2 client permissions'
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/aws-eni/errors.rb
CHANGED
@@ -1,16 +1,29 @@
|
|
1
|
-
|
2
1
|
module Aws
|
3
2
|
module ENI
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
module Errors
|
4
|
+
class ServiceError < RuntimeError; end
|
5
|
+
|
6
|
+
class EnvironmentError < ServiceError; end
|
7
|
+
|
8
|
+
class MetaNotFound < ServiceError; end
|
9
|
+
class MetaBadResponse < ServiceError; end
|
10
|
+
class MetaConnectionFailed < ServiceError; end
|
11
|
+
|
12
|
+
class InterfaceOperationError < ServiceError; end
|
13
|
+
class InterfacePermissionError < InterfaceOperationError; end
|
14
|
+
|
15
|
+
class ClientOperationError < ServiceError; end
|
16
|
+
class ClientPermissionError < ClientOperationError; end
|
17
|
+
|
18
|
+
class MissingInput < ServiceError; end
|
19
|
+
class InvalidInput < ServiceError; end
|
20
|
+
class TimeoutError < ServiceError; end
|
21
|
+
|
22
|
+
class UnknownInterface < ServiceError; end
|
23
|
+
class InvalidInterface < ServiceError; end
|
24
|
+
|
25
|
+
class UnknownAddress < ServiceError; end
|
26
|
+
class InvalidAddress < ServiceError; end
|
27
|
+
end
|
15
28
|
end
|
16
29
|
end
|
@@ -4,7 +4,7 @@ require 'aws-eni/errors'
|
|
4
4
|
|
5
5
|
module Aws
|
6
6
|
module ENI
|
7
|
-
class
|
7
|
+
class Interface
|
8
8
|
|
9
9
|
class << self
|
10
10
|
include Enumerable
|
@@ -28,7 +28,7 @@ module Aws
|
|
28
28
|
when /^[0-9\.]+$/
|
29
29
|
find { |dev| dev.has_ip?(index) }
|
30
30
|
end.tap do |dev|
|
31
|
-
raise
|
31
|
+
raise Errors::UnknownInterface, "No interface found matching #{index}" unless dev
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -76,11 +76,11 @@ module Aws
|
|
76
76
|
when nil
|
77
77
|
to_a
|
78
78
|
when /^eni-/, /^eth[0-9]+$/, /^[0-9a-f:]+$/i, /^[0-9\.]+$/
|
79
|
-
self[filter]
|
79
|
+
[*self[filter]]
|
80
80
|
when /^subnet-/
|
81
81
|
select { |dev| dev.subnet_id == filter }
|
82
82
|
end.tap do |devs|
|
83
|
-
raise
|
83
|
+
raise Errors::UnknownInterface, "No interface found matching #{filter}" if devs.nil? || devs.empty?
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -88,7 +88,7 @@ module Aws
|
|
88
88
|
def mutable?
|
89
89
|
cmd('link set dev eth0') # innocuous command
|
90
90
|
true
|
91
|
-
rescue
|
91
|
+
rescue Errors::InterfacePermissionError
|
92
92
|
false
|
93
93
|
end
|
94
94
|
|
@@ -98,10 +98,10 @@ module Aws
|
|
98
98
|
options[:errors] = true
|
99
99
|
begin
|
100
100
|
exec("/sbin/ip #{command}", options)
|
101
|
-
rescue
|
101
|
+
rescue Errors::InterfaceOperationError => e
|
102
102
|
case e.message
|
103
103
|
when /operation not permitted/i
|
104
|
-
raise
|
104
|
+
raise Errors::InterfacePermissionError, "Operation not permitted"
|
105
105
|
else
|
106
106
|
raise if errors
|
107
107
|
end
|
@@ -128,7 +128,7 @@ module Aws
|
|
128
128
|
else
|
129
129
|
error = e.read
|
130
130
|
warn "Warning: #{error}" if verbose
|
131
|
-
raise
|
131
|
+
raise Errors::InterfaceOperationError, error if errors
|
132
132
|
end
|
133
133
|
end
|
134
134
|
output
|
@@ -139,7 +139,7 @@ module Aws
|
|
139
139
|
|
140
140
|
def initialize(name, auto_config = true)
|
141
141
|
unless name =~ /^eth([0-9]+)$/
|
142
|
-
raise
|
142
|
+
raise Errors::InvalidInterface, "Invalid interface: #{name}"
|
143
143
|
end
|
144
144
|
@name = name
|
145
145
|
@device_number = $1.to_i
|
@@ -153,7 +153,7 @@ module Aws
|
|
153
153
|
exists? && IO.read("/sys/class/net/#{name}/address").strip
|
154
154
|
rescue Errno::ENOENT
|
155
155
|
end.tap do |address|
|
156
|
-
raise
|
156
|
+
raise Errors::UnknownInterface, "Interface #{name} not found on this machine" unless address
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
@@ -167,15 +167,14 @@ module Aws
|
|
167
167
|
# Validate and return basic interface metadata
|
168
168
|
def info
|
169
169
|
hwaddr = self.hwaddr
|
170
|
-
unless @meta_cache &&
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
subnet_cidr: Meta.http_get(conn, "#{dev_path}/subnet-ipv4-cidr-block")
|
170
|
+
unless @meta_cache && @meta_cache[:hwaddr] == hwaddr
|
171
|
+
@meta_cache = Meta.connection do
|
172
|
+
raise MetaBadResponse unless Meta.interface(hwaddr, '')
|
173
|
+
{
|
174
|
+
hwaddr: hwaddr,
|
175
|
+
interface_id: Meta.interface(hwaddr, 'interface-id'),
|
176
|
+
subnet_id: Meta.interface(hwaddr, 'subnet-id'),
|
177
|
+
subnet_cidr: Meta.interface(hwaddr, 'subnet-ipv4-cidr-block')
|
179
178
|
}.freeze
|
180
179
|
end
|
181
180
|
end
|
@@ -210,17 +209,15 @@ module Aws
|
|
210
209
|
end
|
211
210
|
|
212
211
|
def public_ips
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
ip_assoc[local_ip] = public_ip
|
212
|
+
hwaddr = self.hwaddr
|
213
|
+
Hash[
|
214
|
+
Meta.connection do
|
215
|
+
Meta.interface(hwaddr, 'ipv4-associations/', not_found: '', cache: false).lines.map do |public_ip|
|
216
|
+
public_ip.strip!
|
217
|
+
[ Meta.interface(hwaddr, "ipv4-associations/#{public_ip}", cache: false), public_ip ]
|
218
|
+
end
|
221
219
|
end
|
222
|
-
|
223
|
-
ip_assoc
|
220
|
+
]
|
224
221
|
end
|
225
222
|
|
226
223
|
# Enable our interface and create necessary routes
|
@@ -245,7 +242,7 @@ module Aws
|
|
245
242
|
changes = 0
|
246
243
|
prefix = self.prefix # prevent exists? check on each use
|
247
244
|
|
248
|
-
meta_ips = Meta.
|
245
|
+
meta_ips = Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip)
|
249
246
|
local_primary, *local_aliases = local_ips
|
250
247
|
meta_primary, *meta_aliases = meta_ips
|
251
248
|
|
@@ -364,14 +361,14 @@ module Aws
|
|
364
361
|
when :ip, :has_ip
|
365
362
|
"Interface #{name} does not have IP #{val}" unless has_ip? val
|
366
363
|
when :public_ip
|
367
|
-
"Interface #{name} does not have
|
364
|
+
"Interface #{name} does not have public IP #{val}" unless public_ips.has_value? val
|
368
365
|
when :local_ip, :private_ip
|
369
366
|
"Interface #{name} does not have private IP #{val}" unless local_ips.include? val
|
370
367
|
else
|
371
368
|
"Unknown attribute: #{attr}"
|
372
369
|
end
|
373
370
|
end
|
374
|
-
raise
|
371
|
+
raise Errors::UnknownInterface, error if error
|
375
372
|
self
|
376
373
|
end
|
377
374
|
|
data/lib/aws-eni/meta.rb
CHANGED
@@ -6,42 +6,64 @@ module Aws
|
|
6
6
|
module ENI
|
7
7
|
module Meta
|
8
8
|
|
9
|
-
# EC2 instance meta-data connection settings
|
10
|
-
HOST = '169.254.169.254'
|
11
|
-
PORT = '80'
|
12
|
-
BASE = '/latest/meta-data/'
|
13
|
-
|
14
9
|
# These are the errors we trap when attempting to talk to the instance
|
15
|
-
# metadata service. Any of these imply the service is not present,
|
10
|
+
# metadata service. Any of these imply the service is not present, not
|
16
11
|
# responding or some other non-recoverable error.
|
17
12
|
FAILURES = [
|
18
13
|
Errno::EHOSTUNREACH,
|
19
14
|
Errno::ECONNREFUSED,
|
20
15
|
Errno::EHOSTDOWN,
|
21
16
|
Errno::ENETUNREACH,
|
22
|
-
SocketError,
|
23
17
|
Timeout::Error,
|
24
|
-
|
18
|
+
SocketError,
|
19
|
+
Errors::MetaBadResponse,
|
25
20
|
]
|
26
21
|
|
27
|
-
#
|
28
|
-
#
|
22
|
+
# Perform a GET request on an open HTTP connection to the EC2 instance
|
23
|
+
# meta-data and return the body of any 200 response.
|
29
24
|
def self.get(path, options = {})
|
30
|
-
|
31
|
-
|
25
|
+
@cache ||= {}
|
26
|
+
if @cache[path] && options[:cache] != false
|
27
|
+
@cache[path]
|
28
|
+
else
|
29
|
+
connection(options) do |http|
|
30
|
+
response = http.request(Net::HTTP::Get.new(path))
|
31
|
+
case response.code.to_i
|
32
|
+
when 200
|
33
|
+
@cache[path] = response.body
|
34
|
+
when 404
|
35
|
+
raise Errors::MetaNotFound unless options[:not_found]
|
36
|
+
options[:not_found]
|
37
|
+
else
|
38
|
+
raise Errors::MetaBadResponse
|
39
|
+
end
|
40
|
+
end
|
32
41
|
end
|
33
42
|
end
|
34
43
|
|
44
|
+
# Perform a GET request on the instance metadata and return the body of
|
45
|
+
# any 200 response.
|
46
|
+
def self.instance(path, options = {})
|
47
|
+
get("/latest/meta-data/#{path}", options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Perform a GET request on the interface metadata and return the body of
|
51
|
+
# any 200 response.
|
52
|
+
def self.interface(hwaddr, path, options = {})
|
53
|
+
instance("network/interfaces/macs/#{hwaddr}/#{path}", options)
|
54
|
+
end
|
55
|
+
|
35
56
|
# Open a connection and attempt to execute the block `retries` times.
|
36
57
|
# Can specify open and read timeouts in addition to the number of retries.
|
37
|
-
def self.
|
58
|
+
def self.connection(options = {})
|
59
|
+
return yield(@open_connection) if @open_connection
|
38
60
|
retries = options[:retries] || 5
|
39
61
|
failed_attempts = 0
|
40
62
|
begin
|
41
|
-
http = Net::HTTP.new(
|
63
|
+
http = Net::HTTP.new('169.254.169.254', '80', nil)
|
42
64
|
http.open_timeout = options[:open_timeout] || 5
|
43
65
|
http.read_timeout = options[:read_timeout] || 5
|
44
|
-
http.start
|
66
|
+
@open_connection = http.start
|
45
67
|
yield(http).tap { http.finish }
|
46
68
|
rescue *FAILURES => e
|
47
69
|
if failed_attempts < retries
|
@@ -50,22 +72,10 @@ module Aws
|
|
50
72
|
failed_attempts += 1
|
51
73
|
retry
|
52
74
|
else
|
53
|
-
raise
|
75
|
+
raise Errors::MetaConnectionFailed, "EC2 Metadata request failed after #{retries} retries."
|
54
76
|
end
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# Perform a GET request on an open connection to the instance metadata
|
59
|
-
# endpoint and return the body of any 200 response.
|
60
|
-
def self.http_get(connection, path)
|
61
|
-
response = connection.request(Net::HTTP::Get.new(BASE + path))
|
62
|
-
case response.code.to_i
|
63
|
-
when 200
|
64
|
-
response.body
|
65
|
-
when 404
|
66
|
-
nil
|
67
|
-
else
|
68
|
-
raise BadResponse
|
77
|
+
ensure
|
78
|
+
@open_connection = nil
|
69
79
|
end
|
70
80
|
end
|
71
81
|
end
|
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.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Greiling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gli
|
@@ -83,8 +83,9 @@ files:
|
|
83
83
|
- aws-eni.gemspec
|
84
84
|
- bin/aws-eni
|
85
85
|
- lib/aws-eni.rb
|
86
|
+
- lib/aws-eni/client.rb
|
86
87
|
- lib/aws-eni/errors.rb
|
87
|
-
- lib/aws-eni/
|
88
|
+
- lib/aws-eni/interface.rb
|
88
89
|
- lib/aws-eni/meta.rb
|
89
90
|
- lib/aws-eni/version.rb
|
90
91
|
homepage: http://pixelcog.com/
|