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 +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +2 -0
- data/aws_student_accounts.gemspec +28 -0
- data/bin/aws_student_accounts +6 -0
- data/lib/aws_student_accounts.rb +6 -0
- data/lib/aws_student_accounts/app.rb +535 -0
- data/lib/aws_student_accounts/version.rb +3 -0
- data/spec/app_spec.rb +2 -0
- data/spec/fixtures/aws.yml +7 -0
- data/spec/spec_helper.rb +85 -0
- metadata +160 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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,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,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
|
data/spec/app_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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
|