-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnew
executable file
·311 lines (260 loc) · 20.4 KB
/
new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# Run with > ruby path/to/heroku-bootstrap/new
# This will ask you a lot of questions to get your app going!
require 'rubygems'
require 'simple_aws/s3'
require 'simple_aws/cloud_front'
DEFAULTS = {
:project_directory => '~/Projects',
:rvm => '1.9.3-head',
:enable_asset_pipeline => 'true',
:create_staging_heroku_app => 'true',
:newrelic_yml_address => 'https://raw.github.com/gist/2253296/newrelic.yml',
:exception_sender_address => '[email protected]',
:exception_recipient_address => '[email protected]'
}
inputs = {}
def blank?(str)
(str.nil? || str == '')
end
def true_string?(str)
str.downcase == 'true' || str.downcase == 't' || str.downcase == 'true' || str.downcase == 'y' || str.downcase == 'yes' || str == '1'
end
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
lower_case_and_underscored_word = lower_case_and_underscored_word.gsub('-', '_') # Take care of dashes and treat as underscore
if first_letter_in_uppercase
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
else
lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
end
end
def cloudfront_creation_xml(s3bucket)
"<DistributionConfig xmlns=\"http://cloudfront.amazonaws.com/doc/2012-05-05/\"><CallerReference>herokubootstrap-#{Time.now}</CallerReference><S3Origin><DNSName>#{s3bucket}.s3.amazonaws.com</DNSName></S3Origin><Aliases><Quantity>0</Quantity></Aliases><Comment/><Logging><Enabled>false</Enabled><Bucket>#{s3bucket}-logs-disabled.s3.amazonaws.com</Bucket><Prefix>#{s3bucket}.herokuapp.com</Prefix></Logging><TrustedSigners><Enabled>false</Enabled><Quantity>0</Quantity></TrustedSigners><Enabled>true</Enabled></DistributionConfig>"
end
def exec_with_status(cmd, inputs_hash)
puts "- `rvm use #{inputs_hash[:rvm]}; #{cmd}`"
`rvm use #{inputs_hash[:rvm]}; #{cmd}`
end
def requirement(prompt)
print "+ #{prompt} ***: "
input = STDIN.gets.strip
if blank?(input)
puts " --- ERROR value required"
return requirement(prompt)
else
return input
end
end
def optionally(symb, prompt)
print "+ #{prompt} (Hit `enter` for default `#{DEFAULTS[symb]}`): "
input = STDIN.gets.strip
return blank?(input) ? DEFAULTS[symb] : input
end
puts "Welcome to heroku bootstrap. We're going to ask you a few questions to make you a new rails application on heroku!"
# Production Environment Settings
inputs[:app_name] = requirement("What do you want your app to be named locally")
inputs[:project_directory] = optionally(:project_directory, "Where do we put the project on your computer")
inputs[:rvm] = optionally(:rvm, "Which version of rvm environment to use")
DEFAULTS[:heroku_app_name] = inputs[:app_name]
inputs[:heroku_app_name] = optionally(:heroku_app_name, "What do you want to name your HEROKU app")
inputs[:enable_asset_pipeline] = true_string? optionally(:enable_asset_pipeline, "Your app uses the asset pipeline with Amazon Cloudfront")
if inputs[:enable_asset_pipeline]
DEFAULTS[:s3_assets_bucket] = "#{inputs[:app_name]}-assets"
inputs[:s3_assets_bucket] = optionally(:s3_assets_bucket, "What is your PRODUCTION Assets S3 Bucket Name")
inputs[:aws_key] = requirement("What is your PRODUCTION AWS Public Key")
inputs[:aws_secret] = requirement("What is your PRODUCTION AWS Secret Key")
end
# Staging Environment Settings
inputs[:create_staging_heroku_app] = true_string? optionally(:create_staging_heroku_app, "Make a staging environment and heroku app as well?")
if inputs[:create_staging_heroku_app]
DEFAULTS[:heroku_staging_app_name] = "#{inputs[:heroku_app_name]}-staging"
inputs[:heroku_staging_app_name] = optionally(:heroku_staging_app_name, "What do you want to name your STAGING HEROKU app")
if inputs[:enable_asset_pipeline]
DEFAULTS[:s3_staging_assets_bucket] = "#{inputs[:heroku_app_name]}-staging-assets"
inputs[:s3_staging_assets_bucket] = optionally(:s3_staging_assets_bucket, "What is your STAGING Assets S3 Bucket Name")
DEFAULTS[:aws_key_staging] = inputs[:aws_key]
inputs[:aws_key_staging] = optionally(:aws_key_staging, "What is your STAGING AWS Public Key")
DEFAULTS[:aws_secret_staging] = inputs[:aws_secret]
inputs[:aws_secret_staging] = optionally(:aws_secret_staging, "What is your SECRET AWS Secret Key")
end
end
inputs[:collaborators] = optionally(:collaborators, "Collaborator email addresses space delimited").split(' ')
inputs[:exception_sender_address] = optionally(:exception_sender_address, "Send exception notifications from")
inputs[:exception_recipient_address] = optionally(:exception_recipient_address, "Send exception notifications to")
puts "Great! Creating an app with the following settings"
puts "----------------------------------------------------"
puts "#{inputs.inspect}"
puts "----------------------------------------------------"
# Create Rails App
exec_with_status("cd #{inputs[:project_directory]}; rails new #{inputs[:app_name]}", inputs)
project_path = "#{inputs[:project_directory]}/#{inputs[:app_name]}"
# Remove unecessary gem
exec_with_status("sed -i \"\" \"/^gem\\ \\'sqlite3\\'$/d\" #{project_path}/Gemfile", inputs)
# Install necessary gems
exec_with_status("echo \"\" >> #{project_path}/Gemfile", inputs) # newline
exec_with_status("echo \"gem 'delayed_job_active_record'\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"gem 'exception_notification'\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"gem 'sqlite3', :group => [:development, :test]\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"gem 'pg', :group => [:production, :staging]\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"gem 'default_value_for'\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"gem 'oink'\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"gem 'newrelic_rpm'\" >> #{project_path}/Gemfile", inputs)
exec_with_status("echo \"\" >> #{project_path}/Gemfile", inputs) # newline
if inputs[:enable_asset_pipeline]
exec_with_status("echo \"gem 'asset_sync'\" >> #{project_path}/Gemfile", inputs)
end
# Run bundle install to get 'em all
exec_with_status("cd #{project_path}; bundle install", inputs)
# Migrate for active record
exec_with_status("cd #{project_path}; rails generate delayed_job:active_record", inputs)
exec_with_status("cd #{project_path}; rake db:migrate", inputs)
# Get a cheap version of delayed worker running
exec_with_status("touch #{project_path}/lib/tasks/scheduler.rake", inputs)
exec_with_status("echo \"desc 'This task is called by the Heroku scheduler add-on and runs the background worker. Ok for low priority backgrounded tasks.'\" >> #{project_path}/lib/tasks/scheduler.rake", inputs)
exec_with_status("echo \"task :run_delayed_worker => :environment do\" >> #{project_path}/lib/tasks/scheduler.rake", inputs)
exec_with_status("echo \" puts 'Starting run_delayed_worker...'\" >> #{project_path}/lib/tasks/scheduler.rake", inputs)
exec_with_status("echo \" Delayed::Worker.new.work_off(20)\" >> #{project_path}/lib/tasks/scheduler.rake", inputs)
exec_with_status("echo \" puts 'done with run_delayed_worker.'\" >> #{project_path}/lib/tasks/scheduler.rake", inputs)
exec_with_status("echo \"end\" >> #{project_path}/lib/tasks/scheduler.rake", inputs)
# Get a free version of Newrelic enabled
exec_with_status("curl #{DEFAULTS[:newrelic_yml_address]} > #{project_path}/config/newrelic.yml", inputs)
# Set up us the bomb for sendgrid
exec_with_status("touch #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \"ActionMailer::Base.smtp_settings = {\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \" :address => 'smtp.sendgrid.net',\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \" :port => '587',\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \" :authentication => :plain,\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \" :user_name => ENV['SENDGRID_USERNAME'],\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \" :password => ENV['SENDGRID_PASSWORD'],\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \" :domain => 'heroku.com'\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \"}\" >> #{project_path}/config/initializers/mail.rb", inputs)
exec_with_status("echo \"ActionMailer::Base.delivery_method = :smtp\" >> #{project_path}/config/initializers/mail.rb", inputs)
# Set up us the bomb for exception_notifier
exec_with_status("touch #{project_path}/config/initializers/exception_notification.rb", inputs)
exec_with_status("echo \"#{camelize(inputs[:app_name])}::Application.config.middleware.use ExceptionNotifier, :email_prefix => '[#{inputs[:app_name]}] ', :sender_address => %{#{inputs[:exception_sender_address]}}, :exception_recipients => %w{#{inputs[:exception_recipient_address]}}\" >> #{project_path}/config/initializers/exception_notification.rb", inputs)
# Email out delayed_job errors
exec_with_status("touch #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"\# Optional but recommended for less future surprises.\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"\# Fail at startup if method does not exist instead of in background job \" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"[[ExceptionNotifier::Notifier, :background_exception_notification]].each do |object, method_name|\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" raise NoMethodError, \\\"undefined method `\#{method_name}' for \#{object.inspect}\\\" unless object.respond_to?(method_name, true)\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"end\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"\# Chain delayed job's handle_failed_job method to do exception notification\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"Delayed::Worker.class_eval do \" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" def handle_failed_job_with_notification(job, error)\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" handle_failed_job_without_notification(job, error)\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" # only actually send mail in production\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" if Rails.env.production?\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" \# rescue if ExceptionNotifier fails for some reason\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" begin\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" ExceptionNotifier::Notifier.background_exception_notification(error)\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" rescue Exception => e\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" Rails.logger.error \\\"ExceptionNotifier failed: \#{e.class.name}: \#{e.message}\\\"\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" e.backtrace.each do |f|\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" Rails.logger.error \\\" \#{f}\\\"\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" end\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" Rails.logger.flush\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" end\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" end\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" end \" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \" alias_method_chain :handle_failed_job, :notification \" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
exec_with_status("echo \"end\" >> #{project_path}/config/initializers/delayed_job_exception_notification.rb", inputs)
# Set up us the bomb for oink
exec_with_status("touch #{project_path}/config/initializers/oink.rb", inputs)
exec_with_status("echo \"#{camelize(inputs[:app_name])}::Application.middleware.use( Oink::Middleware, :logger => Rails.logger )\" >> #{project_path}/config/initializers/oink.rb", inputs)
# Set up staging environment rb
if inputs[:create_staging_heroku_app]
exec_with_status("cp #{project_path}/config/environments/production.rb #{project_path}/config/environments/staging.rb", inputs)
end
# Create buckets and cloudfronts
if inputs[:enable_asset_pipeline]
s3 = SimpleAWS::S3.new inputs[:aws_key], inputs[:aws_secret]
cloudfront = SimpleAWS::CloudFront.new inputs[:aws_key], inputs[:aws_secret]
puts "- Creating production s3 bucket `#{inputs[:s3_assets_bucket]}`"
s3.put "/", :bucket => inputs[:s3_assets_bucket], :headers => { "x-amz-acl" => "public-read" }
distribution = cloudfront.post "/distribution", :body => cloudfront_creation_xml(inputs[:s3_assets_bucket])
inputs[:cloudfront_domain] = distribution.domain_name
puts "- Cloudfront distribution created `#{inputs[:cloudfront_domain]}`"
if inputs[:create_staging_heroku_app]
s3 = SimpleAWS::S3.new inputs[:aws_key_staging], inputs[:aws_secret_staging]
cloudfront = SimpleAWS::CloudFront.new inputs[:aws_key_staging], inputs[:aws_secret_staging]
puts "- Creating staging s3 bucket `#{inputs[:s3_staging_assets_bucket]}`"
s3.put "/", :bucket => inputs[:s3_staging_assets_bucket], :headers => { "x-amz-acl" => "public-read" }
puts "- Creating staging cloudfront distribution for `#{inputs[:s3_staging_assets_bucket]}`"
distribution = cloudfront.post "/distribution", :body => cloudfront_creation_xml(inputs[:s3_staging_assets_bucket])
inputs[:cloudfront_domain_staging] = distribution.domain_name
puts "- Cloudfront distribution created `#{inputs[:cloudfront_domain_staging]}`"
end
end
# Asset hosting if requested
if inputs[:enable_asset_pipeline]
exec_with_status("echo \"\" >> #{project_path}/config/environments/production.rb", inputs) # newline
exec_with_status("echo \"config.action_controller.asset_host = Proc.new do |source, request = nil|\" >> #{project_path}/config/environments/production.rb", inputs)
exec_with_status("echo \" method = 'http'\" >> #{project_path}/config/environments/production.rb", inputs)
exec_with_status("echo \" unless request.nil?\" >> #{project_path}/config/environments/production.rb", inputs)
exec_with_status("echo \" method = request.ssl? ? 'https' : 'http'\" >> #{project_path}/config/environments/production.rb", inputs)
exec_with_status("echo \" end\" >> #{project_path}/config/environments/production.rb", inputs)
exec_with_status("echo \" \\\"\#{method}://#{inputs[:cloudfront_domain]}\\\"\" >> #{project_path}/config/environments/production.rb", inputs)
exec_with_status("echo \"end\" >> #{project_path}/config/environments/production.rb", inputs)
if inputs[:create_staging_heroku_app]
exec_with_status("echo \"\" >> #{project_path}/config/environments/staging.rb", inputs) # newline
exec_with_status("echo \"config.action_controller.asset_host = Proc.new do |source, request = nil|\" >> #{project_path}/config/environments/staging.rb", inputs)
exec_with_status("echo \" method = 'http'\" >> #{project_path}/config/environments/staging.rb", inputs)
exec_with_status("echo \" unless request.nil?\" >> #{project_path}/config/environments/staging.rb", inputs)
exec_with_status("echo \" method = request.ssl? ? 'https' : 'http'\" >> #{project_path}/config/environments/staging.rb", inputs)
exec_with_status("echo \" end\" >> #{project_path}/config/environments/staging.rb", inputs)
exec_with_status("echo \" \\\"\#{method}://#{inputs[:cloudfront_domain_staging]}\\\"\" >> #{project_path}/config/environments/staging.rb", inputs)
exec_with_status("echo \"end\" >> #{project_path}/config/environments/staging.rb", inputs)
end
end
# Do the git dance
exec_with_status("cd #{project_path}; git init", inputs)
exec_with_status("cd #{project_path}; git add .", inputs)
exec_with_status("cd #{project_path}; git commit -am 'initial commit'", inputs)
# Setup Heroku on production and optionally on staging
current_heroku_app = inputs[:heroku_app_name]
heroku_apps = [ inputs[:heroku_app_name] ]
if inputs[:create_staging_heroku_app]
heroku_apps << inputs[:heroku_staging_app_name]
end
heroku_apps.each do |current_heroku_app|
current_env = current_heroku_app == inputs[:heroku_staging_app_name] ? :staging : :production
exec_with_status("cd #{project_path}; heroku create #{current_heroku_app}", inputs)
# Change heroku git remote to env.
exec_with_status("sed -i \"\" \"s/^\\[remote\\ \\\"heroku\\\"\\]$/\\[remote\\ \\\"#{current_env}\\\"\]/g\" #{project_path}/.git/config", inputs)
exec_with_status("cd #{project_path}; git push #{current_env} master", inputs)
exec_with_status("cd #{project_path}; heroku run rake db:migrate -a #{current_heroku_app}", inputs)
# Setup asset hosting with heroku app
if inputs[:enable_asset_pipeline] && current_env == :staging
exec_with_status("cd #{project_path}; heroku config:add FOG_PROVIDER=AWS FOG_DIRECTORY=#{inputs[:s3_staging_assets_bucket]} AWS_ACCESS_KEY_ID=#{inputs[:aws_key_staging]} AWS_SECRET_ACCESS_KEY=#{inputs[:aws_secret_staging]} -a #{current_heroku_app}", inputs)
elsif inputs[:enable_asset_pipeline] # production
exec_with_status("cd #{project_path}; heroku config:add FOG_PROVIDER=AWS FOG_DIRECTORY=#{inputs[:s3_assets_bucket]} AWS_ACCESS_KEY_ID=#{inputs[:aws_key]} AWS_SECRET_ACCESS_KEY=#{inputs[:aws_secret]} -a #{current_heroku_app}", inputs)
end
# Set the correct RAILS_ENV and RACK_ENV for staging
if current_env == :staging
exec_with_status("cd #{project_path}; heroku config:add RACK_ENV=staging RAILS_ENV=staging -a #{current_heroku_app}", inputs)
end
# Now for some heroku setup for sick add-ons :)
exec_with_status("cd #{project_path}; heroku addons:add sendgrid:starter -a #{current_heroku_app}", inputs)
exec_with_status("cd #{project_path}; heroku addons:add scheduler:standard -a #{current_heroku_app}", inputs)
exec_with_status("cd #{project_path}; heroku addons:add deployhooks:campfire -a #{current_heroku_app}", inputs)
exec_with_status("cd #{project_path}; heroku addons:add papertrail:test -a #{current_heroku_app}", inputs)
exec_with_status("cd #{project_path}; heroku addons:add newrelic:standard -a #{current_heroku_app}", inputs)
# Setup pre-compilation in heroku labs, see https://devcenter.heroku.com/articles/labs-user-env-compile
if inputs[:enable_asset_pipeline]
exec_with_status("cd #{project_path}; heroku labs:enable user_env_compile -a #{current_heroku_app}", inputs)
end
# Add collaborators
inputs[:collaborators].each do |email|
exec_with_status("cd #{project_path}; heroku sharing:add #{email} -a #{current_heroku_app}", inputs)
end
puts "\n\nWe created heroku app `#{current_heroku_app}`.\n"
puts "You still need to add the job to the scheduler plugin. Run:"
puts "$ heroku addons:open scheduler -a #{current_heroku_app}"
puts " And input: `rake run_delayed_worker`, and set it to run every 10 minutes.\n"
puts "You also still need to set the credentials for the campfile plugin. Run:"
puts "$ heroku addons:open campfire -a #{current_heroku_app}\n\n"
end
puts "App creation completed - #{inputs[:app_name]}"
puts "Enjoy!\n"