aws_student_accounts 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
+ SHA1:
3
+ metadata.gz: 77ee4734b87a0f54963c15ea30d20b7850033965
4
+ data.tar.gz: 5ecfe71ef0faa3fadd3443919a88af827c2594a8
5
+ SHA512:
6
+ metadata.gz: 01c95b0019e0844bb058d65dfc46d967632ba5eccfe95f455d3d8cc9000a71226efe90eed3c443a04baffc09967ca97e3b5ecc7a59be4a2dceea3145b06e3014
7
+ data.tar.gz: fdf2a412089a871ef4cf9681ef623fbc5cee32159e48ca276376379694067cfb3313b9e5506df5b6a3fa95769d2ed3b733add75b6c56ddf8164c2d4963fdca40
data/.gitignore ADDED
@@ -0,0 +1,16 @@
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
15
+
16
+ students
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format progress
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aws_student_accounts.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Dr Nic Williams
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.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ AWS Student Accounts
2
+ ====================
3
+
4
+ Manage student AWS accounts in bulk.
5
+
6
+ - Consumes a single `.fog` file of AWS accounts with master credentials
7
+ - Creates/deletes temporary student IAM credentials
8
+ - Emits a `.fog` file for API access
9
+ - Emits a username/password file for AWS Console access
10
+ - Cleans out all VMs, disk, elastic IPs, AMIs, VPCs from student accounts
11
+
12
+ Requires
13
+ --------
14
+
15
+ - Ruby 1.9+
16
+ - RubyGems
17
+
18
+ Installation
19
+ ------------
20
+
21
+ Install using RubyGems:
22
+
23
+ ```
24
+ $ gem install aws_student_accounts
25
+ ```
26
+
27
+ Usage
28
+ -----
29
+
30
+ ### Verify list of API credentials are valid
31
+
32
+ ```
33
+ aws_student_accounts verify-credentials -C path/to/fog.yml
34
+ ```
35
+
36
+ ### Create student IAM access
37
+
38
+ Create a student IAM account for all AWS accounts
39
+
40
+ ```
41
+ aws_student_accounts create-students \
42
+ -C path/to/fog.yml \
43
+ --signin-urls path/to/signin-urls.yml \
44
+ [path/to/students]
45
+ ```
46
+
47
+ For each account key in `path/to/fog.yml` there must be a mapping to the signin URL for that account in `path/to/signin-urls.yml`.
48
+
49
+ E.g.
50
+
51
+ ```yaml
52
+ :student1: https://093368509744.signin.aws.amazon.com/console
53
+ :student2: https://012345678901.signin.aws.amazon.com/console
54
+ ```
55
+
56
+ `path/to/students` will be a folder (defaults to `students` in current folder) into which the following files are created:
57
+
58
+ - `students-fog-api.yml` - the AWS credentials for all students' to access their allocated AWS accounts
59
+ - `students-console-passwords.md` - the AWS console username/passwords for students' to access their allocated AWS accounts
60
+
61
+ ### Delete student IAM access
62
+
63
+ Delete temporary student IAM accounts.
64
+
65
+ ```
66
+ aws_student_accounts delete-students -C path/to/fog.yml
67
+ ```
68
+
69
+ ### Cleans accounts
70
+
71
+ Clean out all VMs, disk, elastic IPs, AMIs, VPCs from student accounts
72
+
73
+ ```
74
+ aws_student_accounts clean-accounts -C path/to/fog.yml
75
+ ```
76
+
77
+ ### Options
78
+
79
+ All commands will perform the account upon all accounts listed in the `-C fog.yml` file provided.
80
+
81
+ You can filter down to 1+ accounts with a comma separated list `--only student1,student2`.
82
+
83
+ You can ignore 1+ accounts from the `-C` list with `-i student19,student20`.
84
+
85
+ Experiment with these options using the read-only `aws_student_accounts verify-credentials` command.
86
+
87
+ Contributing
88
+ ------------
89
+
90
+ 1. Fork it ( https://github.com/[my-github-username]/aws_student_accounts/fork )
91
+ 2. Create your feature branch (`git checkout -b my-new-feature`\)
92
+ 3. Commit your changes (`git commit -am 'Add some feature'`\)
93
+ 4. Push to the branch (`git push origin my-new-feature`\)
94
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'aws_student_accounts/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "aws_student_accounts"
8
+ spec.version = AwsStudentAccounts::VERSION
9
+ spec.authors = ["Dr Nic Williams"]
10
+ spec.email = ["drnicwilliams@gmail.com"]
11
+ spec.summary = %q{Manage student AWS accounts in bulk}
12
+ spec.description = %q{Manage student AWS accounts in bulk}
13
+ spec.homepage = ""
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 "thor", "~> 0.19"
22
+ spec.add_dependency "fog", "~> 1.26"
23
+ spec.add_dependency "parallel"
24
+ spec.add_dependency "thread_safe"
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path("../../lib", __FILE__))
4
+ require 'aws_student_accounts'
5
+
6
+ AwsStudentAccounts::App.start(ARGV, :debug => true)
@@ -0,0 +1,6 @@
1
+ require "aws_student_accounts/version"
2
+ require "aws_student_accounts/app"
3
+
4
+ module AwsStudentAccounts
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,535 @@
1
+ require "thor"
2
+ require "yaml"
3
+ require "fog"
4
+ require "thread_safe"
5
+ require "parallel"
6
+
7
+ class AwsStudentAccounts::App < Thor
8
+ include Thor::Actions
9
+
10
+ attr_reader :fog_credentials
11
+
12
+ def self.common_options
13
+ method_option :fog_file, desc: "Path to YAML file of fog credentials",
14
+ type: :string, aliases: "-C", required: true
15
+ method_option :only, desc: "Restrict to comma-separated list of fog keys",
16
+ type: :string, aliases: "-o"
17
+ end
18
+
19
+ desc "verify-credentials", "Verify AWS credentials"
20
+ common_options
21
+ def verify_credentials
22
+ load_and_verify_options
23
+ @io_semaphore = Mutex.new
24
+ Parallel.each(fog_credentials, in_threads: 10) do |username, credentials|
25
+ begin
26
+ account = account_summary(credentials)
27
+ server_count = account[:servers]
28
+ @io_semaphore.synchronize do
29
+ say "#{username}: "
30
+ say "OK ", :green
31
+ say "(#{server_count} vm)"
32
+ end
33
+ rescue => e
34
+ @io_semaphore.synchronize do
35
+ say "#{username}: "
36
+ say e.message, :red
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ desc "create-students", "Create a student IAM account for all AWS accounts"
43
+ common_options
44
+ method_option :signin_urls, desc: "File mapping usernames to account signin URLs",
45
+ type: :string, aliases: "-s", required: true
46
+ def create_students(path_to_student_folders="students")
47
+ load_and_verify_options
48
+ @io_semaphore = Mutex.new
49
+
50
+ signin_urls = YAML.load_file(options[:signin_urls])
51
+
52
+ @users_credentials = ThreadSafe::Hash.new
53
+ @users_passwords = ThreadSafe::Hash.new
54
+
55
+ FileUtils.mkdir_p(path_to_student_folders)
56
+ FileUtils.chdir(path_to_student_folders) do
57
+ Parallel.each(fog_credentials, in_threads: fog_credentials.size) do |username, credentials|
58
+ create_student_user(username, credentials, signin_urls)
59
+ end
60
+
61
+ File.open("students-fog-api.yml", "w") do |f|
62
+ f << @users_credentials.to_yaml
63
+ end
64
+ say "Stored all user API credentials: #{File.expand_path('students-fog-api.yml')}"
65
+
66
+ File.open("students-console-passwords.md", "w") do |f|
67
+ f << "# Student AWS logins\n\n"
68
+ fog_credentials.each do |username, credentials|
69
+ if user_login = @users_passwords[username]
70
+ f << <<-EOS
71
+ ## #{user_login[:username]}
72
+
73
+ * Sign-in URL: #{user_login[:url]}
74
+ * Username: #{user_login[:username]}
75
+ * Password: #{user_login[:password]}
76
+
77
+ EOS
78
+ end
79
+ end
80
+ say "Stored all user passwords: #{File.expand_path('students-console-passwords.md')}"
81
+ end
82
+ end
83
+ end
84
+
85
+ desc "delete-students", "Delete temporary student IAM accounts"
86
+ common_options
87
+ def delete_students
88
+ load_and_verify_options
89
+ @io_semaphore = Mutex.new
90
+
91
+ Parallel.each(fog_credentials, in_threads: fog_credentials.size) do |username, credentials|
92
+ begin
93
+ iam = Fog::AWS::IAM.new(credentials)
94
+ delete_user(iam, username)
95
+ @io_semaphore.synchronize do
96
+ user_say username, "Deleted user", :green
97
+ end
98
+ rescue Fog::AWS::IAM::NotFound
99
+ @io_semaphore.synchronize do
100
+ user_say username, "Does not exist", :red
101
+ end
102
+ rescue => e
103
+ @io_semaphore.synchronize do
104
+ user_say username, e.message, :red
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ desc "clean-accounts", "Clean out all VMs, disk, elastic IPs, AMIs, VPCs from student accounts"
111
+ common_options
112
+ def clean_accounts
113
+ load_and_verify_options
114
+ @io_semaphore = Mutex.new
115
+
116
+ # Double check before unleashing devastation
117
+ unless yes?("Do you really want to terminate all instances, disk, IPs, networks etc?", :red)
118
+ say "Phew!", :green
119
+ exit 1
120
+ end
121
+ Parallel.each(fog_credentials, in_threads: fog_credentials.size) do |account, credentials|
122
+ all_regions = aws_regions(false)
123
+ Parallel.each(all_regions, in_threads: all_regions.size) do |aws_region|
124
+ compute = Fog::Compute::AWS.new(credentials.merge(region: aws_region))
125
+ destroy_everything(account, aws_region, compute)
126
+ end
127
+ end
128
+ end
129
+
130
+ private
131
+ def load_and_verify_options
132
+ @fog_file = options["fog_file"]
133
+ unless File.exists?(@fog_file)
134
+ say "File #{@fog_file} does not exist", :red
135
+ exit 1
136
+ end
137
+ @fog_credentials = YAML.load_file(@fog_file)
138
+ if !@fog_credentials.is_a?(Hash) || @fog_credentials.first.is_a?(Hash)
139
+ say "File #{@fog_file} does not match a .fog format (Hash of Hashes)", :red
140
+ exit 1
141
+ end
142
+
143
+ if options["only"]
144
+ only_specific_keys = options["only"].split(',')
145
+ @fog_credentials = fog_credentials.keep_if { |key, value| only_specific_keys.include?(key.to_s) }
146
+ end
147
+ # TODO: filter @fog_credentials by filter/ignore list
148
+ end
149
+
150
+ # generated via http://awspolicygen.s3.amazonaws.com/policygen.html
151
+ # Effect Action Resource Conditions
152
+ # Allow ec2:* arn:aws:ec2:*:*:* None
153
+ # Allow s3:* arn:aws:s3:::* None
154
+ def iam_key_policy(arn)
155
+ {
156
+ 'Statement' => [
157
+ 'Effect' => 'Allow',
158
+ 'Action' => 'iam:*AccessKey*',
159
+ 'Resource' => arn
160
+ ]
161
+ }
162
+ end
163
+
164
+ def iam_student_policy
165
+ {
166
+ "Statement" => [
167
+ {
168
+ "Effect" => "Allow",
169
+ "Action" => "*",
170
+ "Resource" => "*"
171
+ },
172
+ ]
173
+ }
174
+ end
175
+
176
+ def generate_password
177
+ "starkandwayne"
178
+ end
179
+
180
+ def user_say(username, *args)
181
+ say "[#{username}] "
182
+ say *args
183
+ end
184
+
185
+ def user_say_region(username, region, *args)
186
+ say "[#{username}] [#{region}] "
187
+ say *args
188
+ end
189
+
190
+ def create_student_user(account, admin_credentials, signin_urls)
191
+ unless account_signin_url = signin_urls[account]
192
+ @io_semaphore.synchronize do
193
+ user_say account, "Admin account #{account} missing from #{options[:signin_urls]}, skipping", :red
194
+ end
195
+ return
196
+ end
197
+
198
+ begin
199
+ iam = Fog::AWS::IAM.new(admin_credentials)
200
+
201
+ # create user with same name as we externally refer to the account; e.g. student15
202
+ username = account
203
+
204
+ begin
205
+ user_response = iam.create_user(username)
206
+ rescue Fog::AWS::IAM::EntityAlreadyExists
207
+ @io_semaphore.synchronize do
208
+ user_say username, "User exists, deleting..."
209
+ end
210
+
211
+ delete_user(iam, username)
212
+ user_response = iam.create_user(username)
213
+ rescue => e
214
+ @io_semaphore.synchronize do
215
+ user_say username, e.message, :red
216
+ end
217
+ end
218
+
219
+ @io_semaphore.synchronize do
220
+ user_say username, "Created user #{username}", :green
221
+ end
222
+ key_response = iam.create_access_key('UserName' => username)
223
+ access_key_id = key_response.body['AccessKey']['AccessKeyId']
224
+ secret_access_key = key_response.body['AccessKey']['SecretAccessKey']
225
+
226
+ @io_semaphore.synchronize do
227
+ user_say username, "Created API access key", :green
228
+ user_say username, "TODO: generated and download SSH public key", :yellow
229
+ end
230
+
231
+ password = generate_password
232
+ iam.create_login_profile(username, password)
233
+ @io_semaphore.synchronize do
234
+ user_say username, "Created login password", :green
235
+ end
236
+
237
+ arn = user_response.body['User']['Arn']
238
+ iam.put_user_policy(username, 'UserKeyPolicy', iam_key_policy(arn))
239
+ iam.put_user_policy(username, 'UserAllPolicy', iam_student_policy)
240
+ @io_semaphore.synchronize do
241
+ user_say username, "Created user policies", :green
242
+ end
243
+
244
+ user_credentials = {
245
+ aws_access_key_id: access_key_id,
246
+ aws_secret_access_key: secret_access_key
247
+ }
248
+ begin
249
+ user_compute = Fog::Compute::AWS.new(user_credentials)
250
+ server_count = user_compute.servers.size
251
+ @io_semaphore.synchronize do
252
+ user_say username, "Verify credentials: "
253
+ say "OK ", :green
254
+ say "(#{server_count} vms)"
255
+ end
256
+ rescue => e
257
+ @io_semaphore.synchronize do
258
+ user_say username, "Verify credentials: "
259
+ say e.message, :red
260
+ end
261
+ end
262
+
263
+ @users_credentials[username.to_sym] = user_credentials
264
+ user_login = {
265
+ password: password,
266
+ username: username.to_s,
267
+ url: account_signin_url
268
+ }
269
+ @users_passwords[username] = user_login
270
+
271
+ write_fog_file(username, user_credentials)
272
+ write_password_file(account_signin_url, user_login)
273
+ rescue => e
274
+ @io_semaphore.synchronize do
275
+ say "#{e.class}: #{e.message}", :red
276
+ end
277
+ end
278
+
279
+ end
280
+
281
+ def delete_user(iam, username)
282
+ access_keys_reponse = iam.list_access_keys('UserName' => username)
283
+ access_keys_reponse.body['AccessKeys'].each do |key|
284
+ user_response = iam.delete_access_key(key['AccessKeyId'], 'UserName' => username)
285
+ end
286
+ @io_semaphore.synchronize do
287
+ user_say username, "Deleted access keys", :yellow
288
+ end
289
+
290
+ begin
291
+ iam.delete_login_profile(username)
292
+ @io_semaphore.synchronize do
293
+ user_say username, "Deleted user login profile", :yellow
294
+ end
295
+ rescue Fog::AWS::IAM::NotFound
296
+ end
297
+
298
+ user_policies_reponse = iam.list_user_policies(username)
299
+ user_policies_reponse.body['PolicyNames'].each do |policy_name|
300
+ iam.delete_user_policy(username, policy_name)
301
+ end
302
+ @io_semaphore.synchronize do
303
+ user_say username, "Deleted user policies", :yellow
304
+ end
305
+
306
+ user_response = iam.delete_user(username)
307
+ @io_semaphore.synchronize do
308
+ user_say username, "Deleted user", :yellow
309
+ end
310
+ end
311
+
312
+ def write_fog_file(username, user_credentials)
313
+ FileUtils.mkdir_p(username.to_s)
314
+ File.open(File.join(username.to_s, "fog-api.yml"), "w") do |f|
315
+ f << {
316
+ default: user_credentials
317
+ }.to_yaml
318
+ end
319
+ @io_semaphore.synchronize do
320
+ user_say username, "Created fog-api.yml", :green
321
+ end
322
+ end
323
+
324
+ def write_password_file(account_signin_url, user_login)
325
+ username = user_login[:username]
326
+ FileUtils.mkdir_p(username)
327
+ File.open(File.join(username, "console-passwords.md"), "w") do |f|
328
+ f << <<-EOS
329
+ # #{username}
330
+
331
+ * Sign-in URL: #{user_login[:url]}
332
+ * Username: #{username}
333
+ * Password: #{user_login[:password]}
334
+ EOS
335
+ end
336
+ @io_semaphore.synchronize do
337
+ user_say username, "Created console-passwords.md", :green
338
+ end
339
+
340
+ end
341
+
342
+ def destroy_everything(account, aws_region, compute)
343
+ # First, destroy instances
344
+ servers = compute.servers
345
+ original_servers_count = servers.size
346
+ if original_servers_count > 0
347
+ @io_semaphore.synchronize do
348
+ user_say_region account, aws_region, "Destroying #{original_servers_count} instances"
349
+ end
350
+ Parallel.each(servers, in_threads: servers.size) do |server|
351
+ @io_semaphore.synchronize do
352
+ user_say_region account, aws_region, "Destroying #{server.id}"
353
+ end
354
+ server.destroy
355
+ server.wait_for { state == "terminated" }
356
+ end
357
+ @io_semaphore.synchronize do
358
+ user_say_region account, aws_region, "Destroyed #{original_servers_count} instances"
359
+ end
360
+ else
361
+ @io_semaphore.synchronize do
362
+ user_say_region account, aws_region, "No instances to destroy"
363
+ end
364
+ end
365
+
366
+ # Destroy elastic IPs
367
+ ips = compute.addresses
368
+ ip_count = ips.size
369
+ if ip_count > 0
370
+ @io_semaphore.synchronize do
371
+ user_say_region account, aws_region, "Destroying #{ip_count} IPs"
372
+ end
373
+ Parallel.each(ips, in_threads: ip_count) do |ip|
374
+ @io_semaphore.synchronize do
375
+ user_say_region account, aws_region, "Destroying #{ip.public_ip}"
376
+ end
377
+ ip.destroy
378
+ end
379
+ else
380
+ @io_semaphore.synchronize do
381
+ user_say_region account, aws_region, "No IP addresses to destroy"
382
+ end
383
+ end
384
+
385
+ # Destroy elastic IPs
386
+ ips = compute.addresses
387
+ ip_count = ips.size
388
+ if ip_count > 0
389
+ @io_semaphore.synchronize do
390
+ user_say_region account, aws_region, "Destroying #{ip_count} IPs"
391
+ end
392
+ Parallel.each(ips, in_threads: ip_count) do |ip|
393
+ @io_semaphore.synchronize do
394
+ user_say_region account, aws_region, "Destroying #{ip.public_ip}"
395
+ end
396
+ ip.destroy
397
+ end
398
+ else
399
+ @io_semaphore.synchronize do
400
+ user_say_region account, aws_region, "No IP addresses to destroy"
401
+ end
402
+ end
403
+
404
+ # Destroy VPC security groups
405
+ sg = compute.security_groups.select {|v| v.vpc_id}
406
+ if sg.size > 0
407
+ @io_semaphore.synchronize do
408
+ user_say_region account, aws_region, "Destroying #{sg.size} VPC security groups"
409
+ end
410
+ Parallel.each(sg, in_threads: sg.size) do |sg|
411
+ @io_semaphore.synchronize do
412
+ user_say_region account, aws_region, "Destroying #{sg.name}"
413
+ end
414
+ begin
415
+ sg.destroy
416
+ rescue Fog::Compute::AWS::Error
417
+ # quietly ignore SGs we can't delete anyway
418
+ rescue => e
419
+ user_say_region account, aws_region, e.message, :red
420
+ end
421
+ end
422
+ else
423
+ @io_semaphore.synchronize do
424
+ user_say_region account, aws_region, "No VPC security groups to destroy"
425
+ end
426
+ end
427
+
428
+ # Destroy subnets
429
+ subnets = compute.subnets
430
+ subnet_count = subnets.size
431
+ if subnet_count > 0
432
+ @io_semaphore.synchronize do
433
+ user_say_region account, aws_region, "Destroying #{subnet_count} subnets"
434
+ end
435
+ Parallel.each(subnets, in_threads: subnet_count) do |subnet|
436
+ @io_semaphore.synchronize do
437
+ user_say_region account, aws_region, "Destroying #{subnet.subnet_id} (#{subnet.cidr_block})"
438
+ end
439
+ subnet.destroy
440
+ end
441
+ else
442
+ @io_semaphore.synchronize do
443
+ user_say_region account, aws_region, "No subnets to destroy"
444
+ end
445
+ end
446
+
447
+ # TODO: detach Internet Gtw from VPC
448
+ # TODO: delete IG
449
+
450
+
451
+ # TODO: figure out how to delete route tables; perhaps detact them first?
452
+ # rts = compute.route_tables
453
+ # @io_semaphore.synchronize do
454
+ # user_say_region account, aws_region, "Destroying #{rts.size} Route Tables"
455
+ # end
456
+ # retry_rts = ThreadSafe::Array.new
457
+ # Parallel.each(rts, in_threads: rts.size) do |rt|
458
+ # @io_semaphore.synchronize do
459
+ # user_say_region account, aws_region, "Destroying #{rt.id}"
460
+ # end
461
+ # begin
462
+ # rt.routes.each do |route|
463
+ # begin
464
+ # compute.delete_route(rt.id, route["destinationCidrBlock"])
465
+ # rescue InvalidParameterValue
466
+ # # quietly ignore local routes that are implicitly deleted when we destroy rt
467
+ # end
468
+ # end
469
+ # rt.destroy
470
+ # rescue
471
+ # retry_rts << rt
472
+ # end
473
+ # end
474
+ #
475
+ # Parallel.each(retry_rts, in_threads: retry_rts.size) do |rt|
476
+ # @io_semaphore.synchronize do
477
+ # user_say_region account, aws_region, "Trying again to destroy #{rt.id}"
478
+ # end
479
+ # begin
480
+ # rt.routes.each do |route|
481
+ # begin
482
+ # compute.delete_route(rt.id, route["destinationCidrBlock"])
483
+ # rescue InvalidParameterValue
484
+ # # quietly ignore local routes that are implicitly deleted when we destroy rt
485
+ # end
486
+ # end
487
+ # rt.destroy
488
+ # rescue => e
489
+ # user_say_region account, aws_region, e.message, :red
490
+ # end
491
+ # end
492
+ #
493
+ # vpcs = compute.vpcs
494
+ # original_vpc_count = vpcs.size
495
+ # @io_semaphore.synchronize do
496
+ # user_say_region account, aws_region, "Destroying #{original_vpc_count} VPCs"
497
+ # end
498
+ # Parallel.each(vpcs, in_threads: vpcs.size) do |vpc|
499
+ # @io_semaphore.synchronize do
500
+ # user_say_region account, aws_region, "Destroying #{vpc.id} (#{vpc.cidr_block})"
501
+ # end
502
+ # begin
503
+ # vpc.destroy
504
+ # rescue => e
505
+ # user_say_region account, aws_region, e.message, :red
506
+ # end
507
+ # end
508
+ end
509
+
510
+ def aws_regions(common_only=true)
511
+ if common_only
512
+ ["us-east-1", "us-west-2"]
513
+ else
514
+ ["eu-central-1", "sa-east-1", "ap-northeast-1",
515
+ "eu-west-1", "us-east-1", "us-west-1", "us-west-2",
516
+ "ap-southeast-2", "ap-southeast-1"]
517
+ end
518
+ end
519
+
520
+ # returns { servers: num-of-servers-across-regions }
521
+ def account_summary(credentials)
522
+ region_server_summary = ThreadSafe::Hash.new
523
+ Parallel.each(aws_regions, in_threads: aws_regions.size) do |aws_region|
524
+ compute = Fog::Compute::AWS.new(credentials.merge(region: aws_region))
525
+ count = compute.servers.select {|s| s.state != "terminated" }.size
526
+ region_server_summary[aws_region] = count
527
+ end
528
+ summary = {}
529
+ summary[:servers] = region_server_summary.inject(0) do |count, pair|
530
+ region, servers = pair
531
+ count + servers
532
+ end
533
+ summary
534
+ end
535
+ end
@@ -0,0 +1,3 @@
1
+ module AwsStudentAccounts
2
+ VERSION = "0.1.0"
3
+ end
data/spec/app_spec.rb ADDED
@@ -0,0 +1,2 @@
1
+ describe AwsStudentAccounts::App do
2
+ end
@@ -0,0 +1,7 @@
1
+ ---
2
+ :student1:
3
+ :aws_access_key_id: STUDENT1KEY
4
+ :aws_secret_access_key: STUDENT1SECRET
5
+ :student2:
6
+ :aws_access_key_id: STUDENT2KEY
7
+ :aws_secret_access_key: STUDENT2SECRET
@@ -0,0 +1,85 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
4
+
5
+ require "rubygems"
6
+ require "bundler"
7
+ Bundler.setup(:default, :test)
8
+
9
+ $:.unshift(File.expand_path("../../lib", __FILE__))
10
+
11
+ require "rspec/core"
12
+ require "aws_student_accounts"
13
+
14
+ # for the #sh helper
15
+ require "rake"
16
+ require "rake/file_utils"
17
+
18
+ # load all files in spec/support/* (but not lower down)
19
+ Dir[File.dirname(__FILE__) + '/support/*'].each do |path|
20
+ require path unless File.directory?(path)
21
+ end
22
+
23
+ def spec_fixture(filename)
24
+ File.expand_path("../fixtures/#{filename}", __FILE__)
25
+ end
26
+
27
+ def files_match(filename, expected_filename)
28
+ file = File.read(filename)
29
+ expected_file = File.read(expected_filename)
30
+ expect(file).to eq(expected_file)
31
+ end
32
+
33
+ def yaml_files_match(filename, expected_filename)
34
+ yaml = YAML.load_file(filename)
35
+ expected_yaml = YAML.load_file(expected_filename)
36
+ expect(yaml).to eq(expected_yaml)
37
+ end
38
+
39
+ def setup_home_dir
40
+ FileUtils.rm_rf(home_dir)
41
+ FileUtils.mkdir_p(home_dir)
42
+ ENV['HOME'] = home_dir
43
+ end
44
+
45
+ def setup_work_dir
46
+ FileUtils.mkdir_p(work_dir)
47
+ FileUtils.chdir(work_dir)
48
+ end
49
+
50
+ def work_dir
51
+ File.join(home_dir)
52
+ end
53
+
54
+ def home_dir
55
+ File.expand_path("../../tmp/home", __FILE__)
56
+ end
57
+
58
+ # returns the file path to a file
59
+ # in the fake $HOME folder
60
+ def home_file(*path)
61
+ File.join(ENV['HOME'], *path)
62
+ end
63
+
64
+ # returns the file path to a file
65
+ # in the fake ~/workspace/deployments/microbosh folder
66
+ def work_file(*path)
67
+ File.join(work_dir, *path)
68
+ end
69
+
70
+ RSpec.configure do |c|
71
+ c.before(:each) do
72
+ setup_home_dir
73
+ setup_work_dir
74
+ end
75
+
76
+ c.color = true
77
+ end
78
+
79
+ def get_tmp_file_path(content)
80
+ tmp_file = File.open(File.join(Dir.mktmpdir, "tmp"), "w")
81
+ tmp_file.write(content)
82
+ tmp_file.close
83
+
84
+ tmp_file.path
85
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_student_accounts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dr Nic Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-12 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.19'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.19'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.26'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.26'
41
+ - !ruby/object:Gem::Dependency
42
+ name: parallel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thread_safe
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Manage student AWS accounts in bulk
112
+ email:
113
+ - drnicwilliams@gmail.com
114
+ executables:
115
+ - aws_student_accounts
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - aws_student_accounts.gemspec
126
+ - bin/aws_student_accounts
127
+ - lib/aws_student_accounts.rb
128
+ - lib/aws_student_accounts/app.rb
129
+ - lib/aws_student_accounts/version.rb
130
+ - spec/app_spec.rb
131
+ - spec/fixtures/aws.yml
132
+ - spec/spec_helper.rb
133
+ homepage: ''
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.4.3
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Manage student AWS accounts in bulk
157
+ test_files:
158
+ - spec/app_spec.rb
159
+ - spec/fixtures/aws.yml
160
+ - spec/spec_helper.rb