awsadm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e1c945a82af301d859b2e606b39443e56140faab44616d3c60eea69de9723f9d
4
+ data.tar.gz: ff6770b94e06fcd44be5bdd785c3ddcd903c130b25ddaa17acbb9d12780cb057
5
+ SHA512:
6
+ metadata.gz: 536c6fa9a349f7fbefcad3943c7aeeea2b3f784ad91617c8880cb7bed09b4f1ba88263e1753361e71194f595f836b447289d3d426413939e7c4dbc44cbe4bccc
7
+ data.tar.gz: 13501ae9f0106aab6b628aaed767ffefb8dec84d061e54b983a1758b3545199199c0b9006afa7cc9d4a5706b7f12df7efb33469fb04b01040234151d034cb37f
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016–2017 Jimmy Thelander
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # awsadm
2
+
3
+ A command-line tool to manage Amazon Web Services (AWS) Elastic Compute Cloud (EC2) spot instance requests, instances and images.
4
+
5
+ *Note: This tools is highly experimental. Use at your own risk!*
6
+
7
+ # Installation
8
+
9
+ ```
10
+ $ gem install awsadm
11
+ ```
12
+
13
+ # Environment variables
14
+
15
+ * AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY: [Credentials](http://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) for using AWS
16
+ * AWS_DEFAULT_REGION: Default AWS [region](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions)
17
+ * AWSADM_SECURITY_GROUP: Name of [security group](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html) to use
18
+
19
+ # Usage
20
+
21
+ ```
22
+ awsadm commands:
23
+ awsadm cancel REQUEST # Cancel spot instance REQUEST
24
+ awsadm help [COMMAND] # Describe available commands or one specific command
25
+ awsadm list OWNER # List available images by OWNER
26
+ awsadm price INSTANCE_TYPE # Show price history for INSTANCE_TYPE
27
+ awsadm save INSTANCE # Save an image from INSTANCE
28
+ awsadm start IMAGE INSTANCE_TYPE # Start INSTANCE_TYPE from IMAGE
29
+ awsadm status # Return status on spot instance requests and instances
30
+ awsadm stop INSTANCE # Stop INSTANCE
31
+
32
+ Options:
33
+  v, [--verbose], [--no-verbose]
34
+ ```
35
+
36
+ # To do
37
+
38
+ * Tests, tests, tests
39
+ * More and better environment variable and input checks
40
+ * Sort image list by creation date
41
+ * Implement support for other parts of the Amazon Web Services
42
+
43
+ # License
44
+
45
+ [MIT License](http://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/awsadm.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'awsadm/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "awsadm"
8
+ spec.version = Awsadm::VERSION
9
+ spec.authors = ["Jimmy Thelander"]
10
+ spec.email = ["jimmy.thelander@gmail.com"]
11
+ spec.summary = %q{EXPERIMENTAL: Command-line tool for AWS resources.}
12
+ spec.description = %q{Manage your AWS resources from the command-line.}
13
+ spec.homepage = "https://github.com/thelander/awsadm"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "thor"
22
+ spec.add_dependency "aws-sdk"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.11"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "pry"
27
+ end
data/bin/awsadm ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby -U
2
+
3
+ require "awsadm"
4
+
5
+ begin
6
+ Awsadm::Cli.start(ARGV)
7
+ rescue Interrupt
8
+ exit 1
9
+ end
data/lib/awsadm/cli.rb ADDED
@@ -0,0 +1,361 @@
1
+ module Awsadm
2
+ class Cli < Thor
3
+ include Awsadm::Helpers
4
+
5
+ package_name "awsadm"
6
+ class_option :verbose, type: :boolean, aliases: :v
7
+
8
+ def initialize(*args)
9
+ super
10
+
11
+ @config = {
12
+ security_name: ENV["AWSADM_SECURITY_GROUP"] || "awsadm",
13
+ security_desc: "Allow port 22 on TCP (ssh)"
14
+ }
15
+
16
+ if ENV["AWS_ACCESS_KEY_ID"].nil? or ENV["AWS_SECRET_ACCESS_KEY"].nil?
17
+ abort "ERROR: Amazon Web Services credentials not set"
18
+ end
19
+
20
+ if ENV["AWS_DEFAULT_REGION"].nil?
21
+ abort "ERROR: Amazon Web Services default region not set"
22
+ end
23
+
24
+ Aws.config.update({
25
+ credentials: Aws::Credentials.new(
26
+ ENV["AWS_ACCESS_KEY_ID"],
27
+ ENV["AWS_SECRET_ACCESS_KEY"]
28
+ ),
29
+ region: ENV["AWS_DEFAULT_REGION"]
30
+ })
31
+
32
+ @client = Aws::EC2::Client.new
33
+ end
34
+
35
+ desc "list OWNER", "List available images by OWNER"
36
+ def list owner=""
37
+ print "Finding images... " if options[:verbose]
38
+
39
+ if owner.empty?
40
+ images = @client.describe_images[0]
41
+ else
42
+ images = @client.describe_images({
43
+ owners: [owner]
44
+ })[0]
45
+ end
46
+
47
+ puts "#{images.count} image(s) found." if options[:verbose]
48
+
49
+ image_table_header = ["IMAGE", "NAME", "PLATFORM", "STATE", "CREATED"]
50
+ image_table_items = []
51
+
52
+ images.each do |image|
53
+ image_table_items << [
54
+ image.image_id,
55
+ image.name.nil? ? "-" : truncate(image.name),
56
+ image.platform,
57
+ image.state,
58
+ empty?(image.creation_date) ? "-" : time_string_format(image.creation_date)
59
+ ]
60
+ end
61
+
62
+ if image_table_items.size > 0
63
+ puts_table image_table_header, image_table_items
64
+ end
65
+ end
66
+
67
+ desc "start IMAGE INSTANCE_TYPE", "Start INSTANCE_TYPE from IMAGE"
68
+ def start image_id, instance_type
69
+ print "Finding image... " if options[:verbose]
70
+
71
+ begin
72
+ image = @client.describe_images({
73
+ image_ids: [image_id]
74
+ })[0][0]
75
+ rescue Aws::EC2::Errors::InvalidAMIIDMalformed
76
+ abort "ERROR: Invalid image ID"
77
+ rescue Aws::EC2::Errors::InvalidAMIIDNotFound
78
+ abort "ERROR: Image not found"
79
+ end
80
+
81
+ puts "Found." if options[:verbose]
82
+
83
+ image_table_header = ["IMAGE", "NAME", "PLATFORM", "STATE", "CREATED"]
84
+ image_table_items = []
85
+
86
+ image_table_items = [[
87
+ image.image_id,
88
+ image.name.nil? ? "-" : truncate(image.name),
89
+ image.platform,
90
+ image.state,
91
+ empty?(image.creation_date) ? "-" : time_string_format(image.creation_date)
92
+ ]]
93
+
94
+ if image_table_items.size > 0
95
+ puts_table image_table_header, image_table_items
96
+ end
97
+
98
+ puts
99
+ print "Finding security group \"#{@config[:security_name]}\"... " if options[:verbose]
100
+
101
+ begin
102
+ security_group = @client.describe_security_groups({
103
+ group_names: [@config[:security_name]]
104
+ })[0][0]
105
+ rescue Aws::EC2::Errors::InvalidGroupNotFound
106
+ puts "Not found." if options[:verbose]
107
+ print "Creating one with defaults... " if options[:verbose]
108
+
109
+ @client.create_security_group({
110
+ group_name: @config[:security_name],
111
+ description: @config[:security_desc]
112
+ })
113
+
114
+ default_security.each do |rule|
115
+ @client.authorize_security_group_ingress(rule.merge({
116
+ group_name: @config[:security_name]
117
+ }))
118
+ end
119
+
120
+ security_group = @client.describe_security_groups({
121
+ group_names: [@config[:security_name]]
122
+ })[0][0]
123
+ end
124
+
125
+ puts "Done." if options[:verbose]
126
+
127
+ rule_table_header = ["PROTOCOL", "PORT RANGE", "IP RANGES"]
128
+ rule_table_items = []
129
+
130
+ security_group.ip_permissions.each do |p|
131
+ ip_ranges = []
132
+ p.ip_ranges.each do |ip|
133
+ ip_ranges << ip.cidr_ip
134
+ end
135
+
136
+ rule_table_items << [
137
+ p.ip_protocol,
138
+ "#{p.from_port} to #{p.to_port}",
139
+ "#{ip_ranges.join(", ")}"
140
+ ]
141
+ end
142
+
143
+ puts_table rule_table_header, rule_table_items, false
144
+ puts
145
+ print "Retrieving price history... " if options[:verbose]
146
+
147
+ if image.platform.nil? or image.platform.empty?
148
+ price_history = @client.describe_spot_price_history({
149
+ instance_types: [instance_type],
150
+ start_time: Time.now
151
+ })[0]
152
+ else
153
+ price_history = @client.describe_spot_price_history({
154
+ instance_types: [instance_type],
155
+ start_time: Time.now,
156
+ product_descriptions: [image.platform.capitalize]
157
+ })[0]
158
+ end
159
+
160
+ puts "Retrieved." if options[:verbose]
161
+
162
+ price_table_header = ["ZONE", "INSTANCE TYPE", "PLATFORM", "PRICE"]
163
+ price_table_items = []
164
+ prices = []
165
+
166
+ price_history.each do |p|
167
+ price = p.spot_price.to_f.round(3)
168
+ prices << price
169
+ price_table_items << [
170
+ p.availability_zone,
171
+ instance_type,
172
+ p.product_description.downcase,
173
+ price
174
+ ]
175
+ end
176
+
177
+ puts_table price_table_header, price_table_items
178
+ puts
179
+
180
+ suggested_bid = bid_formula prices
181
+ bid = ask "How much are you willing to bid for the instance (#{suggested_bid} usd/hour)?"
182
+ bid = suggested_bid if bid.nil? or bid.empty?
183
+ exit unless valid_float? bid
184
+ bid.to_f.round(3)
185
+
186
+ print "Creating instance request... " if options[:verbose]
187
+
188
+ request = @client.request_spot_instances({
189
+ spot_price: bid.to_s,
190
+ launch_specification: {
191
+ image_id: image_id,
192
+ security_groups: [@config[:security_name]],
193
+ instance_type: instance_type
194
+ }
195
+ })
196
+
197
+ puts "Created." if options[:verbose]
198
+ puts "Run \"awsadm status\" to see status." if options[:verbose]
199
+ end
200
+
201
+ desc "status", "Return status on spot instance requests and instances"
202
+ def status
203
+ requests = @client.describe_spot_instance_requests()[0]
204
+
205
+ request_table_header = ["REQUEST", "INSTANCE", "STATE", "PRICE", "AGE"]
206
+ request_table_items = []
207
+
208
+ requests.each do |r|
209
+ request_table_items << [
210
+ r.spot_instance_request_id,
211
+ r.instance_id.nil? ? "-" : r.instance_id,
212
+ r.status.code,
213
+ r.spot_price.to_f.round(3).to_s,
214
+ r.create_time.nil? ? "-" : "#{minutes_since(r.create_time)}m"
215
+ ]
216
+ end
217
+
218
+ if request_table_items.size > 0
219
+ puts_table request_table_header, request_table_items
220
+ end
221
+
222
+ instances = @client.describe_instances()[0]
223
+
224
+ instance_table_header = [
225
+ "INSTANCE", "IMAGE", "STATE", "IP", "ZONE", "AGE"
226
+ ]
227
+ instance_table_items = []
228
+
229
+ instances.each do |i|
230
+ i = i.instances.first
231
+ ni = i.network_interfaces.first
232
+
233
+ instance_table_items << [
234
+ i.instance_id,
235
+ i.image_id,
236
+ i.state.name,
237
+ i.state.name == "running" ? ni.association.public_ip : "-",
238
+ i.placement.availability_zone,
239
+ i.state.name == "running" ? "#{minutes_since(i.launch_time)}m" : "-"
240
+ ]
241
+ end
242
+
243
+ if instance_table_items.size > 0
244
+ puts
245
+ puts_table instance_table_header, instance_table_items
246
+ end
247
+ end
248
+
249
+ desc "stop INSTANCE", "Stop INSTANCE"
250
+ def stop instance_id
251
+ if instance_id == "all"
252
+ instances = @client.describe_instances()[0]
253
+ else
254
+ begin
255
+ instances = @client.describe_instances({
256
+ instance_ids: [instance_id]
257
+ })[0]
258
+ rescue Aws::EC2::Errors::InvalidInstanceIDMalformed
259
+ abort "ERROR: Invalid instance ID"
260
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
261
+ abort "ERROR: Instance not found"
262
+ end
263
+ end
264
+
265
+ instance_ids = []
266
+ instances.each do |i|
267
+ i = i.instances.first
268
+ instance_ids << i.instance_id
269
+ end
270
+
271
+ @client.terminate_instances({
272
+ instance_ids: instance_ids
273
+ })
274
+
275
+ print "Instance(s) have been stopped. " if options[:verbose]
276
+ puts "Run \"awsadm status\" to see status." if options[:verbose]
277
+ end
278
+
279
+ desc "cancel REQUEST", "Cancel spot instance REQUEST"
280
+ def cancel request_id
281
+ if request_id == "all"
282
+ requests = @client.describe_spot_instance_requests()[0]
283
+ else
284
+ begin
285
+ requests = @client.describe_spot_instance_requests({
286
+ spot_instance_request_ids: [request_id]
287
+ })[0]
288
+ rescue Aws::EC2::Errors::InvalidSpotInstanceRequestIDMalformed
289
+ abort "ERROR: Invalid request ID"
290
+ rescue Aws::EC2::Errors::InvalidSpotInstanceRequestIDNotFound
291
+ abort "ERROR: Request not found"
292
+ end
293
+ end
294
+
295
+ request_ids = []
296
+
297
+ requests.each do |r|
298
+ request_ids << r.spot_instance_request_id
299
+ end
300
+
301
+ if request_ids.size > 0
302
+ @client.cancel_spot_instance_requests({
303
+ spot_instance_request_ids: request_ids
304
+ })
305
+ end
306
+
307
+ print "Instance request(s) cancelled. " if options[:verbose]
308
+ puts "Run \"awsadm status\" to see status." if options[:verbose]
309
+ end
310
+
311
+ desc "save INSTANCE", "Save an image from INSTANCE"
312
+ def save instance_id
313
+ begin
314
+ instance = @client.describe_instances({
315
+ instance_ids: [instance_id]
316
+ })[0][0]
317
+ rescue Aws::EC2::Errors::InvalidInstanceIDMalformed
318
+ abort "ERROR: Invalid instance ID"
319
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
320
+ abort "ERROR: Instance not found"
321
+ end
322
+
323
+ image = @client.create_image({
324
+ instance_id: instance_id,
325
+ name: "awsadm-#{instance_id}-#{Time.now.strftime('%Y%m%d-%H%M%S%L')}"
326
+ })
327
+
328
+ print "Instance saved. " if options[:verbose]
329
+ puts "Run \"awsadm list\" to see images." if options[:verbose]
330
+ end
331
+
332
+ desc "price INSTANCE_TYPE", "Show price history for INSTANCE_TYPE"
333
+ def price instance_type
334
+ print "Retrieving price history... " if options[:verbose]
335
+
336
+ price_history = @client.describe_spot_price_history({
337
+ instance_types: [instance_type],
338
+ start_time: Time.now
339
+ })[0]
340
+
341
+ puts "Retrieved." if options[:verbose]
342
+
343
+ price_table_header = ["ZONE", "INSTANCE TYPE", "PLATFORM", "PRICE"]
344
+ price_table_items = []
345
+ prices = []
346
+
347
+ price_history.each do |p|
348
+ price = p.spot_price.to_f.round(3)
349
+ prices << price
350
+ price_table_items << [
351
+ p.availability_zone,
352
+ p.instance_type,
353
+ p.product_description.downcase,
354
+ price
355
+ ]
356
+ end
357
+
358
+ puts_table price_table_header, price_table_items
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,49 @@
1
+ module Awsadm::Helpers
2
+ def bid_formula x
3
+ y = 0.02
4
+ (x.max + y).round(3)
5
+ end
6
+
7
+ def minutes_since time
8
+ (time - Time.now).to_i.abs / 60
9
+ end
10
+
11
+ def time_format time
12
+ time.strftime("%Y-%m-%d")
13
+ end
14
+
15
+ def time_string_format time
16
+ time_format(Time.parse(time))
17
+ end
18
+
19
+ def truncate string, max=24
20
+ string.length > max ? "#{string[0..max]}..." : string
21
+ end
22
+
23
+ def valid_float? string
24
+ !!Float(string) rescue false
25
+ end
26
+
27
+ def puts_table header, items, sort=true
28
+ full_table = []
29
+ full_table = items
30
+ full_table.sort! if sort
31
+ full_table.unshift header
32
+ print_table full_table, colwidth: 14
33
+ end
34
+
35
+ def empty? string
36
+ (string.nil? or string.empty?)
37
+ end
38
+
39
+ def default_security
40
+ [
41
+ {
42
+ ip_protocol: "tcp",
43
+ cidr_ip: "0.0.0.0/0",
44
+ from_port: 22,
45
+ to_port: 22
46
+ }
47
+ ]
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Awsadm
2
+ VERSION = "0.1.0"
3
+ end
data/lib/awsadm.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "rubygems"
2
+ require "thor"
3
+ require "aws-sdk"
4
+
5
+ module Awsadm
6
+ autoload :Cli, "awsadm/cli"
7
+ autoload :Helpers, "awsadm/helpers"
8
+ autoload :Version, "awsadm/version"
9
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: awsadm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jimmy Thelander
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Manage your AWS resources from the command-line.
84
+ email:
85
+ - jimmy.thelander@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - awsadm.gemspec
96
+ - bin/awsadm
97
+ - lib/awsadm.rb
98
+ - lib/awsadm/cli.rb
99
+ - lib/awsadm/helpers.rb
100
+ - lib/awsadm/version.rb
101
+ homepage: https://github.com/thelander/awsadm
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.7.3
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: 'EXPERIMENTAL: Command-line tool for AWS resources.'
125
+ test_files: []