aws_student_accounts 0.1.0

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 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