jw-heroku-rails 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +38 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +43 -0
- data/README.md +196 -0
- data/Rakefile +43 -0
- data/TODO +5 -0
- data/heroku-rails.gemspec +37 -0
- data/lib/generators/heroku/config_generator.rb +19 -0
- data/lib/generators/templates/heroku.rake +33 -0
- data/lib/generators/templates/heroku.yml +53 -0
- data/lib/heroku-rails.rb +3 -0
- data/lib/heroku-rails/config.rb +86 -0
- data/lib/heroku-rails/railtie.rb +8 -0
- data/lib/heroku-rails/runner.rb +273 -0
- data/lib/heroku/rails/tasks.rb +232 -0
- data/spec/fixtures/heroku-config.yml +48 -0
- data/spec/heroku/rails/heroku_config_spec.rb +131 -0
- data/spec/heroku/rails/heroku_runner_spec.rb +1 -0
- data/spec/spec_helper.rb +18 -0
- metadata +131 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Generators
|
3
|
+
class ConfigGenerator < ::Rails::Generators::Base
|
4
|
+
desc "Generates a Heroku Config file at config/heroku.yml"
|
5
|
+
|
6
|
+
def self.source_root
|
7
|
+
@_heroku_gen_source_root ||= File.expand_path("../../templates", __FILE__)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_config_file
|
11
|
+
template 'heroku.yml', File.join('config', "heroku.yml")
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_rake_file
|
15
|
+
template 'heroku.rake', File.join('lib', 'tasks', "heroku.rake")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# ### Shortcuts: uncomment these for easier to type deployments
|
2
|
+
# ### e.g. rake deploy (instead of rake heroku:deploy)
|
3
|
+
# ###
|
4
|
+
# task :deploy => ["heroku:deploy"]
|
5
|
+
# task :console => ["heroku:console"]
|
6
|
+
# task :setup => ["heroku:setup"]
|
7
|
+
# task :logs => ["heroku:logs"]
|
8
|
+
# task :restart => ["heroku:restart"]
|
9
|
+
|
10
|
+
# Heroku Deploy Callbacks
|
11
|
+
namespace :heroku do
|
12
|
+
|
13
|
+
# runs before all the deploys complete
|
14
|
+
task :before_deploy do
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
# runs before each push to a particular heroku deploy environment
|
19
|
+
task :before_each_deploy, [:app_name] do |t,args|
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
# runs after each push to a particular heroku deploy environment
|
24
|
+
task :after_each_deploy, [:app_name] do |t,args|
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# runs after all the deploys complete
|
29
|
+
task :after_deploy do
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
apps:
|
2
|
+
# map the environments to your desired heroku app names
|
3
|
+
# for example,
|
4
|
+
# production: awesomeapp
|
5
|
+
# would create the RACK_ENV=production
|
6
|
+
# and deploy to http://awesomeapp.heroku.com
|
7
|
+
production: awesomeapp
|
8
|
+
staging: awesomeapp-staging
|
9
|
+
legacy: awesomeapp-legacy
|
10
|
+
|
11
|
+
stacks:
|
12
|
+
# choose the stacks you want to use for each app.
|
13
|
+
# the all: setting sets the default
|
14
|
+
all: bamboo-mri-1.9.2
|
15
|
+
|
16
|
+
# override on a per environment basis
|
17
|
+
legacy: bamboo-ree-1.8.7
|
18
|
+
|
19
|
+
config:
|
20
|
+
# choose the configuration settings for all environments
|
21
|
+
all:
|
22
|
+
BUNDLE_WITHOUT: "test:development"
|
23
|
+
CONFIG_VAR1: "config1"
|
24
|
+
CONFIG_VAR2: "config2"
|
25
|
+
|
26
|
+
# you can also override configuration settings for each environment
|
27
|
+
production:
|
28
|
+
CONFIG_VAR1: "config1-production"
|
29
|
+
staging:
|
30
|
+
CONFIG_VAR1: "config1-staging"
|
31
|
+
|
32
|
+
collaborators:
|
33
|
+
# Be sure to add yourself as a collaborator, otherwise your
|
34
|
+
# access to the app will be revoked.
|
35
|
+
all:
|
36
|
+
- "my-heroku-email@somedomain.com"
|
37
|
+
- "another-heroku-email@somedomain.com"
|
38
|
+
|
39
|
+
domains:
|
40
|
+
production:
|
41
|
+
- "awesomeapp.com"
|
42
|
+
- "www.awesomeapp.com"
|
43
|
+
|
44
|
+
addons:
|
45
|
+
all:
|
46
|
+
- custom_domains:basic
|
47
|
+
# add any other addons here
|
48
|
+
|
49
|
+
production:
|
50
|
+
- ssl:piggyback
|
51
|
+
- cron:daily
|
52
|
+
# - newrelic:bronze
|
53
|
+
# production env specific addons here
|
data/lib/heroku-rails.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module HerokuRails
|
4
|
+
class Config
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def root
|
8
|
+
@heroku_rails_root || ENV["RAILS_ROOT"] || "."
|
9
|
+
end
|
10
|
+
def root=(root)
|
11
|
+
@heroku_rails_root = root
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :settings
|
16
|
+
|
17
|
+
def initialize(config_filepath)
|
18
|
+
if File.exists?(config_filepath)
|
19
|
+
self.settings = YAML.load(ERB.new(File.read(config_filepath)).result) || {}
|
20
|
+
else
|
21
|
+
self.settings = {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def apps
|
26
|
+
self.settings['apps'] || {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def app_names
|
30
|
+
apps.values
|
31
|
+
end
|
32
|
+
|
33
|
+
def app_environments
|
34
|
+
apps.keys
|
35
|
+
end
|
36
|
+
|
37
|
+
# return the stack setting for a particular app environment
|
38
|
+
def stack(app_env)
|
39
|
+
stacks = self.settings['stacks'] || {}
|
40
|
+
stacks[app_env] || stacks['all']
|
41
|
+
end
|
42
|
+
|
43
|
+
def rake_cmd(app_env)
|
44
|
+
if self.stack(app_env) =~ /cedar/i
|
45
|
+
'heroku run rake'
|
46
|
+
else
|
47
|
+
'heroku rake'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# pull out the config setting hash for a particular app environment
|
52
|
+
def config(app_env)
|
53
|
+
config = self.settings['config'] || {}
|
54
|
+
all = config['all'] || {}
|
55
|
+
|
56
|
+
# overwrite all configs with the environment specific ones
|
57
|
+
all.merge(config[app_env] || {})
|
58
|
+
end
|
59
|
+
|
60
|
+
# return a list of domains for a particular app environment
|
61
|
+
def domains(app_env)
|
62
|
+
domains = self.settings['domains'] || {}
|
63
|
+
domains[app_env] || []
|
64
|
+
end
|
65
|
+
|
66
|
+
# return a list of collaborators for a particular app environment
|
67
|
+
def collaborators(app_env)
|
68
|
+
app_setting_list('collaborators', app_env)
|
69
|
+
end
|
70
|
+
|
71
|
+
# return a list of addons for a particular app environment
|
72
|
+
def addons(app_env)
|
73
|
+
app_setting_list('addons', app_env)
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def app_setting_list(setting_key, app)
|
79
|
+
setting = self.settings[setting_key] || {}
|
80
|
+
all = setting['all'] || []
|
81
|
+
|
82
|
+
# add in collaborators from app environment to the ones defined in all
|
83
|
+
(all + (setting[app] || [])).uniq
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'heroku-api'
|
2
|
+
|
3
|
+
module HerokuRails
|
4
|
+
class Runner
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
@environments = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def authorize
|
11
|
+
@heroku ||= Heroku::Auth.client
|
12
|
+
end
|
13
|
+
|
14
|
+
# add a specific environment to the run list
|
15
|
+
def add_environment(env)
|
16
|
+
@environments << env
|
17
|
+
end
|
18
|
+
|
19
|
+
# use all environments
|
20
|
+
def all_environments
|
21
|
+
@environments = @config.app_environments
|
22
|
+
end
|
23
|
+
|
24
|
+
# setup apps (create if necessary)
|
25
|
+
def setup_apps
|
26
|
+
authorize
|
27
|
+
|
28
|
+
# get a list of all my current apps on Heroku (so we don't create dupes)
|
29
|
+
@my_apps = @heroku.list.map{|a| a.first}
|
30
|
+
|
31
|
+
each_heroku_app do |heroku_env, app_name, repo|
|
32
|
+
next if @my_apps.include?(app_name)
|
33
|
+
|
34
|
+
stack = @config.stack(heroku_env)
|
35
|
+
stack_option = " --stack #{stack}" if stack.to_s.size > 0
|
36
|
+
creation_command "heroku create #{app_name}#{stack_option} --remote #{app_name}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# setup the stacks for each app (migrating if necessary)
|
41
|
+
def setup_stacks
|
42
|
+
authorize
|
43
|
+
each_heroku_app do |heroku_env, app_name, repo|
|
44
|
+
# get the intended stack setting
|
45
|
+
stack = @config.stack(heroku_env)
|
46
|
+
|
47
|
+
# get the remote info about the app from heroku
|
48
|
+
heroku_app_info = @heroku.info(app_name) || {}
|
49
|
+
|
50
|
+
# if the stacks don't match, then perform a migration
|
51
|
+
if stack != heroku_app_info[:stack]
|
52
|
+
puts "Migrating the app: #{app_name} to the stack: #{stack}"
|
53
|
+
creation_command "heroku stack:migrate #{stack} --app #{app_name}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# setup the list of collaborators
|
59
|
+
def setup_collaborators
|
60
|
+
authorize
|
61
|
+
each_heroku_app do |heroku_env, app_name, repo|
|
62
|
+
# get the remote info about the app from heroku
|
63
|
+
heroku_app_info = @heroku.info(app_name) || {}
|
64
|
+
|
65
|
+
# get the intended list of collaborators to add
|
66
|
+
collaborator_emails = @config.collaborators(heroku_env)
|
67
|
+
|
68
|
+
# add current user to collaborator list (always)
|
69
|
+
collaborator_emails << @heroku.user unless collaborator_emails.include?(@heroku.user)
|
70
|
+
collaborator_emails << heroku_app_info[:owner] unless collaborator_emails.include?(heroku_app_info[:owner])
|
71
|
+
|
72
|
+
# get existing collaborators
|
73
|
+
existing_emails = heroku_app_info[:collaborators].to_a.map{|c| c[:email]}
|
74
|
+
|
75
|
+
# get the list of collaborators to delete
|
76
|
+
existing_emails.each do |existing_email|
|
77
|
+
# check to see if we need to delete this person
|
78
|
+
unless collaborator_emails.include?(existing_email)
|
79
|
+
# delete that collaborator if they arent on the approved list
|
80
|
+
destroy_command "heroku sharing:remove #{existing_email} --app #{app_name}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# get the list of collaborators to add
|
85
|
+
collaborator_emails.each do |collaborator_email|
|
86
|
+
# check to see if we need to add this person
|
87
|
+
unless existing_emails.include?(collaborator_email)
|
88
|
+
# add the collaborator if they are not already on the server
|
89
|
+
creation_command "heroku sharing:add #{collaborator_email} --app #{app_name}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# display the destructive commands
|
94
|
+
output_destroy_commands(app_name)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# setup configuration
|
99
|
+
def setup_config
|
100
|
+
authorize
|
101
|
+
each_heroku_app do |heroku_env, app_name, repo|
|
102
|
+
# get the configuration that we are aiming towards
|
103
|
+
new_config = @config.config(heroku_env)
|
104
|
+
|
105
|
+
# default RACK_ENV to the heroku_env (unless its manually set to something else)
|
106
|
+
unless new_config["RACK_ENV"].to_s.length > 0
|
107
|
+
new_config["RACK_ENV"] = heroku_env
|
108
|
+
end
|
109
|
+
|
110
|
+
# get the existing config from heroku's servers
|
111
|
+
existing_config = @heroku.config_vars(app_name) || {}
|
112
|
+
|
113
|
+
# find the config variables to add
|
114
|
+
add_config = {}
|
115
|
+
new_config.each do |new_key, new_val|
|
116
|
+
add_config[new_key] = new_val unless existing_config[new_key] == new_val
|
117
|
+
end
|
118
|
+
|
119
|
+
# persist the changes onto heroku
|
120
|
+
unless add_config.empty?
|
121
|
+
# add the config
|
122
|
+
set_config = ""
|
123
|
+
add_config.each do |key, val|
|
124
|
+
set_config << "#{key}='#{val}' "
|
125
|
+
end
|
126
|
+
|
127
|
+
creation_command "heroku config:add #{set_config} --app #{app_name}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# setup the addons for heroku
|
133
|
+
def setup_addons
|
134
|
+
authorize
|
135
|
+
each_heroku_app do |heroku_env, app_name, repo|
|
136
|
+
# get the addons that we are aiming towards
|
137
|
+
addons = @config.addons(heroku_env)
|
138
|
+
|
139
|
+
# get the addons that are already on the servers
|
140
|
+
existing_addons = (@heroku.installed_addons(app_name) || []).map{|a| a["name"]}
|
141
|
+
|
142
|
+
# all apps need the shared database
|
143
|
+
addons << "shared-database:5mb" unless addons.index("shared-database:5mb") || addons.index("shared-database:20gb")
|
144
|
+
|
145
|
+
# add "custom_domains" if that addon doesnt already exist
|
146
|
+
# and we have domains configured for this app
|
147
|
+
addons << "custom_domains:basic" unless @config.domains(heroku_env).empty? or
|
148
|
+
addons.any?{|a| a =~ /custom_domains/} or
|
149
|
+
existing_addons.any?{|a| a =~ /custom_domains/}
|
150
|
+
|
151
|
+
# remove the addons that need to be removed
|
152
|
+
existing_addons.each do |existing_addon|
|
153
|
+
# check to see if we need to delete this addon
|
154
|
+
unless addons.include?(existing_addon)
|
155
|
+
# delete this addon if they arent on the approved list
|
156
|
+
destroy_command "heroku addons:remove #{existing_addon} --app #{app_name}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# add the addons that dont exist already
|
161
|
+
addons.each do |addon|
|
162
|
+
# check to see if we need to add this addon
|
163
|
+
unless existing_addons.include?(addon)
|
164
|
+
# add this addon if they are not already added
|
165
|
+
creation_command "heroku addons:add #{addon} --app #{app_name}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# display the destructive commands
|
170
|
+
output_destroy_commands(app_name)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# setup the domains for heroku
|
175
|
+
def setup_domains
|
176
|
+
authorize
|
177
|
+
each_heroku_app do |heroku_env, app_name, repo|
|
178
|
+
# get the domains that we are aiming towards
|
179
|
+
domains = @config.domains(heroku_env)
|
180
|
+
|
181
|
+
# get the domains that are already on the servers
|
182
|
+
existing_domains = (@heroku.list_domains(app_name) || []).map{|a| a[:domain]}
|
183
|
+
|
184
|
+
# remove the domains that need to be removed
|
185
|
+
existing_domains.each do |existing_domain|
|
186
|
+
# check to see if we need to delete this domain
|
187
|
+
unless domains.include?(existing_domain)
|
188
|
+
# delete this domain if they arent on the approved list
|
189
|
+
destroy_command "heroku domains:remove #{existing_domain} --app #{app_name}"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# add the domains that dont exist already
|
194
|
+
domains.each do |domain|
|
195
|
+
# check to see if we need to add this domain
|
196
|
+
unless existing_domains.include?(domain)
|
197
|
+
# add this domain if they are not already added
|
198
|
+
creation_command "heroku domains:add #{domain} --app #{app_name}"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# display the destructive commands
|
203
|
+
output_destroy_commands(app_name)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# cycles through each configured heroku app
|
208
|
+
# yields the environment name, the app name, and the repo url
|
209
|
+
def each_heroku_app
|
210
|
+
|
211
|
+
if @config.apps.size == 0
|
212
|
+
puts "\nNo heroku apps are configured. Run:
|
213
|
+
rails generate heroku:config\n\n"
|
214
|
+
puts "this will generate a default config/heroku.yml that you should edit"
|
215
|
+
puts "and then try running this command again"
|
216
|
+
|
217
|
+
exit(1)
|
218
|
+
end
|
219
|
+
|
220
|
+
if @environments.blank? && @config.apps.size == 1
|
221
|
+
@environments = [@config.app_environments.first]
|
222
|
+
end
|
223
|
+
|
224
|
+
if @environments.present?
|
225
|
+
@environments.each do |heroku_env|
|
226
|
+
app_name = @config.apps[heroku_env]
|
227
|
+
yield(heroku_env, app_name, "git@heroku.com:#{app_name}.git")
|
228
|
+
end
|
229
|
+
else
|
230
|
+
puts "\nYou must first specify at least one Heroku app:
|
231
|
+
rake <app> [<app>] <command>
|
232
|
+
rake production restart
|
233
|
+
rake demo staging deploy"
|
234
|
+
|
235
|
+
puts "\n\nYou can use also command all Heroku apps for this project:
|
236
|
+
rake all heroku:setup\n"
|
237
|
+
|
238
|
+
exit(1)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def system_with_echo(*args)
|
243
|
+
puts args.join(' ')
|
244
|
+
command(*args)
|
245
|
+
end
|
246
|
+
|
247
|
+
def creation_command(*args)
|
248
|
+
system_with_echo(*args)
|
249
|
+
end
|
250
|
+
|
251
|
+
def destroy_command(*args)
|
252
|
+
# puts args.join(' ')
|
253
|
+
@destroy_commands ||= []
|
254
|
+
@destroy_commands << args.join(' ')
|
255
|
+
end
|
256
|
+
|
257
|
+
def output_destroy_commands(app)
|
258
|
+
puts "The #{app} had a few things removed from the heroku.yml."
|
259
|
+
puts "If they are no longer neccessary, then run the following commands:\n\n"
|
260
|
+
(@destroy_commands || []).each do |destroy_command|
|
261
|
+
puts destroy_command
|
262
|
+
end
|
263
|
+
puts "\n\nthese commands may cause data loss so make sure you know that these are necessary"
|
264
|
+
# clear destroy commands
|
265
|
+
@destroy_commands = []
|
266
|
+
end
|
267
|
+
|
268
|
+
def command(*args)
|
269
|
+
system(*args)
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
end
|