aws-eni 0.1.0 → 0.1.1
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 +53 -7
- data/lib/aws-eni.rb +100 -22
- data/lib/aws-eni/errors.rb +3 -0
- data/lib/aws-eni/ifconfig.rb +9 -1
- data/lib/aws-eni/meta.rb +1 -4
- 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: 34f9ba0cdfc6a898ddf6b4ec24f72b04187d4b49
|
|
4
|
+
data.tar.gz: a0de204e867336cb1d3293a4d13fab1ab572b45e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80b6c15ac5abdd73f50b9811a20d6494ac5c4df28afc14ebedcadb9398424d470d81eba1de018da964b0926aad7bfa98903a1440d6cc5b29a4b470d040e18bcd
|
|
7
|
+
data.tar.gz: 3bc10d67d60cd4afaf30146e293f7acfcc0de280f09e863796de7e03297c5cd5712ba8b987cad90c640af8c7c136343b3cb77f780f3eddaadd99888985a38810
|
data/bin/aws-eni
CHANGED
|
@@ -8,7 +8,7 @@ include GLI::App
|
|
|
8
8
|
|
|
9
9
|
program_desc 'Manage and sync local network config with AWS Elastic Network Interfaces'
|
|
10
10
|
|
|
11
|
-
version Aws::ENI::VERSION
|
|
11
|
+
@version = Aws::ENI::VERSION
|
|
12
12
|
|
|
13
13
|
autocomplete_commands true
|
|
14
14
|
subcommand_option_handling :normal
|
|
@@ -17,8 +17,11 @@ sort_help :manually
|
|
|
17
17
|
|
|
18
18
|
# global options
|
|
19
19
|
|
|
20
|
+
desc 'Display the program version'
|
|
21
|
+
switch [:v,:version], negatable: false
|
|
22
|
+
|
|
20
23
|
desc 'Display all system commands and warnings'
|
|
21
|
-
switch [:
|
|
24
|
+
switch [:V,:verbose], negatable: false
|
|
22
25
|
|
|
23
26
|
pre do |opt|
|
|
24
27
|
Aws::ENI::IFconfig.verbose = opt[:verbose]
|
|
@@ -207,6 +210,7 @@ arg 'security-groups', :optional
|
|
|
207
210
|
arg 'ip-address', :optional
|
|
208
211
|
command [:create] do |c|
|
|
209
212
|
c.action do |global,opts,args|
|
|
213
|
+
args.delete('new')
|
|
210
214
|
params = parse_args args, :subnet_id, :security_groups, :primary_ip
|
|
211
215
|
interface = Aws::ENI.create_interface(params)
|
|
212
216
|
puts "interface #{interface[:id]} created on #{interface[:subnet_id]}"
|
|
@@ -231,20 +235,26 @@ arg 'subnet-id', :optional
|
|
|
231
235
|
arg 'security-groups', :optional
|
|
232
236
|
arg 'ip-address', :optional
|
|
233
237
|
command [:attach] do |c|
|
|
234
|
-
c.desc '
|
|
235
|
-
c.switch [:
|
|
238
|
+
c.desc 'Do not configure or enable the device after attachment (implies block)'
|
|
239
|
+
c.switch [:n,'no-config'], negatable: false
|
|
240
|
+
|
|
241
|
+
c.desc 'Do not return until attachment is complete'
|
|
242
|
+
c.switch [:b,:block], negatable: false
|
|
236
243
|
|
|
237
244
|
c.action do |global,opts,args|
|
|
245
|
+
config = !opts['no_config']
|
|
238
246
|
if args.first =~ /^eni-/
|
|
239
247
|
help_now! 'Too many arguments' if args.count > 1
|
|
240
248
|
id = args.first
|
|
241
249
|
else
|
|
250
|
+
args.delete('new')
|
|
242
251
|
params = parse_args args, :subnet_id, :security_groups, :primary_ip
|
|
252
|
+
Aws::ENI.assert_ifconfig_access if config
|
|
243
253
|
interface = Aws::ENI.create_interface(params)
|
|
244
254
|
puts "interface #{interface[:id]} created on #{interface[:subnet_id]}"
|
|
245
255
|
id = interface[:id]
|
|
246
256
|
end
|
|
247
|
-
device = Aws::ENI.attach_interface(id, enable:
|
|
257
|
+
device = Aws::ENI.attach_interface(id, enable: config, configure: config, block: options[:block])
|
|
248
258
|
puts "interface #{device[:id]} attached to #{device[:name]}"
|
|
249
259
|
puts "device #{device[:name]} enabled and configured" if opts[:config]
|
|
250
260
|
end
|
|
@@ -262,18 +272,22 @@ long_desc %{
|
|
|
262
272
|
}
|
|
263
273
|
arg 'interface-id OR device-name'
|
|
264
274
|
command [:detach] do |c|
|
|
265
|
-
c.desc 'Delete (or preserve) the unused ENI resource after dataching'
|
|
275
|
+
c.desc 'Delete (or preserve) the unused ENI resource after dataching (implies block)'
|
|
266
276
|
c.switch [:d,:delete], default_value: :not_provided # GLI behavior workaround
|
|
267
277
|
|
|
278
|
+
c.desc 'Do not return until detachment is complete'
|
|
279
|
+
c.switch [:b,:block], negatable: false
|
|
280
|
+
|
|
268
281
|
c.action do |global,opts,args|
|
|
269
282
|
help_now! "Missing argument" if args.empty?
|
|
270
283
|
params = parse_args args, :interface_id, :device_name
|
|
284
|
+
params[:block] = opts[:block]
|
|
271
285
|
params[:delete] = opts[:delete] unless opts[:delete] == :not_provided
|
|
272
286
|
id = params[:interface_id] || params[:device_name]
|
|
273
287
|
|
|
274
288
|
device = Aws::ENI.detach_interface(id, params)
|
|
275
289
|
if device[:deleted]
|
|
276
|
-
puts "interface #{device[:id]} detached from #{device[:name]} and
|
|
290
|
+
puts "interface #{device[:id]} detached from #{device[:name]} and deleted"
|
|
277
291
|
else
|
|
278
292
|
puts "interface #{device[:id]} detached from #{device[:name]}"
|
|
279
293
|
end
|
|
@@ -368,6 +382,7 @@ arg 'device-name', :optional
|
|
|
368
382
|
command [:associate] do |c|
|
|
369
383
|
c.action do |global,opts,args|
|
|
370
384
|
help_now! "Missing argument" if args.empty?
|
|
385
|
+
args.delete('new')
|
|
371
386
|
params = parse_args args, :private_ip, :public_ip, :allocation_id, :interface_id, :device_name
|
|
372
387
|
assoc = Aws::ENI.associate_elastic_ip(params[:private_ip], params)
|
|
373
388
|
puts "EIP #{assoc[:public_ip]} (#{assoc[:allocation_id]}) associated with #{assoc[:private_ip]} on #{assoc[:device_name]} (#{assoc[:interface_id]})"
|
|
@@ -405,6 +420,8 @@ long_desc %{
|
|
|
405
420
|
}
|
|
406
421
|
command [:allocate] do |c|
|
|
407
422
|
c.action do |global,opts,args|
|
|
423
|
+
args.delete('new')
|
|
424
|
+
help_now! "Invalid argument: #{args.first}" unless args.empty?
|
|
408
425
|
alloc = Aws::ENI.allocate_elastic_ip
|
|
409
426
|
puts "EIP #{alloc[:public_ip]} allocated as #{alloc[:allocation_id]}"
|
|
410
427
|
end
|
|
@@ -427,6 +444,35 @@ command [:release] do |c|
|
|
|
427
444
|
end
|
|
428
445
|
end
|
|
429
446
|
|
|
447
|
+
desc 'Test access to AWS EC2 and our machine\'s network config'
|
|
448
|
+
long_desc %{
|
|
449
|
+
Check for sufficient privileges to alter the local machine's network interface
|
|
450
|
+
configuration and verify that the AWS access credentials include permissions
|
|
451
|
+
necessary to perform all network related functions.
|
|
452
|
+
}
|
|
453
|
+
command [:test] do |c|
|
|
454
|
+
c.action do |global,opts,args|
|
|
455
|
+
help_now! "Too many arguments" if args.count > 1
|
|
456
|
+
|
|
457
|
+
print 'IFconfig permissions test... '
|
|
458
|
+
if Aws::ENI.can_modify_ifconfig?
|
|
459
|
+
puts 'success!'
|
|
460
|
+
else
|
|
461
|
+
puts 'failed'
|
|
462
|
+
puts "- unable to modify network configuration with /sbin/ip (try sudo)"
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
print 'AWS EC2 permissions test... '
|
|
466
|
+
if Aws::ENI.can_access_ec2?
|
|
467
|
+
puts 'success!'
|
|
468
|
+
else
|
|
469
|
+
puts 'failed'
|
|
470
|
+
puts "- insufficient EC2 access. Ensure you have granted access to the"
|
|
471
|
+
puts " appropriate EC2 methods in your IAM policy (see documentation)"
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
430
476
|
# error handling
|
|
431
477
|
|
|
432
478
|
on_error do |exception|
|
data/lib/aws-eni.rb
CHANGED
|
@@ -23,13 +23,18 @@ module Aws
|
|
|
23
23
|
raise EnvironmentError, "Unable to detect VPC settings, library incompatible with EC2-Classic"
|
|
24
24
|
end
|
|
25
25
|
end.freeze
|
|
26
|
-
rescue
|
|
26
|
+
rescue ConnectionFailed
|
|
27
27
|
raise EnvironmentError, "Unable to load EC2 meta-data"
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def owner_tag(new_owner = nil)
|
|
31
31
|
@owner_tag = new_owner.to_s if new_owner
|
|
32
|
-
@owner_tag
|
|
32
|
+
@owner_tag ||= 'aws-eni script'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def timeout(new_default = nil)
|
|
36
|
+
@timeout = new_default.to_i if new_default
|
|
37
|
+
@timeout ||= 30
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
def client
|
|
@@ -63,11 +68,15 @@ module Aws
|
|
|
63
68
|
params[:description] = "generated by #{owner_tag} from #{environment[:instance_id]} on #{timestamp}"
|
|
64
69
|
|
|
65
70
|
response = client.create_network_interface(params)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
wait_for 'the interface to be created', rescue: Aws::EC2::Errors::ServiceError do
|
|
72
|
+
if interface_status(response[:network_interface][:network_interface_id]) == 'available'
|
|
73
|
+
client.create_tags(resources: [response[:network_interface][:network_interface_id]], tags: [
|
|
74
|
+
{ key: 'created by', value: owner_tag },
|
|
75
|
+
{ key: 'created on', value: timestamp },
|
|
76
|
+
{ key: 'created from', value: environment[:instance_id] }
|
|
77
|
+
])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
71
80
|
{
|
|
72
81
|
id: response[:network_interface][:network_interface_id],
|
|
73
82
|
subnet_id: response[:network_interface][:subnet_id],
|
|
@@ -77,6 +86,10 @@ module Aws
|
|
|
77
86
|
|
|
78
87
|
# attach network interface
|
|
79
88
|
def attach_interface(id, options = {})
|
|
89
|
+
do_enable = true unless options[:enable] == false
|
|
90
|
+
do_config = true unless options[:configure] == false
|
|
91
|
+
assert_ifconfig_access if do_config || do_enable
|
|
92
|
+
|
|
80
93
|
interface = IFconfig[options[:device_number] || options[:name]]
|
|
81
94
|
raise InvalidParameterError, "Interface #{interface.name} is already in use" if interface.exists?
|
|
82
95
|
|
|
@@ -86,10 +99,14 @@ module Aws
|
|
|
86
99
|
params[:device_index] = interface.device_number
|
|
87
100
|
|
|
88
101
|
response = client.attach_network_interface(params)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
|
|
103
|
+
if options[:block] || do_config || do_enable
|
|
104
|
+
wait_for 'the interface to attach', rescue: ConnectionFailed do
|
|
105
|
+
interface.exists? && interface_status(interface.interface_id) == 'in-use'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
interface.configure if do_config
|
|
109
|
+
interface.enable if do_enable
|
|
93
110
|
{
|
|
94
111
|
id: interface.interface_id,
|
|
95
112
|
name: interface.name,
|
|
@@ -125,22 +142,21 @@ module Aws
|
|
|
125
142
|
attachment_id: description[:attachment][:attachment_id],
|
|
126
143
|
force: true
|
|
127
144
|
)
|
|
128
|
-
deleted = false
|
|
129
145
|
created_by_us = description.tag_set.any? { |tag| tag.key == 'created by' && tag.value == owner_tag }
|
|
130
|
-
|
|
131
|
-
|
|
146
|
+
do_delete = options[:delete] || options[:delete].nil? && created_by_us
|
|
147
|
+
|
|
148
|
+
if options[:block] || do_delete
|
|
149
|
+
wait_for 'the interface to detach', interval: 0.3 do
|
|
132
150
|
!interface.exists? && interface_status(description[:network_interface_id]) == 'available'
|
|
133
151
|
end
|
|
134
|
-
raise TimeoutError, "Timed out waiting for the interface to detach" unless detached
|
|
135
|
-
client.delete_network_interface(network_interface_id: description[:network_interface_id])
|
|
136
|
-
deleted = true
|
|
137
152
|
end
|
|
153
|
+
client.delete_network_interface(network_interface_id: description[:network_interface_id]) if do_delete
|
|
138
154
|
{
|
|
139
155
|
id: description[:network_interface_id],
|
|
140
156
|
name: "eth#{description[:attachment][:device_index]}",
|
|
141
157
|
device_number: description[:attachment][:device_index],
|
|
142
158
|
created_by_us: created_by_us,
|
|
143
|
-
deleted:
|
|
159
|
+
deleted: do_delete,
|
|
144
160
|
api_response: description
|
|
145
161
|
}
|
|
146
162
|
end
|
|
@@ -260,6 +276,59 @@ module Aws
|
|
|
260
276
|
}
|
|
261
277
|
end
|
|
262
278
|
|
|
279
|
+
# test whether we have permission to modify our local configuration
|
|
280
|
+
def can_modify_ifconfig?
|
|
281
|
+
IFconfig.mutable?
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def assert_ifconfig_access
|
|
285
|
+
raise PermissionError, 'Insufficient user priveleges (try sudo)' unless can_modify_ifconfig?
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# test whether we have the appropriate permissions within our AWS access
|
|
289
|
+
# credentials to perform all possible API calls
|
|
290
|
+
def can_access_ec2?
|
|
291
|
+
client = self.client
|
|
292
|
+
test_methods = {
|
|
293
|
+
describe_network_interfaces: nil,
|
|
294
|
+
create_network_interface: {
|
|
295
|
+
subnet_id: 'subnet-abcd1234'
|
|
296
|
+
},
|
|
297
|
+
attach_network_interface: {
|
|
298
|
+
network_interface_id: 'eni-abcd1234',
|
|
299
|
+
instance_id: 'i-abcd1234',
|
|
300
|
+
device_index: 0
|
|
301
|
+
},
|
|
302
|
+
detach_network_interface: {
|
|
303
|
+
attachment_id: 'eni-attach-abcd1234'
|
|
304
|
+
},
|
|
305
|
+
delete_network_interface: {
|
|
306
|
+
network_interface_id: 'eni-abcd1234'
|
|
307
|
+
},
|
|
308
|
+
create_tags: {
|
|
309
|
+
resources: ['eni-abcd1234'],
|
|
310
|
+
tags: []
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
test_methods.each do |method, params|
|
|
314
|
+
begin
|
|
315
|
+
params ||= {}
|
|
316
|
+
params[:dry_run] = true
|
|
317
|
+
client.public_send(method, params)
|
|
318
|
+
raise Error, "Unexpected behavior while testing AWS API access"
|
|
319
|
+
rescue Aws::EC2::Errors::DryRunOperation
|
|
320
|
+
# success
|
|
321
|
+
rescue Aws::EC2::Errors::UnauthorizedOperation
|
|
322
|
+
return false
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
true
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def assert_ec2_access
|
|
329
|
+
raise AWSPermissionError, 'Insufficient AWS API access' unless can_access_ec2?
|
|
330
|
+
end
|
|
331
|
+
|
|
263
332
|
private
|
|
264
333
|
|
|
265
334
|
def interface_status(id)
|
|
@@ -267,12 +336,21 @@ module Aws
|
|
|
267
336
|
resp[:network_interfaces].first[:status] unless resp[:network_interfaces].empty?
|
|
268
337
|
end
|
|
269
338
|
|
|
270
|
-
def wait_for(
|
|
271
|
-
|
|
272
|
-
|
|
339
|
+
def wait_for(task, options = {}, &block)
|
|
340
|
+
errors = [*options[:rescue]]
|
|
341
|
+
timeout = options[:timeout] || self.timeout
|
|
342
|
+
interval = options[:interval] || 0.1
|
|
343
|
+
|
|
344
|
+
until timeout < 0
|
|
345
|
+
begin
|
|
346
|
+
break if block.call
|
|
347
|
+
rescue Exception => e
|
|
348
|
+
raise unless errors.any? { |error| error === e }
|
|
349
|
+
end
|
|
273
350
|
sleep interval
|
|
351
|
+
timeout -= interval
|
|
274
352
|
end
|
|
275
|
-
|
|
353
|
+
raise TimeoutError, "Timed out waiting for #{task}" unless timeout > 0
|
|
276
354
|
end
|
|
277
355
|
end
|
|
278
356
|
end
|
data/lib/aws-eni/errors.rb
CHANGED
data/lib/aws-eni/ifconfig.rb
CHANGED
|
@@ -70,6 +70,14 @@ module Aws
|
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
# Test whether we have permission to run RTNETLINK commands
|
|
74
|
+
def mutable?
|
|
75
|
+
exec 'link set dev eth0' # random innocuous command
|
|
76
|
+
true
|
|
77
|
+
rescue PermissionError
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
|
|
73
81
|
# Execute a command
|
|
74
82
|
def exec(command, options = {})
|
|
75
83
|
output = nil
|
|
@@ -127,7 +135,7 @@ module Aws
|
|
|
127
135
|
unless @meta_cache && hwaddr == @meta_cache[:hwaddr]
|
|
128
136
|
dev_path = "network/interfaces/macs/#{hwaddr}"
|
|
129
137
|
Meta.open_connection do |conn|
|
|
130
|
-
raise
|
|
138
|
+
raise BadResponse unless Meta.http_get(conn, "#{dev_path}/")
|
|
131
139
|
@meta_cache = {
|
|
132
140
|
hwaddr: hwaddr,
|
|
133
141
|
interface_id: Meta.http_get(conn, "#{dev_path}/interface-id"),
|
data/lib/aws-eni/meta.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'time'
|
|
2
2
|
require 'net/http'
|
|
3
|
+
require 'aws-eni/errors'
|
|
3
4
|
|
|
4
5
|
module Aws
|
|
5
6
|
module ENI
|
|
@@ -10,10 +11,6 @@ module Aws
|
|
|
10
11
|
PORT = '80'
|
|
11
12
|
BASE = '/latest/meta-data/'
|
|
12
13
|
|
|
13
|
-
# Custom exception classes
|
|
14
|
-
class BadResponse < RuntimeError; end
|
|
15
|
-
class ConnectionFailed < RuntimeError; end
|
|
16
|
-
|
|
17
14
|
# These are the errors we trap when attempting to talk to the instance
|
|
18
15
|
# metadata service. Any of these imply the service is not present, no
|
|
19
16
|
# responding or some other non-recoverable error.
|
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.1
|
|
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-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: gli
|