aws-eni 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7f9bdc9bb5d6c7427d50f1354b77317a5a4f1bc
4
+ data.tar.gz: fe7568417322ab7bfa41be38041efaf1e0aeeae7
5
+ SHA512:
6
+ metadata.gz: f86aba30d2b1b0e564810648c701f144c098388d8e349ae1143817b4ee58a07c3d86008be3b79b8e9cca66bc5a92ca801b68907711914a32eeece675894f30f4
7
+ data.tar.gz: bd60223a0bc09962fb82db2cd7c413da1a08f37ff603a85b716723eaa3fecbf983de317155e8c451badf50063fa59df417d3f7c3e7b6df181651026e2be6696f
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aws-eni.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Mike Greiling
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,63 @@
1
+ # aws-eni
2
+
3
+ A command line tool and client library to manage AWS Elastic Network Interfaces from within an EC2 instance.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'aws-eni'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install aws-eni
20
+
21
+ ## Usage
22
+
23
+ Synchronize your EC2 instance network interface config with AWS.
24
+
25
+ $ aws-eni sync
26
+
27
+ List all interface cards, their IPs, and their associations
28
+
29
+ $ aws-eni list
30
+ eth0:
31
+ 10.0.1.23
32
+ eth1:
33
+ 10.0.2.54 => 54.25.169.87 (EIP)
34
+ 10.0.2.72 => 52.82.17.251 (EIP)
35
+
36
+ Add a new private IP
37
+
38
+ $ aws-eni add eth1
39
+ added 10.0.2.81
40
+
41
+ Associate a new elastic IP
42
+
43
+ $ aws-eni associate 10.0.2.81
44
+ associated 10.0.2.81 => 52.171.254.36
45
+
46
+ Dissociate an elastic IP
47
+
48
+ $ aws-eni dissociate 10.0.2.81
49
+ dissociated 52.171.254.36 from 10.0.2.81
50
+
51
+ Remove a private IP
52
+
53
+ $ aws-eni remove 10.0.2.81
54
+ removed 10.0.2.81 from eth1
55
+
56
+
57
+ ## Contributing
58
+
59
+ 1. Fork it ( https://github.com/[my-github-username]/aws-eni/fork )
60
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
61
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
62
+ 4. Push to the branch (`git push origin my-new-feature`)
63
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'aws-eni/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "aws-eni"
8
+ spec.version = Aws::ENI::VERSION
9
+ spec.authors = ["Mike Greiling"]
10
+ spec.email = ["mike@pixelcog.com"]
11
+ spec.summary = "Manage and sync local network config with AWS Elastic Network Interfaces"
12
+ spec.description = "A command line tool and client library to manage AWS Elastic Network Interfaces from within an EC2 instance"
13
+ spec.homepage = "http://pixelcog.com/"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "gli", "~> 2.5"
22
+ spec.add_dependency "aws-sdk", "~> 2.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gli'
3
+ require 'resolv'
4
+ require 'ipaddr'
5
+ require 'aws-eni'
6
+
7
+ include GLI::App
8
+
9
+ program_desc 'Manage and sync local network config with AWS Elastic Network Interfaces'
10
+
11
+ version Aws::ENI::VERSION
12
+
13
+ autocomplete_commands true
14
+ subcommand_option_handling :normal
15
+ arguments :loose
16
+ sort_help :manually
17
+
18
+ # global options
19
+
20
+ desc 'Display all system commands and warnings'
21
+ switch [:v,:verbose], negatable: false
22
+
23
+ pre do |opt|
24
+ Aws::ENI::IFconfig.verbose = opt[:verbose]
25
+ true
26
+ end
27
+
28
+ # arg parser methods
29
+
30
+ def local_ip?(ip)
31
+ IPAddr.new(Aws::ENI.environment[:vpc_cidr]) === IPAddr.new(ip)
32
+ end
33
+
34
+ def parse_args(args, *accept)
35
+ params = {}
36
+ args.each do |arg|
37
+ if arg =~ /^eth[0-9]+/ && accept.include?(:device_name)
38
+ help_now! "You may only specify one device name." if params[:device_name]
39
+ params[:device_name] = arg
40
+ elsif arg =~ /^eni-/ && accept.include?(:interface_id)
41
+ help_now! "You may only specify one interface ID." if params[:interface_id]
42
+ params[:interface_id] = arg
43
+ elsif arg =~ /^subnet-/ && accept.include?(:subnet_id)
44
+ help_now! "You may only specify one subnet." if params[:subnet_id]
45
+ params[:subnet_id] = arg
46
+ elsif arg =~ /^sg-/ && accept.include?(:security_groups)
47
+ params[:security_groups] ||= []
48
+ params[:security_groups] << arg
49
+ elsif arg =~ /^eipalloc-/ && accept.include?(:allocation_id)
50
+ help_now! "You may only specify one allocation ID" if params[:allocation_id]
51
+ params[:allocation_id] = arg
52
+ elsif arg =~ /^eipassoc-/ && accept.include?(:association_id)
53
+ help_now! "You may only specify one association ID" if params[:association_id]
54
+ params[:association_id] = arg
55
+ elsif arg =~ Resolv::IPv4::Regex
56
+ if local_ip? arg
57
+ if accept.include?(:primary_ip)
58
+ help_now! "You may only specify one primary IP address." if params[:primary_ip]
59
+ params[:primary_ip] = arg
60
+ elsif accept.include?(:private_ip)
61
+ help_now! "You may only specify one private IP address." if params[:private_ip]
62
+ params[:private_ip] = arg
63
+ else
64
+ help_now! "Invalid IP address: #{arg}"
65
+ end
66
+ elsif accept.include?(:public_ip)
67
+ help_now! "You may only specify one public IP address." if params[:public_ip]
68
+ params[:public_ip] = arg
69
+ else
70
+ help_now! "Invalid IP address: #{arg}"
71
+ end
72
+ else
73
+ help_now! "Invalid argument: #{arg}"
74
+ end
75
+ end
76
+ params
77
+ end
78
+
79
+ # commands
80
+
81
+ desc 'List current interface configuration'
82
+ long_desc %{
83
+ List information about a set of interfaces including interface ID, interface
84
+ name, MAC address, and a list of primary and secondary IP addresses along with
85
+ any public ips associated with them.
86
+
87
+ Use the optional filter argument to limit the listing to interfaces with a
88
+ matching name, interface ID, subnet ID, or MAC address (default 'all').
89
+ }
90
+ arg 'filter', :optional
91
+ command [:list,:ls] do |c|
92
+ c.action do |global,opts,args|
93
+ help_now! "Too many arguments" if args.count > 1
94
+ args.delete('all')
95
+ Aws::ENI.list(args.first).each do |interface|
96
+ print "#{interface[:name]}:"
97
+ print "\tID #{interface[:interface_id]}"
98
+ print " HWaddr #{interface[:hwaddr]}"
99
+ print " Status " << (interface[:enabled] ? "UP" : "DOWN") << "\n"
100
+ interface[:local_ips].each do |local_ip|
101
+ if interface[:public_ips][local_ip]
102
+ puts "\t#{local_ip} => #{interface[:public_ips][local_ip]}"
103
+ else
104
+ puts "\t#{local_ip}"
105
+ end
106
+ end
107
+ end
108
+ puts "\ninterface config is out of sync" if Aws::ENI.configure(nil, dry_run: true) > 0
109
+ end
110
+ end
111
+
112
+ desc 'Configure network interfaces'
113
+ long_desc %{
114
+ Syncronize configuration for a set of interfaces to match their configuration
115
+ on AWS by managing secondary ips, routes, and rules.
116
+
117
+ Use the optional filter argument to limit this action to interfaces with a
118
+ matching name, interface ID, subnet ID, or MAC address (default 'all').
119
+ }
120
+ arg 'filter', :optional
121
+ command [:config] do |c|
122
+ c.action do |global,opts,args|
123
+ help_now! "Too many arguments" if args.count > 1
124
+ args.delete('all')
125
+ if Aws::ENI.configure(args.first) != 0
126
+ puts 'synchronized interface config'
127
+ else
128
+ puts 'network interface config already in sync'
129
+ end
130
+ end
131
+ end
132
+
133
+ desc 'Remove custom configuration from network interfaces'
134
+ long_desc %{
135
+ Remove custom configuration for a set of interfaces removing any custom ips,
136
+ routes, and rules previously added (the 'eth0' primary IP is always left
137
+ untouched for safety).
138
+
139
+ Use the optional filter argument to limit this action to interfaces with a
140
+ matching name, interface ID, subnet ID, or MAC address (default 'all').
141
+ }
142
+ arg 'filter', :optional
143
+ command [:deconfig] do |c|
144
+ c.action do |global,opts,args|
145
+ help_now! "Too many arguments" if args.count > 1
146
+ args.delete('all')
147
+ Aws::ENI.deconfigure(args.first)
148
+ end
149
+ end
150
+
151
+ desc 'Enable network interface'
152
+ long_desc %{
153
+ Enable one or more network interfaces (similar to 'ifup').
154
+
155
+ Specify one name, interface ID, subnet ID, or MAC address to enable any
156
+ matching interfaces, or specify 'all' to enable all interfaces.
157
+ }
158
+ arg 'filter'
159
+ command [:enable,:up] do |c|
160
+ c.action do |global,opts,args|
161
+ help_now! "Incorrect number of arguments" unless args.count.between?(0,1)
162
+ args.delete('all')
163
+ Aws::ENI::IFconfig.filter(args.first).each(&:enable)
164
+ puts "interfaces enabled"
165
+ end
166
+ end
167
+
168
+ desc 'Disable network interface'
169
+ long_desc %{
170
+ Disable one or more network interfaces (similar to 'ifdown').
171
+
172
+ Specify one name, interface ID, subnet ID, or MAC address to disable any
173
+ matching interfaces, or specify 'all' to disable all interfaces.
174
+
175
+ eth0 cannot be disabled.
176
+ }
177
+ arg 'filter'
178
+ command [:disable,:down] do |c|
179
+ c.action do |global,opts,args|
180
+ help_now! "Incorrect number of arguments" unless args.count.between?(0,1)
181
+ args.delete('all')
182
+ Aws::ENI::IFconfig.filter(args.first).each do |dev|
183
+ if dev.name == 'eth0'
184
+ warn 'skipping eth0'
185
+ else
186
+ dev.disable
187
+ puts "interface #{dev.name} disabled"
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ desc 'Create new network interface'
194
+ long_desc %{
195
+ Create a new Elastic Network Interface with a given set of parameters.
196
+
197
+ Optional arguments include subnet ID, security group IDs, and a primary ip
198
+ address.
199
+
200
+ If no subnet ID is provided (e.g. subnet-1a2b3c4d) the subnet for eth0 will
201
+ be used. If no security group is provided (e.g. sg-1a2b3c4d) the VPC default
202
+ security group will be used. If a private IP is provided, it must fall within
203
+ the subnet's CIDR block. Arguments can be provided in any order.
204
+ }
205
+ arg 'subnet', :optional
206
+ arg 'security-groups', :optional
207
+ arg 'ip-address', :optional
208
+ command [:create] do |c|
209
+ c.action do |global,opts,args|
210
+ params = parse_args args, :subnet_id, :security_groups, :primary_ip
211
+ interface = Aws::ENI.create_interface(params)
212
+ puts "interface #{interface[:id]} created on #{interface[:subnet_id]}"
213
+ end
214
+ end
215
+
216
+ desc 'Attach network interface'
217
+ long_desc %{
218
+ Attach an Elastic Network Interface to our instance.
219
+
220
+ If interface ID provided (e.g. eni-1a2b3c4d), that interface will be attached,
221
+ otherwise a new interface will be created with the provided parameters which
222
+ may include a subnet ID, security group IDs, and a primary IP address.
223
+
224
+ If no subnet ID is provided (e.g. subnet-1a2b3c4d) the subnet for eth0 will
225
+ be used. If no security group is provided (e.g. sg-1a2b3c4d) the VPC default
226
+ security group will be used. If a private IP is provided, it must fall within
227
+ the subnet's CIDR block. Arguments can be provided in any order.
228
+ }
229
+ arg 'interface-id', :optional
230
+ arg 'subnet-id', :optional
231
+ arg 'security-groups', :optional
232
+ arg 'ip-address', :optional
233
+ command [:attach] do |c|
234
+ c.desc 'Refresh the interface configuration after attachment'
235
+ c.switch [:r,:c,:config], negatable: false
236
+
237
+ c.action do |global,opts,args|
238
+ if args.first =~ /^eni-/
239
+ help_now! 'Too many arguments' if args.count > 1
240
+ id = args.first
241
+ else
242
+ params = parse_args args, :subnet_id, :security_groups, :primary_ip
243
+ interface = Aws::ENI.create_interface(params)
244
+ puts "interface #{interface[:id]} created on #{interface[:subnet_id]}"
245
+ id = interface[:id]
246
+ end
247
+ device = Aws::ENI.attach_interface(id, enable: opts[:config], configure: opts[:config])
248
+ puts "interface #{device[:id]} attached to #{device[:name]}"
249
+ puts "device #{device[:name]} enabled and configured" if opts[:config]
250
+ end
251
+ end
252
+
253
+ desc 'Detach network interface'
254
+ long_desc %{
255
+ Detach an Elastic Network Interface from our instance.
256
+
257
+ You must provide the interface ID (e.g. eni-1a2b3c4d) or the device name
258
+ (e.g. eth1) or both.
259
+
260
+ If no flag is present, the default action is to destroy the network interface
261
+ after detachment only if it was originally created by aws-eni.
262
+ }
263
+ arg 'interface-id OR device-name'
264
+ command [:detach] do |c|
265
+ c.desc 'Delete (or preserve) the unused ENI resource after dataching'
266
+ c.switch [:d,:delete], default_value: :not_provided # GLI behavior workaround
267
+
268
+ c.action do |global,opts,args|
269
+ help_now! "Missing argument" if args.empty?
270
+ params = parse_args args, :interface_id, :device_name
271
+ params[:delete] = opts[:delete] unless opts[:delete] == :not_provided
272
+ id = params[:interface_id] || params[:device_name]
273
+
274
+ device = Aws::ENI.detach_interface(id, params)
275
+ if device[:deleted]
276
+ puts "interface #{device[:id]} detached from #{device[:name]} and destroyed"
277
+ else
278
+ puts "interface #{device[:id]} detached from #{device[:name]}"
279
+ end
280
+ end
281
+ end
282
+
283
+ desc 'Clean unattached network interfaces'
284
+ long_desc %{
285
+ Delete unused Elastic Network Interfaces based on provided criteria.
286
+
287
+ You may provide a specific interface ID (e.g. eni-1a2b3c4d), a subnet ID
288
+ (e.g. subnet-1a2b3c4d), or an availability zone from this region (e.g.
289
+ us-east-1a) to act as search criteria.
290
+
291
+ By default, this will only delete ENIs which were originally created with this
292
+ script.
293
+ }
294
+ arg 'filter', :optional
295
+ command [:clean] do |c|
296
+ c.desc 'Force deletion of all unattached interfaces which meet our criteria'
297
+ c.switch [:f,:force], negatable: false
298
+
299
+ c.action do |global,opts,args|
300
+ help_now! "Too many arguments" if args.count > 1
301
+ deleted = Aws::ENI.clean_interfaces(args.first, safe_mode: !opts[:force])
302
+ puts "#{deleted[:count]} interfaces deleted"
303
+ end
304
+ end
305
+
306
+ desc 'Assign a new secondary private IP address'
307
+ long_desc %{
308
+ Assign an additional private IP address to a given interface.
309
+
310
+ You may optionally specify a private IP in the interface's subnet CIDR range
311
+ to assign, otherwise one will be generated automatically.
312
+ }
313
+ arg 'ip-address', :optional
314
+ arg 'interface-id OR device-name'
315
+ command [:assign] do |c|
316
+ c.action do |global,opts,args|
317
+ help_now! "Missing argument" if args.empty?
318
+ params = parse_args args, :interface_id, :device_name, :private_ip
319
+ device = params[:interface_id] || params[:device_name]
320
+ assignment = Aws::ENI.assign_secondary_ip(device, params)
321
+ puts "IP #{assignment[:private_ip]} assigned to #{assignment[:device_name]} (#{assignment[:interface_id]})"
322
+ end
323
+ end
324
+
325
+ desc 'Unassign a secondary private IP address'
326
+ long_desc %{
327
+ Remove a private IP address from a given interface. Any associated public ip
328
+ address will be dissociated first and optionally released.
329
+
330
+ If no interface is specified, it will be inferred by the IP address.
331
+ }
332
+ arg 'ip-address'
333
+ arg 'interface-id', :optional
334
+ arg 'device-name', :optional
335
+ command [:unassign] do |c|
336
+ c.desc 'Release any associated public IP address'
337
+ c.switch [:r,:release], negatable: false
338
+
339
+ c.action do |global,opts,args|
340
+ help_now! "Missing argument" if args.empty?
341
+ params = parse_args args, :interface_id, :device_name, :private_ip
342
+ params[:release] = opts[:release]
343
+ unassign = Aws::ENI.unassign_secondary_ip(params[:private_ip], params)
344
+ if unassign[:released]
345
+ puts "EIP #{unassign[:public_ip]} (#{unassign[:allocation_id]}) dissociated from #{unassign[:private_ip]} and released"
346
+ elsif unassign[:public_ip]
347
+ puts "EIP #{unassign[:public_ip]} (#{unassign[:allocation_id]}) dissociated from #{unassign[:private_ip]}"
348
+ end
349
+ puts "IP #{unassign[:private_ip]} removed from #{unassign[:device_name]} (#{unassign[:interface_id]})"
350
+ end
351
+ end
352
+
353
+ desc 'Associate a public IP address with a private IP address'
354
+ long_desc %{
355
+ Associate a private IP address with a new or existing public IP address.
356
+
357
+ If no public IP or allocation ID is provided, a new Elastic IP Address will be
358
+ allocated and used.
359
+
360
+ If no interface ID or device name provided, it will be inferred from the ip
361
+ address.
362
+ }
363
+ arg 'private-ip'
364
+ arg 'public-ip', :optional
365
+ arg 'allocation-id', :optional
366
+ arg 'interface-id', :optional
367
+ arg 'device-name', :optional
368
+ command [:associate] do |c|
369
+ c.action do |global,opts,args|
370
+ help_now! "Missing argument" if args.empty?
371
+ params = parse_args args, :private_ip, :public_ip, :allocation_id, :interface_id, :device_name
372
+ assoc = Aws::ENI.associate_elastic_ip(params[:private_ip], params)
373
+ puts "EIP #{assoc[:public_ip]} (#{assoc[:allocation_id]}) associated with #{assoc[:private_ip]} on #{assoc[:device_name]} (#{assoc[:interface_id]})"
374
+ end
375
+ end
376
+
377
+ desc 'Dissociate a public IP address from a private IP address'
378
+ long_desc %{
379
+ Remove an association between a given public IP address or private IP address
380
+ and their counterpart. The public IP address is then optionally released.
381
+ }
382
+ arg 'private-ip OR public-ip OR allocation-id'
383
+ arg 'interface-id', :optional
384
+ arg 'device-name', :optional
385
+ command [:dissociate] do |c|
386
+ c.desc 'Release the associated public IP address'
387
+ c.switch [:r,:release], negatable: false
388
+
389
+ c.action do |global,opts,args|
390
+ help_now! "Missing argument" if args.empty?
391
+ params = parse_args args, :private_ip, :public_ip, :allocation_id, :association_id, :interface_id, :device_name
392
+ ip = params[:private_ip] || params[:public_ip] || params[:association_id] || params[:allocation_id]
393
+ dissoc = Aws::ENI.dissociate_elastic_ip(ip, params)
394
+ if dissoc[:released]
395
+ puts "EIP #{dissoc[:public_ip]} (#{dissoc[:allocation_id]}) dissociated from #{dissoc[:private_ip]} on #{dissoc[:device_name]} (#{dissoc[:interface_id]}) and released"
396
+ else
397
+ puts "EIP #{dissoc[:public_ip]} (#{dissoc[:allocation_id]}) dissociated from #{dissoc[:private_ip]} on #{dissoc[:device_name]} (#{dissoc[:interface_id]})"
398
+ end
399
+ end
400
+ end
401
+
402
+ desc 'Allocate a new Elastic IP address'
403
+ long_desc %{
404
+ Allocate a new Elastic IP address for use
405
+ }
406
+ command [:allocate] do |c|
407
+ c.action do |global,opts,args|
408
+ alloc = Aws::ENI.allocate_elastic_ip
409
+ puts "EIP #{alloc[:public_ip]} allocated as #{alloc[:allocation_id]}"
410
+ end
411
+ end
412
+
413
+ desc 'Release one or more unassigned Elastic IP addresses'
414
+ long_desc %{
415
+ Release one or more unassigned Elastic IP addresses. If IP address or
416
+ allication, will release only that EIP, otherwise will clean all unassigned
417
+ EIPs.
418
+ }
419
+ arg 'ip-address OR allocation-id'
420
+ command [:release] do |c|
421
+ c.action do |global,opts,args|
422
+ help_now! "Missing argument" if args.empty?
423
+ params = parse_args args, :public_ip, :allocation_id
424
+ eip = params[:public_ip] || params[:allocation_id]
425
+ release = Aws::ENI.release_elastic_ip(eip, params)
426
+ puts "EIP #{release[:public_ip]} (#{release[:allocation_id]}) released"
427
+ end
428
+ end
429
+
430
+ # error handling
431
+
432
+ on_error do |exception|
433
+ if Aws::ENI::PermissionError === exception
434
+ warn 'error: This action requires super-user privileges (try sudo)'
435
+ false
436
+ else
437
+ true
438
+ end
439
+ end
440
+
441
+ ARGV << 'ls' if ARGV.empty?
442
+ exit run(ARGV)