cloudhealth-setup 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cloudhealth-setup +10 -0
- data/bin/fog +16 -0
- data/bin/nokogiri +16 -0
- data/cloudhealth-setup.gemspec +26 -0
- data/lib/cht/aws.rb +273 -0
- data/lib/cht/error_handling.rb +16 -0
- data/lib/cht/mechanize.rb +336 -0
- data/lib/cht/output.rb +32 -0
- data/lib/cht/policy.rb +108 -0
- data/lib/cloudhealth-setup.rb +244 -0
- metadata +174 -0
data/bin/fog
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'fog' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('fog', 'fog')
|
data/bin/nokogiri
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'nokogiri' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('nokogiri', 'nokogiri')
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'cloudhealth-setup'
|
3
|
+
s.version = '0.0.10'
|
4
|
+
s.date = '2013-08-31'
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.authors = ['CloudHealth Technologies']
|
7
|
+
s.email = ['support@cloudhealthtech.com']
|
8
|
+
s.homepage = 'http://www.cloudhealthtech.com'
|
9
|
+
s.summary = 'Configures an Amazon AWS account for use with the CloudHealth service'
|
10
|
+
s.description = 'Configures an Amazon AWS account for use with the CloudHealth service, including creating a least privilege read only AWS user and enabling the retrieval of cost and usage information.'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.has_rdoc = false
|
13
|
+
|
14
|
+
s.add_dependency("nokogiri", "= 1.5.8")
|
15
|
+
s.add_dependency("fog", "= 1.12.1")
|
16
|
+
s.add_dependency("json", "= 1.8.0")
|
17
|
+
s.add_dependency("excon", "= 0.23.0")
|
18
|
+
s.add_dependency("mixlib-cli", "= 1.3.0")
|
19
|
+
s.add_dependency("mechanize", "= 2.5.1")
|
20
|
+
s.add_dependency("highline", "= 1.6.19")
|
21
|
+
|
22
|
+
s.bindir = "bin"
|
23
|
+
s.files = Dir.glob('{bin,lib}/**/*') + %w[cloudhealth-setup.gemspec]
|
24
|
+
s.executables = Dir.glob('bin/**/*').map { |file| File.basename(file) }
|
25
|
+
s.require_paths = ['lib']
|
26
|
+
end
|
data/lib/cht/aws.rb
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
require "highline/system_extensions"
|
2
|
+
include HighLine::SystemExtensions
|
3
|
+
|
4
|
+
class Setup
|
5
|
+
def iam
|
6
|
+
begin
|
7
|
+
Fog::AWS::IAM.new({:aws_access_key_id => @aws_key, :aws_secret_access_key => @aws_secret})
|
8
|
+
rescue => e
|
9
|
+
critical_failure("Failed to connect to Amazon AWS IAM: Please ensure your provided credentials are correct and you have internet access. #{e if @verbose}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def s3
|
14
|
+
begin
|
15
|
+
Fog::Storage.new({:provider => 'AWS', :aws_access_key_id => @aws_key, :aws_secret_access_key => @aws_secret})
|
16
|
+
rescue => e
|
17
|
+
critical_failure("Failed to connect to Amazon AWS S3: Please ensure your provided credentials are correct and you have internet access. #{e if @verbose}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_iam_credentials
|
22
|
+
puts "Please wait, ensuring the provided credentials work."
|
23
|
+
begin
|
24
|
+
@iam.list_users
|
25
|
+
rescue => e
|
26
|
+
if @input_file.nil? || @input_file.empty?
|
27
|
+
# Single account mode
|
28
|
+
puts "Could not login to your account with the provided Amazon Web Services credentials. Please check your AWS Access Key and Secret Access Key and try again. If issue persists contact support@cloudhealthtech.com."
|
29
|
+
exit
|
30
|
+
else
|
31
|
+
# Multi account mode
|
32
|
+
raise SetupFailed, "Could not login to your account with the provided Amazon Web Services credentials. Please check your AWS Access Key and Secret Access Key and try again. If issue persists contact support@cloudhealthtech.com."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_ro_user
|
38
|
+
begin
|
39
|
+
if user_exists
|
40
|
+
puts "[X] AWS Read-Only user (#{@aws_ro_name}) is setup -- Exists"
|
41
|
+
else
|
42
|
+
puts "[ ] AWS Read-Only user (#{@aws_ro_name}) not setup -- No user"
|
43
|
+
end
|
44
|
+
if user_has_policy
|
45
|
+
puts "[X] AWS Read-Only user has a policy -- Exists"
|
46
|
+
else
|
47
|
+
puts "[ ] AWS Read-Only user has no policy -- No policy"
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
puts " We were unable to test your read only user."
|
51
|
+
puts " Please contact CloudHealth support at support@cloudhealthtech.com."
|
52
|
+
warning(e)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def uninstall_ro_user
|
57
|
+
begin
|
58
|
+
print "Delete Cloudhealth Read-only user #{@aws_ro_name},attached policies, profiles (y/n)? "
|
59
|
+
k = get_character
|
60
|
+
if k.chr == "y"
|
61
|
+
puts "Deleting user #{@aws_ro_name} and all associated policies and login profiles..."
|
62
|
+
user_policies = @iam.list_user_policies(@aws_ro_name)
|
63
|
+
|
64
|
+
user_policies.body['PolicyNames'].each do |policy|
|
65
|
+
puts " Deleting user policy #{policy} attached to this user."
|
66
|
+
@iam.delete_user_policy(@aws_ro_name, policy)
|
67
|
+
end
|
68
|
+
|
69
|
+
puts " Deleting users access keys..."
|
70
|
+
access_keys = @iam.list_access_keys('UserName' => @aws_ro_name).body
|
71
|
+
|
72
|
+
access_keys['AccessKeys'].each do |access_key|
|
73
|
+
puts " Deleting access key #{access_key['AccessKeyId']}..."
|
74
|
+
@iam.delete_access_key(access_key['AccessKeyId'], 'UserName' => @aws_ro_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
begin
|
78
|
+
puts " Deleting login profile..."
|
79
|
+
@iam.delete_login_profile(@aws_ro_name)
|
80
|
+
rescue => e
|
81
|
+
puts " User does not have a login profile, skipping."
|
82
|
+
warning(e)
|
83
|
+
end
|
84
|
+
|
85
|
+
puts " Deleting user...."
|
86
|
+
@iam.delete_user(@aws_ro_name)
|
87
|
+
puts " IAM User #{@aws_ro_name} deleted."
|
88
|
+
else
|
89
|
+
puts " You did not agree to delete the AWS Read only user #{aws_ro_name}."
|
90
|
+
end
|
91
|
+
rescue => e
|
92
|
+
if defined? e.response
|
93
|
+
if e.response.status == 409
|
94
|
+
puts " Could not delete user and/or login profile/policy, subordinate entities exist."
|
95
|
+
elsif e.response.status == 404
|
96
|
+
puts " Could not delete user and/or login profile/policy, user/profile/policy does not exist."
|
97
|
+
else
|
98
|
+
puts " Could not delete User or Policy/profile, unknown error: #{e.response.inspect}"
|
99
|
+
end
|
100
|
+
else
|
101
|
+
puts " Could not delete: #{e.message}"
|
102
|
+
warning(e) if @verbose
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup_ro_user
|
108
|
+
user_created = nil
|
109
|
+
begin
|
110
|
+
puts "Setting up AWS Read only user... "
|
111
|
+
if @aws_ro_name.nil?
|
112
|
+
puts " Name not specified -- Skipping."
|
113
|
+
else
|
114
|
+
if user_exists
|
115
|
+
if @ro_user_exists
|
116
|
+
puts " User #{@aws_ro_name} exists... continuing due to --user-exists"
|
117
|
+
puts " Note: CSV Output file will NOT be complete. Since we did not create the aws read only user"
|
118
|
+
puts " You must fill in the blanks of the CSV if you plan on importing the CSV on the website."
|
119
|
+
else
|
120
|
+
#TODO This should not exit() in multi-account import mode, it should be raised up and caught. e.g. SetupFailed
|
121
|
+
puts "User #{@aws_ro_name} exists already, If this was your intention please re-run with --user-exists and ensure you update the CSV manually or choose another username via -r <name>"
|
122
|
+
exit
|
123
|
+
end
|
124
|
+
else
|
125
|
+
puts " Creating user... "
|
126
|
+
user_create = @iam.create_user(@aws_ro_name)
|
127
|
+
key_create = @iam.create_access_key('UserName' => @aws_ro_name)
|
128
|
+
access_key = key_create.body['AccessKey']['AccessKeyId']
|
129
|
+
secret_key = key_create.body['AccessKey']['SecretAccessKey']
|
130
|
+
arn = user_create.body['User']['Arn']
|
131
|
+
user_pass = create_user_password
|
132
|
+
@created_account.merge!(:access_key => access_key, :secret_key => secret_key, :user_pass => user_pass, :user => @aws_ro_name)
|
133
|
+
user_created = " The user #{@aws_ro_name} has been created with password #{user_pass}, access key #{access_key}, and secret key #{secret_key}"
|
134
|
+
end
|
135
|
+
if user_has_policy
|
136
|
+
puts " User policy already exists..."
|
137
|
+
else
|
138
|
+
puts " Creating user policy... "
|
139
|
+
@iam.put_user_policy(@aws_ro_name, "CHTRoPolicy", aws_ro_policy)
|
140
|
+
end
|
141
|
+
puts user_created unless user_created.nil?
|
142
|
+
puts " Setup of AWS Read only user completed"
|
143
|
+
|
144
|
+
end
|
145
|
+
rescue => e
|
146
|
+
puts " We were unable to create a read only user."
|
147
|
+
puts " Please contact CloudHealth support at support@cloudhealthtech.com."
|
148
|
+
warning(e)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_s3_bucket
|
153
|
+
begin
|
154
|
+
if @s3.directories.collect{|d| d.key}.include?(@setup_bucket)
|
155
|
+
puts "[X] S3 Billing bucket (#{@setup_bucket}) -- Enabled"
|
156
|
+
else
|
157
|
+
puts "[ ] S3 Billing bucket (#{@setup_bucket}) -- Does not exist"
|
158
|
+
end
|
159
|
+
|
160
|
+
if bucket_has_policy
|
161
|
+
puts "[X] S3 Billing bucket policy -- Exists"
|
162
|
+
else
|
163
|
+
puts "[ ] S3 Billing bucket policy -- No Policy"
|
164
|
+
end
|
165
|
+
rescue => e
|
166
|
+
puts " We were unable to test your S3 billing bucket. You can manually enable this,"
|
167
|
+
puts " by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
|
168
|
+
warning(e)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def setup_s3_bucket
|
173
|
+
begin
|
174
|
+
puts "Setting up S3 billing bucket... "
|
175
|
+
if @s3.directories.collect{|d| d.key}.include?(@setup_bucket)
|
176
|
+
puts " Bucket exists..."
|
177
|
+
else
|
178
|
+
puts " Creating bucket... "
|
179
|
+
begin
|
180
|
+
@s3.directories.create(:key => @setup_bucket, :public => false)
|
181
|
+
rescue => e
|
182
|
+
if e.response.status == 409
|
183
|
+
puts " The bucket you are trying to use is already created, but owned by another account. Please use another bucket name."
|
184
|
+
else
|
185
|
+
raise e
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
if bucket_has_policy
|
191
|
+
puts " Bucket already has policy... "
|
192
|
+
else
|
193
|
+
puts " Creating bucket policy..."
|
194
|
+
@s3.put_bucket_policy(@setup_bucket, bucket_policy)
|
195
|
+
end
|
196
|
+
@created_account.merge!(:s3_bucket => @setup_bucket)
|
197
|
+
puts " Bucket setup finished"
|
198
|
+
rescue => e
|
199
|
+
puts " We were unable to setup an S3 billing bucket. You can manually enable this,"
|
200
|
+
puts " by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
|
201
|
+
warning(e)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def user_exists
|
206
|
+
begin
|
207
|
+
@iam.get_user(@aws_ro_name)
|
208
|
+
true
|
209
|
+
rescue
|
210
|
+
false
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_account_alias
|
215
|
+
begin
|
216
|
+
acct_aliases = @iam.list_account_aliases.body['AccountAliases']
|
217
|
+
if acct_aliases.empty?
|
218
|
+
puts "[ ] AWS Account alias is not setup, Account ID: #{@aws_account_id} used instead -- Not setup"
|
219
|
+
else
|
220
|
+
puts "[X] AWS Account alias(es) are setup (#{acct_aliases.map{|aa| aa.strip}.join(', ')}), ID: #{@aws_account_id} -- Setup"
|
221
|
+
end
|
222
|
+
rescue => e
|
223
|
+
puts " We were unable to check for an account alias."
|
224
|
+
puts " Please contact CloudHealth support at support@cloudhealthtech.com"
|
225
|
+
warning(e)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def setup_account_alias
|
230
|
+
begin
|
231
|
+
if @aws_acct_alias.nil?
|
232
|
+
puts "AWS account alias not given, will use account id: #{@created_account[:account_id]} -- Skipping alias setup."
|
233
|
+
@created_account.merge!(:account_alias => @created_account[:account_id],
|
234
|
+
:account_url => "https://#{@created_account[:account_id]}.signin.aws.amazon.com/")
|
235
|
+
else
|
236
|
+
puts "Setting up account alias... "
|
237
|
+
begin
|
238
|
+
@iam.create_account_alias(@aws_acct_alias)
|
239
|
+
puts " alias set to #{@aws_acct_alias}..."
|
240
|
+
rescue => e
|
241
|
+
if e.class == Fog::AWS::IAM::EntityAlreadyExists
|
242
|
+
puts " Account alias was already set."
|
243
|
+
elsif e.response.status == 409
|
244
|
+
puts " Account alias was already set."
|
245
|
+
else
|
246
|
+
raise e
|
247
|
+
end
|
248
|
+
end
|
249
|
+
@created_account.merge!(:account_alias => @aws_acct_alias,
|
250
|
+
:account_url => "https://#{@aws_acct_alias}.signin.aws.amazon.com/")
|
251
|
+
end
|
252
|
+
rescue => e
|
253
|
+
puts " We were unable to create an account alias."
|
254
|
+
puts " Please contact CloudHealth support at support@cloudhealthtech.com"
|
255
|
+
warning(e)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def create_user_password
|
260
|
+
puts " Setting user password"
|
261
|
+
pw = SecureRandom.hex
|
262
|
+
begin
|
263
|
+
@iam.create_login_profile(@aws_ro_name, pw)
|
264
|
+
rescue => e
|
265
|
+
if e.response.status == 409
|
266
|
+
puts " User already had a password set..."
|
267
|
+
else
|
268
|
+
raise e
|
269
|
+
end
|
270
|
+
end
|
271
|
+
pw
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
class Setup
|
2
|
+
AWS_LOGIN_URL = 'https://www.amazon.com/ap/signin?openid.assoc_handle=aws&openid.return_to=https%3A%2F%2Fportal.aws.amazon.com%2Fgp%2Faws%2Fdeveloper%2Faccount%2Findex.html%3Fie%3DUTF8%26action%3Dactivity-summary&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&action=&disableCorpSignUp=&clientContext=&marketPlaceId=&poolName=&authCookies=&pageId=aws.ssop&siteState=&accountStatusPolicy=&sso=&openid.pape.preferred_auth_policies=MultifactorPhysical&openid.pape.max_auth_age=3600&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&server=%2Fap%2Fsignin%3Fie%3DUTF8&accountPoolAlias='
|
3
|
+
|
4
|
+
def mech_browser
|
5
|
+
# Creating Mechanize object
|
6
|
+
browser = Mechanize.new
|
7
|
+
browser.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
8
|
+
browser.redirect_ok = true
|
9
|
+
# Let's give some header here
|
10
|
+
browser.request_headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4'
|
11
|
+
browser.request_headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
12
|
+
browser.request_headers['application/xml'] = '*/*'
|
13
|
+
browser
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_page
|
17
|
+
begin
|
18
|
+
url = AWS_LOGIN_URL
|
19
|
+
page = @browser.get(url)
|
20
|
+
signed_in = page.link_with(:text => "Sign Out")
|
21
|
+
|
22
|
+
if signed_in.nil? # Since @browser is shared, sometimes we are already logged in.
|
23
|
+
form = page.form_with(:name => 'signIn')
|
24
|
+
form['email'] = @aws_user
|
25
|
+
form['password'] = @aws_pass
|
26
|
+
login_page = form.submit
|
27
|
+
|
28
|
+
if login_page.code.to_i != 200
|
29
|
+
puts "The login page returned error code #{login_page.code}"
|
30
|
+
raise SetupFailed, " Could not login to AWS Web Console, Amazon may be experiencing issues or the credentials you provided are incorrect. HTTP Code: #{login_page.code}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
account_id_search = page.search('//span[@class="txtxxsm"]/text()')
|
35
|
+
unless account_id_search.nil? || account_id_search.size == 0 || @created_account[:account_id]
|
36
|
+
firstw, secondw, account_id_long = account_id_search.first.content.strip.split(" ")
|
37
|
+
account_id = account_id_long.gsub("-","")
|
38
|
+
puts " Account ID for this account is: #{account_id.to_s}" if @verbose
|
39
|
+
@aws_account_id = account_id
|
40
|
+
@created_account.merge!(:account_id => account_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
page = @browser.get("https://portal.aws.amazon.com/gp/aws/developer/account?ie=UTF8&action=billing-preferences")
|
44
|
+
|
45
|
+
if page.code.to_i != 200
|
46
|
+
raise SetupFailed, " Could not login to AWS Web Console, Amazon may be experiencing issues or the credentials you provided are incorrect. HTTP Code: #{page.code}"
|
47
|
+
end
|
48
|
+
|
49
|
+
page
|
50
|
+
rescue => e
|
51
|
+
raise SetupFailed, e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_web_credentials
|
56
|
+
get_page
|
57
|
+
get_page # We run this twice, only on the second login do we find our account id on the initial page. This primes the pump so to speak anyway for subsequent usage. TODO: Fix me
|
58
|
+
if @aws_account_id.nil? || @aws_account_id.empty?
|
59
|
+
if @input_file.nil? || @input_file.empty?
|
60
|
+
# Single account mode
|
61
|
+
puts "Could not login to your account with the provided Amazon Web Services credentials. Please check your provided email address and password and try again. If issue persists contact support@cloudhealthtech.com."
|
62
|
+
exit
|
63
|
+
else
|
64
|
+
# Multi account mode
|
65
|
+
raise SetupFailed, "Could not login to your account with the provided Amazon Web Services credentials. Please check your provided email address and password and try again. If issue persists contact support@cloudhealthtech.com."
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_consolidated
|
71
|
+
page = @browser.get("https://portal.aws.amazon.com/gp/aws/developer/account?ie=UTF8&action=consolidated-billing")
|
72
|
+
consolidated_link = page.link_with(:href => "https://portal.aws.amazon.com/gp/aws/developer/subscription/index.html?ie=UTF8&productCode=AWSCBill")
|
73
|
+
if consolidated_link.nil?
|
74
|
+
puts "[O] Account is setup on consolidated billing - Optional"
|
75
|
+
else
|
76
|
+
#We must be on consolidated billing
|
77
|
+
puts "[O] Account is not on consolidated billing - Optional"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def account_consolidated
|
82
|
+
page = @browser.get("https://portal.aws.amazon.com/gp/aws/developer/account?ie=UTF8&action=consolidated-billing")
|
83
|
+
consolidated_link = page.link_with(:href => "https://portal.aws.amazon.com/gp/aws/developer/subscription/index.html?ie=UTF8&productCode=AWSCBill")
|
84
|
+
if consolidated_link.nil?
|
85
|
+
@created_account.merge!(:consolidated => true)
|
86
|
+
puts "Account is on consolidated billing"
|
87
|
+
else
|
88
|
+
#We must be on consolidated billing
|
89
|
+
@created_account.merge!(:consolidated => false)
|
90
|
+
puts "Account is not on consolidated billing"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def dump_page(page)
|
95
|
+
no = rand(500)
|
96
|
+
puts "Writing out page as #{no}.html"
|
97
|
+
File.open("#{no}.html", 'w') do |file|
|
98
|
+
file << page.body
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_monthly_report
|
103
|
+
begin
|
104
|
+
page = get_page
|
105
|
+
monthly_report_form = page.form_with(:name => "csvReportOptInForm")
|
106
|
+
mrf_enabled = monthly_report_form.field_with(:name => "buttonOption")
|
107
|
+
|
108
|
+
if mrf_enabled.value == "EnableCSVReport"
|
109
|
+
puts "[ ] Monthly report -- Disabled"
|
110
|
+
elsif mrf_enabled.value == "CancelCSVReport"
|
111
|
+
puts "[X] Monthly report -- Enabled"
|
112
|
+
end
|
113
|
+
rescue => e
|
114
|
+
puts " We were unable to test if monthly report access is enabled."
|
115
|
+
puts " This setting can be checked manually under Billing Preferences from the AWS Account page."
|
116
|
+
warning(e)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def setup_monthly_report
|
121
|
+
begin
|
122
|
+
puts "Setting up monthly report access... "
|
123
|
+
page = get_page
|
124
|
+
monthly_report_form = page.form_with(:name => "csvReportOptInForm")
|
125
|
+
mrf_enabled = monthly_report_form.field_with(:name => "buttonOption")
|
126
|
+
|
127
|
+
if mrf_enabled.value == "EnableCSVReport"
|
128
|
+
puts " Report not enabled, enabling... "
|
129
|
+
monthly_report_form.submit
|
130
|
+
elsif mrf_enabled.value == "CancelCSVReport"
|
131
|
+
puts " Report already enabled... "
|
132
|
+
end
|
133
|
+
puts " Monthly report access setup complete."
|
134
|
+
rescue => e
|
135
|
+
puts " We were unable to setup monthly report access."
|
136
|
+
puts " This setting can be enabled manually under Billing Preferences from the AWS Account page."
|
137
|
+
warning(e)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_prog_access
|
142
|
+
begin
|
143
|
+
page = get_page
|
144
|
+
prog_access_form = page.form_with(:name => "paOptInForm")
|
145
|
+
paf_enabled = prog_access_form.field_with(:name => "existingS3BucketName")
|
146
|
+
|
147
|
+
if paf_enabled.nil?
|
148
|
+
puts "[ ] Programmatic access not setup -- Disabled"
|
149
|
+
else
|
150
|
+
puts "[X] Programmatic access is setup -- Enabled"
|
151
|
+
end
|
152
|
+
rescue => e
|
153
|
+
puts " We were unable to test programmatic access to your billing information."
|
154
|
+
puts " You can manually enable/test this by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
|
155
|
+
warning(e)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def setup_prog_access
|
160
|
+
begin
|
161
|
+
puts "Setting up programmatic access to billing... "
|
162
|
+
page = get_page
|
163
|
+
prog_access_form = page.form_with(:name => "paOptInForm")
|
164
|
+
paf_enabled = prog_access_form.field_with(:name => "existingS3BucketName")
|
165
|
+
|
166
|
+
if paf_enabled.nil?
|
167
|
+
puts " Enabling access in bucket #{@setup_bucket}... "
|
168
|
+
prog_access_form['s3BucketName'] = @setup_bucket
|
169
|
+
prog_access_form.submit
|
170
|
+
else
|
171
|
+
puts " Access already enabled..."
|
172
|
+
end
|
173
|
+
puts " Setup of programmatic access to billing finished"
|
174
|
+
rescue => e
|
175
|
+
puts " We were unable to setup programmatic access to your billing information."
|
176
|
+
puts " You can manually enable this by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
|
177
|
+
warning(e)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_detailed_billing
|
182
|
+
begin
|
183
|
+
page = get_page
|
184
|
+
detailed_billing_form = page.form_with(:name=>'hourlyOptInForm')
|
185
|
+
bill_enabled = detailed_billing_form.field_with(:name => "buttonOptionHourly")
|
186
|
+
|
187
|
+
if bill_enabled.value == "EnableHourly"
|
188
|
+
puts "[ ] Detailed billing report not setup -- Disabled"
|
189
|
+
elsif bill_enabled.value == "DisableHourly"
|
190
|
+
puts "[X] Detailed billing report setup -- Enabled"
|
191
|
+
end
|
192
|
+
rescue => e
|
193
|
+
puts " We were unable to test detailed billing reports."
|
194
|
+
puts " This setting can be enabled/tested manually under Billing Preferences from the AWS account page."
|
195
|
+
warning(e)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def setup_detailed_billing
|
200
|
+
begin
|
201
|
+
puts "Setting up detailed billing report..."
|
202
|
+
page = get_page
|
203
|
+
detailed_billing_form = page.form_with(:name=>'hourlyOptInForm')
|
204
|
+
bill_enabled = detailed_billing_form.field_with(:name => "buttonOptionHourly")
|
205
|
+
|
206
|
+
if bill_enabled.value == "EnableHourly"
|
207
|
+
puts " Enabling detailed billing report... "
|
208
|
+
detailed_billing_form.submit
|
209
|
+
elsif bill_enabled.value == "DisableHourly"
|
210
|
+
puts " Report already enabled... "
|
211
|
+
end
|
212
|
+
puts " Report setup finished"
|
213
|
+
rescue => e
|
214
|
+
puts " We were unable to enable detailed billing reports."
|
215
|
+
puts " This setting can be enabled manually under Billing Preferences from the AWS account page."
|
216
|
+
warning(e)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_cost_alloc
|
221
|
+
begin
|
222
|
+
page = get_page
|
223
|
+
cost_alloc_form = page.form_with(:name=>'carOptInForm')
|
224
|
+
car_enabled = cost_alloc_form.field_with(:name => "buttonOptionCAR")
|
225
|
+
|
226
|
+
if car_enabled.value == "EnableCAR"
|
227
|
+
puts "[ ] Cost allocation report not setup -- Disabled"
|
228
|
+
elsif car_enabled.value == "DisableCAR"
|
229
|
+
puts "[X] Cost allocation report setup -- Enabled"
|
230
|
+
end
|
231
|
+
rescue => e
|
232
|
+
puts " We were unable to test cost allocation reports."
|
233
|
+
puts " This setting can be tested manually under Billing Preferences from the AWS account page."
|
234
|
+
warning(e)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def setup_cost_alloc
|
239
|
+
begin
|
240
|
+
puts "Setting up cost allocation report... "
|
241
|
+
page = get_page
|
242
|
+
cost_alloc_form = page.form_with(:name=>'carOptInForm')
|
243
|
+
car_enabled = cost_alloc_form.field_with(:name => "buttonOptionCAR")
|
244
|
+
|
245
|
+
if car_enabled.value == "EnableCAR"
|
246
|
+
puts " Enabling cost allocation report... "
|
247
|
+
cost_alloc_form.submit
|
248
|
+
elsif car_enabled.value == "DisableCAR"
|
249
|
+
puts " Report already enabled... "
|
250
|
+
end
|
251
|
+
puts " Report setup finished"
|
252
|
+
rescue => e
|
253
|
+
puts " We were unable to enable cost allocation reports."
|
254
|
+
puts " This setting can be enabled manually under Billing Preferences from the AWS account page."
|
255
|
+
warning(e)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_checkboxes
|
260
|
+
begin
|
261
|
+
get_page
|
262
|
+
page = @browser.get("https://portal.aws.amazon.com/gp/aws/manageYourAccount")
|
263
|
+
checkbox_account_activity_search = page.search('[@id="account_activity_checkbox"]')
|
264
|
+
usage_report_search = page.search('[@id="usage_reports_checkbox"]')
|
265
|
+
activate_button_search = page.search('[@id="activateIAMUserAccess"]')
|
266
|
+
deactivate_button_search = page.search('[@id="deactivateIAMUserAccess"]')
|
267
|
+
|
268
|
+
activate_hidden = begin
|
269
|
+
activate_button_search.first.attributes['style'].value.include?("display:none")
|
270
|
+
rescue
|
271
|
+
false
|
272
|
+
end
|
273
|
+
|
274
|
+
deactivate_hidden = begin
|
275
|
+
deactivate_button_search.first.attributes['style'].value.include?("display:none")
|
276
|
+
rescue
|
277
|
+
false
|
278
|
+
end
|
279
|
+
|
280
|
+
if activate_hidden
|
281
|
+
#Activate button is hidden, Deactivate is shown
|
282
|
+
if checkbox_account_activity_search.first.attributes['checked'].nil?
|
283
|
+
# Not checked
|
284
|
+
puts "[ ] IAM access to Account Activity not setup -- Disabled"
|
285
|
+
else
|
286
|
+
# Checked
|
287
|
+
puts "[X] IAM access to Account Activity is setup -- Enabled"
|
288
|
+
end
|
289
|
+
if usage_report_search.first.attributes['checked'].nil?
|
290
|
+
# Not checked
|
291
|
+
puts "[ ] IAM access to Usage Reports not setup -- Disabled"
|
292
|
+
else
|
293
|
+
# Checked
|
294
|
+
puts "[X] IAM access to Usage Reports is setup -- Enabled"
|
295
|
+
end
|
296
|
+
elsif deactivate_hidden
|
297
|
+
#Deactivate button is hidden, Activate shown
|
298
|
+
if checkbox_account_activity_search.first.attributes['checked'].nil?
|
299
|
+
# Not checked
|
300
|
+
puts "[X] IAM access to Account Activity is setup -- Enabled"
|
301
|
+
else
|
302
|
+
# Checked
|
303
|
+
puts "[ ] IAM access to Account Activity not setup -- Disabled"
|
304
|
+
end
|
305
|
+
if usage_report_search.first.attributes['checked'].nil?
|
306
|
+
# Not checked
|
307
|
+
puts "[X] IAM access to Usage Reports is setup -- Enabled"
|
308
|
+
else
|
309
|
+
# Checked
|
310
|
+
puts "[ ] IAM access to Usage Reports not setup -- Disabled"
|
311
|
+
end
|
312
|
+
else
|
313
|
+
puts "[ ] Could not get status of IAM Usage report & Account activity checkboxes - Unknown"
|
314
|
+
end
|
315
|
+
rescue => e
|
316
|
+
warning(e)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def setup_checkboxes
|
321
|
+
checkbox_setup_error = " We were unable to enable account activity & usage reports.\n This setting can be enabled manually under the Manage Your Account from the AWS account page."
|
322
|
+
begin
|
323
|
+
puts "Enabling account activity & usage reports..."
|
324
|
+
page = @browser.get("https://portal.aws.amazon.com/gp/aws/manageYourAccount?action=updateIAMUserAccess&activateaa=1&activateur=1")
|
325
|
+
json = JSON.parse(page.body)
|
326
|
+
if json["error"] != "0"
|
327
|
+
puts checkbox_setup_error
|
328
|
+
else
|
329
|
+
puts " Activity & Usage reports enabled."
|
330
|
+
end
|
331
|
+
rescue => e
|
332
|
+
puts checkbox_setup_error
|
333
|
+
warning(e)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
data/lib/cht/output.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Setup
|
2
|
+
def self.write_csv(accounts, filename)
|
3
|
+
file_exists = File.exists?(filename)
|
4
|
+
mode = if file_exists
|
5
|
+
if @overwrite_file
|
6
|
+
puts "CSV Output file #{filename} exists! Overwriting the file per --overwrite."
|
7
|
+
"wb"
|
8
|
+
else
|
9
|
+
puts "CSV Output file #{filename} exists! Appending to existing file."
|
10
|
+
"ab"
|
11
|
+
end
|
12
|
+
else
|
13
|
+
"wb"
|
14
|
+
end
|
15
|
+
|
16
|
+
count = 0
|
17
|
+
CSV.open(filename, mode) do |csv|
|
18
|
+
unless mode == "ab"
|
19
|
+
csv << ["Account ID", "Console URL", "IAM Username", "IAM Password", "AWS Access Key", "AWS Access Secret", "S3 Bucket", "Is Consolidated?"]
|
20
|
+
end
|
21
|
+
accounts.each do |account|
|
22
|
+
count += 1
|
23
|
+
csv << [account[:account_id], account[:account_url], account[:user], account[:user_pass], account[:access_key], account[:secret_key], account[:s3_bucket], account[:consolidated]]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
puts "Finished setting up #{count} account(s). CSV File path: #{filename}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def response
|
30
|
+
@created_account
|
31
|
+
end
|
32
|
+
end
|
data/lib/cht/policy.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
class Setup
|
2
|
+
def bucket_has_policy
|
3
|
+
begin
|
4
|
+
@s3.get_bucket_policy(@setup_bucket)
|
5
|
+
true
|
6
|
+
rescue
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def user_has_policy
|
12
|
+
begin
|
13
|
+
@iam.get_user_policy("CHTRoPolicy", @aws_ro_name)
|
14
|
+
true
|
15
|
+
rescue
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def bucket_policy
|
21
|
+
{ "Version" => "2008-10-17",
|
22
|
+
"Id" => "Policy1335892530063",
|
23
|
+
"Statement" => [
|
24
|
+
{
|
25
|
+
"Sid" => "Stmt1335892150622",
|
26
|
+
"Effect" => "Allow",
|
27
|
+
"Principal" => {
|
28
|
+
"AWS" => "arn:aws:iam::386209384616:root"
|
29
|
+
},
|
30
|
+
"Action" => ["s3:GetBucketAcl", "s3:GetBucketPolicy"],
|
31
|
+
"Resource" => "arn:aws:s3:::#{@setup_bucket}"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"Sid" => "Stmt1335892526596",
|
35
|
+
"Effect" => "Allow",
|
36
|
+
"Principal" => {
|
37
|
+
"AWS" => "arn:aws:iam::386209384616:root"
|
38
|
+
},
|
39
|
+
"Action" => ["s3:PutObject"],
|
40
|
+
"Resource" => "arn:aws:s3:::#{@setup_bucket}/*"
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
def aws_ro_policy
|
46
|
+
{
|
47
|
+
"Statement" => [
|
48
|
+
{
|
49
|
+
"Effect" => "Allow",
|
50
|
+
"Action" => [
|
51
|
+
"aws-portal:ViewBilling",
|
52
|
+
"aws-portal:ViewUsage",
|
53
|
+
"autoscaling:Describe*",
|
54
|
+
"cloudformation:ListStacks",
|
55
|
+
"cloudformation:ListStackResources",
|
56
|
+
"cloudformation:DescribeStacks",
|
57
|
+
"cloudformation:DescribeStackEvents",
|
58
|
+
"cloudformation:DescribeStackResources",
|
59
|
+
"cloudformation:GetTemplate",
|
60
|
+
"cloudfront:Get*",
|
61
|
+
"cloudfront:List*",
|
62
|
+
"cloudwatch:Describe*",
|
63
|
+
"cloudwatch:Get*",
|
64
|
+
"cloudwatch:List*",
|
65
|
+
"dynamodb:DescribeTable",
|
66
|
+
"dynamodb:ListTables",
|
67
|
+
"ec2:Describe*",
|
68
|
+
"elasticache:Describe*",
|
69
|
+
"elasticbeanstalk:Check*",
|
70
|
+
"elasticbeanstalk:Describe*",
|
71
|
+
"elasticbeanstalk:List*",
|
72
|
+
"elasticbeanstalk:RequestEnvironmentInfo",
|
73
|
+
"elasticbeanstalk:RetrieveEnvironmentInfo",
|
74
|
+
"elasticloadbalancing:Describe*",
|
75
|
+
"iam:List*",
|
76
|
+
"iam:Get*",
|
77
|
+
"redshift:Describe*",
|
78
|
+
"route53:Get*",
|
79
|
+
"route53:List*",
|
80
|
+
"rds:Describe*",
|
81
|
+
"s3:List*",
|
82
|
+
"sdb:GetAttributes",
|
83
|
+
"sdb:List*",
|
84
|
+
"sdb:Select*",
|
85
|
+
"ses:Get*",
|
86
|
+
"ses:List*",
|
87
|
+
"sns:Get*",
|
88
|
+
"sns:List*",
|
89
|
+
"sqs:GetQueueAttributes",
|
90
|
+
"sqs:ListQueues",
|
91
|
+
"sqs:ReceiveMessage",
|
92
|
+
"storagegateway:List*",
|
93
|
+
"storagegateway:Describe*"
|
94
|
+
],
|
95
|
+
"Resource" => "*"
|
96
|
+
},
|
97
|
+
{
|
98
|
+
"Effect" => "Allow",
|
99
|
+
"Action" => [ "s3:Get*","s3:List*" ],
|
100
|
+
"Resource" => [
|
101
|
+
"arn:aws:s3:::#{@setup_bucket}",
|
102
|
+
"arn:aws:s3:::#{@setup_bucket}/*"
|
103
|
+
]
|
104
|
+
}
|
105
|
+
]
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
gem "fog", "1.12.1"
|
2
|
+
gem "json", "1.8.0"
|
3
|
+
gem "excon", "0.23.0"
|
4
|
+
gem "mixlib-cli", "1.3.0"
|
5
|
+
gem "mechanize", "2.5.1"
|
6
|
+
gem "highline", "1.6.19"
|
7
|
+
gem "nokogiri", "1.5.8"
|
8
|
+
|
9
|
+
require "fog"
|
10
|
+
require "mixlib/cli"
|
11
|
+
require "json"
|
12
|
+
require "mechanize"
|
13
|
+
require "excon"
|
14
|
+
require "highline/import"
|
15
|
+
require "securerandom"
|
16
|
+
require "csv"
|
17
|
+
|
18
|
+
# Ruby 1.8.7 hack for compatability
|
19
|
+
unless Kernel.respond_to?(:require_relative)
|
20
|
+
module Kernel
|
21
|
+
def require_relative(path)
|
22
|
+
require File.join(File.dirname(caller[0]), path.to_str)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require_relative "cht/aws"
|
28
|
+
require_relative "cht/mechanize"
|
29
|
+
require_relative "cht/policy"
|
30
|
+
require_relative "cht/error_handling"
|
31
|
+
require_relative "cht/output"
|
32
|
+
|
33
|
+
|
34
|
+
class MyCLI
|
35
|
+
include Mixlib::CLI
|
36
|
+
|
37
|
+
option :output_file,
|
38
|
+
:short => "-o OUTFILE",
|
39
|
+
:long => "--output-file OUTFILE",
|
40
|
+
:default => "./cloudhealth-accounts.csv",
|
41
|
+
:description => "Output CSV"
|
42
|
+
|
43
|
+
option :overwrite_file,
|
44
|
+
:long => "--overwrite",
|
45
|
+
:boolean => true,
|
46
|
+
:description => "Overwrite output file if it exists."
|
47
|
+
|
48
|
+
option :input_file,
|
49
|
+
:short => "-i INPUT",
|
50
|
+
:long => "--input-file INPUT",
|
51
|
+
:description => "INPUT CSV File"
|
52
|
+
|
53
|
+
option :aws_acct_alias,
|
54
|
+
:long => "--account-alias ALIAS",
|
55
|
+
:description => "Set an AWS Account alias"
|
56
|
+
|
57
|
+
option :aws_user,
|
58
|
+
:short => "-u USER",
|
59
|
+
:long => "--aws-user USER",
|
60
|
+
:description => "AWS Username"
|
61
|
+
|
62
|
+
option :aws_pass,
|
63
|
+
:short => "-p PASS",
|
64
|
+
:long => "--aws-pass PASS",
|
65
|
+
:description => "AWS Password"
|
66
|
+
|
67
|
+
option :aws_key,
|
68
|
+
:short => "-k KEY",
|
69
|
+
:long => "--aws-key KEY",
|
70
|
+
:description => "AWS Key"
|
71
|
+
|
72
|
+
option :aws_secret,
|
73
|
+
:short => "-s SECRET",
|
74
|
+
:long => "--aws-secret SECRET",
|
75
|
+
:description => "AWS Secret"
|
76
|
+
|
77
|
+
option :setup_bucket,
|
78
|
+
:short => "-b BUCKET",
|
79
|
+
:long => "--setup-billing-bucket BUCKET",
|
80
|
+
:description => "Name of billing bucket to create/use."
|
81
|
+
|
82
|
+
option :aws_ro_name,
|
83
|
+
:short => "-r READONLY",
|
84
|
+
:long => "--aws-ro-name READONLY",
|
85
|
+
:default => "cloudhealth",
|
86
|
+
:description => "Name of the Read only account to create in AWS"
|
87
|
+
|
88
|
+
option :ro_user_exists,
|
89
|
+
:long => "--user-exists",
|
90
|
+
:boolean => true,
|
91
|
+
:description => "AWS Read-only user exists, Perform other setup anyway (CSV will not be complete)"
|
92
|
+
|
93
|
+
option :verbose,
|
94
|
+
:short => "-v",
|
95
|
+
:long => "--verbose",
|
96
|
+
:description => "Enable verbose output / stack trace errors",
|
97
|
+
:boolean => true
|
98
|
+
|
99
|
+
option :help,
|
100
|
+
:short => "-h",
|
101
|
+
:long => "--help",
|
102
|
+
:description => "Help",
|
103
|
+
:on => :tail,
|
104
|
+
:boolean => true,
|
105
|
+
:show_options => true,
|
106
|
+
:exit => 0
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
class CsvImport
|
112
|
+
def initialize(filename)
|
113
|
+
@filename = filename
|
114
|
+
end
|
115
|
+
|
116
|
+
def import_file # Make me work!
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Setup
|
121
|
+
attr_accessor :aws_key, :aws_secret, :aws_user, :aws_pass, :output_file, :input_file, :aws_url, :setup_bucket, :aws_ro_name, :aws_acct_alias, :account_name, :aws_account_id
|
122
|
+
|
123
|
+
def initialize(options)
|
124
|
+
manual_input = ensure_options(options)
|
125
|
+
options.merge!(manual_input)
|
126
|
+
@aws_key = options[:aws_key]
|
127
|
+
@aws_secret = options[:aws_secret]
|
128
|
+
@output_file = options[:output_file]
|
129
|
+
@input_file = options[:input_file]
|
130
|
+
@aws_user = options[:aws_user]
|
131
|
+
@aws_pass = options[:aws_pass]
|
132
|
+
@setup_bucket = options[:setup_bucket]
|
133
|
+
@aws_ro_name = options[:aws_ro_name]
|
134
|
+
@aws_acct_alias = options[:aws_acct_alias]
|
135
|
+
@aws_account_id = nil
|
136
|
+
@verbose = options[:verbose]
|
137
|
+
@overwrite_file = options[:overwrite_file]
|
138
|
+
@ro_user_exists = options[:ro_user_exists]
|
139
|
+
@created_account = {}
|
140
|
+
@iam = iam
|
141
|
+
@s3 = s3
|
142
|
+
@browser = mech_browser
|
143
|
+
@mode = ARGV[0]
|
144
|
+
end
|
145
|
+
|
146
|
+
def ensure_options(input)
|
147
|
+
# If things dont exist in the options that are required, or required in a combination.
|
148
|
+
output_opts = {}
|
149
|
+
|
150
|
+
if input[:aws_key].nil?
|
151
|
+
output_opts[:aws_key] = ask("Input AWS Key: ") do |q|
|
152
|
+
q.responses[:not_valid] = "You must enter a 20 char AWS Access Key ID"
|
153
|
+
q.responses[:invalid_type] = "You must enter a 20 char AWS Access Key ID"
|
154
|
+
q.validate = lambda {|p| p.length == 20 }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
if input[:aws_secret].nil?
|
159
|
+
output_opts[:aws_secret] = ask("Input AWS Secret: ") do |q|
|
160
|
+
q.responses[:not_valid] = "You must enter a 40 char AWS Access Secret Key"
|
161
|
+
q.responses[:invalid_type] = "You must enter a 40 char AWS Access Secret Key"
|
162
|
+
q.validate = lambda {|p| p.length == 40 }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
if input[:aws_user].nil?
|
167
|
+
output_opts[:aws_user] = ask("Input AWS Email/Username: ") do |q|
|
168
|
+
q.responses[:not_valid] = "You must enter a valid Email/Username"
|
169
|
+
q.responses[:invalid_type] = "You must enter a valid Email/Username"
|
170
|
+
q.validate = lambda {|p| p.length > 5 }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
if input[:aws_pass].nil?
|
175
|
+
output_opts[:aws_pass] = ask("Input AWS Password: ") do |q|
|
176
|
+
q.responses[:not_valid] = "You must enter a valid password"
|
177
|
+
q.responses[:invalid_type] = "You must enter a valid password"
|
178
|
+
q.validate = lambda {|p| p.length > 4 }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
if input[:setup_bucket].nil?
|
183
|
+
output_opts[:setup_bucket] = ask("Input S3 Bucket name to setup for billing: ")
|
184
|
+
end
|
185
|
+
|
186
|
+
output_opts
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.run
|
191
|
+
cli = MyCLI.new
|
192
|
+
cli.banner = "Usage: cloudhealth-setup test|install|uninstall (options)"
|
193
|
+
cli.parse_options
|
194
|
+
accounts_to_setup = []
|
195
|
+
accounts_processed = []
|
196
|
+
if ARGV[0].nil?
|
197
|
+
puts cli.opt_parser
|
198
|
+
exit
|
199
|
+
end
|
200
|
+
|
201
|
+
if cli.config[:input_file]
|
202
|
+
puts "Starting CloudHealth setup in multi-account setup mode, using input file #{cli.config[:input_file]}"
|
203
|
+
accounts_to_setup << CsvImport.import_file(cli.config[:input_file])
|
204
|
+
else
|
205
|
+
puts "Starting CloudHealth setup in single account mode."
|
206
|
+
accounts_to_setup << cli.config
|
207
|
+
end
|
208
|
+
|
209
|
+
accounts_to_setup.each do |account_options|
|
210
|
+
new_account = Setup.new(account_options)
|
211
|
+
|
212
|
+
case ARGV[0]
|
213
|
+
when "install"
|
214
|
+
new_account.check_iam_credentials
|
215
|
+
new_account.check_web_credentials
|
216
|
+
new_account.setup_monthly_report
|
217
|
+
new_account.setup_s3_bucket
|
218
|
+
new_account.setup_prog_access
|
219
|
+
new_account.setup_detailed_billing
|
220
|
+
new_account.setup_cost_alloc
|
221
|
+
new_account.setup_checkboxes
|
222
|
+
new_account.setup_ro_user
|
223
|
+
new_account.setup_account_alias
|
224
|
+
new_account.account_consolidated
|
225
|
+
accounts_processed << new_account.response
|
226
|
+
when "test"
|
227
|
+
new_account.test_monthly_report
|
228
|
+
new_account.test_s3_bucket
|
229
|
+
new_account.test_prog_access
|
230
|
+
new_account.test_detailed_billing
|
231
|
+
new_account.test_cost_alloc
|
232
|
+
new_account.test_checkboxes
|
233
|
+
new_account.test_ro_user
|
234
|
+
new_account.test_account_alias
|
235
|
+
new_account.test_consolidated
|
236
|
+
when "uninstall"
|
237
|
+
new_account.uninstall_ro_user
|
238
|
+
else
|
239
|
+
puts cli.opt_parser
|
240
|
+
end
|
241
|
+
end
|
242
|
+
Setup.write_csv(accounts_processed, cli.config[:output_file]) if ARGV[0] == "install"
|
243
|
+
end
|
244
|
+
end
|
metadata
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudhealth-setup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.10
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- CloudHealth Technologies
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.5.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.5.8
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: fog
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.12.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.12.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.8.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.8.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: excon
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.23.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - '='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.23.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: mixlib-cli
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.3.0
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - '='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.3.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: mechanize
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.5.1
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.5.1
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: highline
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.6.19
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - '='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.6.19
|
126
|
+
description: Configures an Amazon AWS account for use with the CloudHealth service,
|
127
|
+
including creating a least privilege read only AWS user and enabling the retrieval
|
128
|
+
of cost and usage information.
|
129
|
+
email:
|
130
|
+
- support@cloudhealthtech.com
|
131
|
+
executables:
|
132
|
+
- cloudhealth-setup
|
133
|
+
- fog
|
134
|
+
- nokogiri
|
135
|
+
extensions: []
|
136
|
+
extra_rdoc_files: []
|
137
|
+
files:
|
138
|
+
- bin/cloudhealth-setup
|
139
|
+
- bin/fog
|
140
|
+
- bin/nokogiri
|
141
|
+
- lib/cht/aws.rb
|
142
|
+
- lib/cht/error_handling.rb
|
143
|
+
- lib/cht/mechanize.rb
|
144
|
+
- lib/cht/output.rb
|
145
|
+
- lib/cht/policy.rb
|
146
|
+
- lib/cloudhealth-setup.rb
|
147
|
+
- cloudhealth-setup.gemspec
|
148
|
+
homepage: http://www.cloudhealthtech.com
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ! '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
none: false
|
163
|
+
requirements:
|
164
|
+
- - ! '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 1.8.25
|
170
|
+
signing_key:
|
171
|
+
specification_version: 3
|
172
|
+
summary: Configures an Amazon AWS account for use with the CloudHealth service
|
173
|
+
test_files: []
|
174
|
+
has_rdoc: false
|