heroku-rails-saas 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.
- data/CHANGELOG +39 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +46 -0
- data/LICENSE +65 -0
- data/README.md +211 -0
- data/Rakefile +43 -0
- data/TODO +0 -0
- data/heroku-rails.gemspec +38 -0
- data/lib/generators/heroku/config_generator.rb +19 -0
- data/lib/generators/templates/heroku.rake +33 -0
- data/lib/generators/templates/heroku.yml +52 -0
- data/lib/heroku-rails.rb +4 -0
- data/lib/heroku-rails/config.rb +146 -0
- data/lib/heroku-rails/hash_recursive_merge.rb +11 -0
- data/lib/heroku-rails/railtie.rb +8 -0
- data/lib/heroku-rails/runner.rb +278 -0
- data/lib/heroku/rails/tasks.rb +246 -0
- data/spec/fixtures/awesomeapp.yml +32 -0
- data/spec/fixtures/heroku-config.yml +16 -0
- data/spec/fixtures/mediocreapp.yml +14 -0
- data/spec/heroku/rails/heroku_config_spec.rb +157 -0
- data/spec/heroku/rails/heroku_runner_spec.rb +15 -0
- data/spec/spec_helper.rb +17 -0
- metadata +109 -0
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'heroku-rails'
|
2
|
+
|
3
|
+
HEROKU_CONFIG_FILE = File.join(HerokuRails::Config.root, 'config', 'heroku.yml')
|
4
|
+
HEROKU_APP_SPECIFIC_CONFIG_FILES = Dir.glob("#{File.join(HerokuRails::Config.root, 'config', 'heroku')}/*.yml")
|
5
|
+
HEROKU_CONFIG = HerokuRails::Config.new({:default => HEROKU_CONFIG_FILE, :apps => HEROKU_APP_SPECIFIC_CONFIG_FILES})
|
6
|
+
HEROKU_RUNNER = HerokuRails::Runner.new(HEROKU_CONFIG)
|
7
|
+
|
8
|
+
# create all the environment specific tasks
|
9
|
+
(HEROKU_CONFIG.apps).each do |app, hsh|
|
10
|
+
hsh.each do |env, heroku_env|
|
11
|
+
app_name = HerokuRails::Config.app_name(app, env)
|
12
|
+
desc "Select #{app_name} Heroku app for later commands"
|
13
|
+
task app_name do
|
14
|
+
# callback switch_environment
|
15
|
+
@heroku_app = {:env => heroku_env, :app_name => app_name}
|
16
|
+
Rake::Task["heroku:switch_environment"].reenable
|
17
|
+
Rake::Task["heroku:switch_environment"].invoke
|
18
|
+
|
19
|
+
HEROKU_RUNNER.add_environment(app_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Select all Heroku apps for later command (production must be explicitly declared)'
|
25
|
+
task :all do
|
26
|
+
HEROKU_RUNNER.all_environments(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
(HEROKU_CONFIG.all_environments).each do |env|
|
30
|
+
desc "Select all Heroku apps in #{env} environment"
|
31
|
+
task "all:#{env}" do
|
32
|
+
HEROKU_RUNNER.environments(env)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
namespace :heroku do
|
37
|
+
def system_with_echo(*args)
|
38
|
+
HEROKU_RUNNER.system_with_echo(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Add git remotes for all apps in this project'
|
42
|
+
task :remotes do
|
43
|
+
HEROKU_RUNNER.all_environments
|
44
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
45
|
+
system_with_echo("git remote add #{app_name} #{repo}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc 'Lists configured apps'
|
50
|
+
task :apps do
|
51
|
+
HEROKU_RUNNER.all_environments
|
52
|
+
puts "\n"
|
53
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
54
|
+
puts "#{heroku_env} maps to the Heroku app #{app_name} located at:"
|
55
|
+
puts " #{repo}"
|
56
|
+
puts
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "Get remote server information on the heroku app"
|
61
|
+
task :info do
|
62
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
63
|
+
system_with_echo "heroku info --app #{app_name}"
|
64
|
+
puts "\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Deploys, migrates and restarts latest git tag"
|
69
|
+
task :deploy => "heroku:before_deploy" do |t, args|
|
70
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
71
|
+
puts "\n\nDeploying to #{app_name}..."
|
72
|
+
# set the current heroku_app so that callbacks can read the data
|
73
|
+
@heroku_app = {:env => heroku_env, :app_name => app_name, :repo => repo}
|
74
|
+
Rake::Task["heroku:before_each_deploy"].reenable
|
75
|
+
Rake::Task["heroku:before_each_deploy"].invoke(app_name)
|
76
|
+
|
77
|
+
cmd = HEROKU_CONFIG.cmd(heroku_env)
|
78
|
+
if heroku_env == "production"
|
79
|
+
branch = `git branch`.scan(/^\* (.*)\n/).flatten.first.to_s
|
80
|
+
all_tags = `git tag`
|
81
|
+
target_tag = all_tags[/.+\Z/] # Set lastest tag as default
|
82
|
+
|
83
|
+
begin
|
84
|
+
puts "\nGit tags:"
|
85
|
+
puts all_tags
|
86
|
+
print "\nPlease enter a tag to deploy (or hit Enter for \"#{target_tag}\"): "
|
87
|
+
input_tag = STDIN.gets.chomp
|
88
|
+
if input_tag.present?
|
89
|
+
if all_tags[/^#{input_tag}\n/].present?
|
90
|
+
target_tag = input_tag
|
91
|
+
invalid = false
|
92
|
+
else
|
93
|
+
puts "\n\nInvalid git tag!"
|
94
|
+
invalid = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end while invalid
|
98
|
+
else
|
99
|
+
branch = `git branch`.scan(/^\* (.*)\n/).flatten.first.to_s
|
100
|
+
if branch.present?
|
101
|
+
@git_push_arguments ||= []
|
102
|
+
@git_push_arguments << '--force'
|
103
|
+
to_deploy = "#{target_tag}^{}"
|
104
|
+
system_with_echo "git push #{repo} #{@git_push_arguments.join(' ')} #{branch}:master"
|
105
|
+
Rake::Task["heroku:setup:config"].invoke
|
106
|
+
system_with_echo "#{cmd} rake --app #{app_name} db:migrate && heroku restart --app #{app_name}"
|
107
|
+
else
|
108
|
+
puts "Unable to determine the current git branch, please checkout the branch you'd like to deploy."
|
109
|
+
|
110
|
+
exit(1)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
Rake::Task["heroku:after_each_deploy"].reenable
|
115
|
+
Rake::Task["heroku:after_each_deploy"].invoke(app_name)
|
116
|
+
puts "\n"
|
117
|
+
end
|
118
|
+
Rake::Task["heroku:after_deploy"].invoke
|
119
|
+
end
|
120
|
+
|
121
|
+
# Callback before all deploys
|
122
|
+
task :before_deploy do
|
123
|
+
end
|
124
|
+
|
125
|
+
# Callback after all deploys
|
126
|
+
task :after_deploy do
|
127
|
+
end
|
128
|
+
|
129
|
+
# Callback before each deploy
|
130
|
+
task :before_each_deploy, [:app_name] do |t,args|
|
131
|
+
end
|
132
|
+
|
133
|
+
# Callback after each deploy
|
134
|
+
task :after_each_deploy, [:app_name] do |t,args|
|
135
|
+
end
|
136
|
+
|
137
|
+
# Callback for when we switch environment
|
138
|
+
task :switch_environment do
|
139
|
+
end
|
140
|
+
|
141
|
+
desc "Force deploys, migrates and restarts latest code"
|
142
|
+
task :force_deploy do
|
143
|
+
@git_push_arguments ||= []
|
144
|
+
@git_push_arguments << '--force'
|
145
|
+
Rake::Task["heroku:deploy"].execute
|
146
|
+
end
|
147
|
+
|
148
|
+
desc "Captures a bundle on Heroku"
|
149
|
+
task :capture do
|
150
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
151
|
+
system_with_echo "heroku bundles:capture --app #{app_name}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
desc "Opens a remote console"
|
156
|
+
task :console do
|
157
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
158
|
+
cmd = HEROKU_CONFIG.cmd(heroku_env)
|
159
|
+
system_with_echo "#{cmd} console --app #{app_name}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
desc "Shows the Heroku logs"
|
164
|
+
task :logs do
|
165
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
166
|
+
system_with_echo "heroku logs --app #{app_name}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
desc "Restarts remote servers"
|
171
|
+
task :restart do
|
172
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
173
|
+
system_with_echo "heroku restart --app #{app_name}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
namespace :setup do
|
178
|
+
|
179
|
+
desc "Creates the apps on Heroku"
|
180
|
+
task :apps do
|
181
|
+
HEROKU_RUNNER.setup_apps
|
182
|
+
end
|
183
|
+
|
184
|
+
desc "Setup the Heroku stacks from heroku.yml config"
|
185
|
+
task :stacks do
|
186
|
+
HEROKU_RUNNER.setup_stacks
|
187
|
+
end
|
188
|
+
|
189
|
+
desc "Setup the Heroku collaborators from heroku.yml config"
|
190
|
+
task :collaborators do
|
191
|
+
HEROKU_RUNNER.setup_collaborators
|
192
|
+
end
|
193
|
+
|
194
|
+
desc "Setup the Heroku environment config variables from heroku.yml config"
|
195
|
+
task :config do
|
196
|
+
HEROKU_RUNNER.setup_config
|
197
|
+
end
|
198
|
+
|
199
|
+
desc "Setup the Heroku addons from heroku.yml config"
|
200
|
+
task :addons do
|
201
|
+
HEROKU_RUNNER.setup_addons
|
202
|
+
end
|
203
|
+
|
204
|
+
desc "Setup the Heroku domains from heroku.yml config"
|
205
|
+
task :domains do
|
206
|
+
HEROKU_RUNNER.setup_domains
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
desc "Setup Heroku deploy environment from heroku.yml config"
|
211
|
+
task :setup => [
|
212
|
+
"heroku:setup:apps",
|
213
|
+
"heroku:setup:stacks",
|
214
|
+
"heroku:setup:collaborators",
|
215
|
+
"heroku:setup:config",
|
216
|
+
"heroku:setup:addons",
|
217
|
+
"heroku:setup:domains",
|
218
|
+
]
|
219
|
+
|
220
|
+
namespace :db do
|
221
|
+
desc "Migrates and restarts remote servers"
|
222
|
+
task :migrate do
|
223
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
224
|
+
cmd = HEROKU_CONFIG.cmd(heroku_env)
|
225
|
+
system_with_echo "#{cmd} rake --app #{app_name} db:migrate && heroku restart --app #{app_name}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
desc "Pulls the database from heroku and stores it into db/dumps/"
|
230
|
+
task :pull do
|
231
|
+
HEROKU_RUNNER.each_heroku_app do |heroku_env, app_name, repo|
|
232
|
+
system_with_echo "heroku pgdumps:capture --app #{app_name}"
|
233
|
+
dump = `heroku pgdumps --app #{app_name}`.split("\n").last.split(" ").first
|
234
|
+
system_with_echo "mkdir -p #{HerokuRails::Config.root}/db/dumps"
|
235
|
+
file = "#{HerokuRails::Config.root}/db/dumps/#{dump}.sql.gz"
|
236
|
+
url = `heroku pgdumps:url --app #{app_name} #{dump}`.chomp
|
237
|
+
system_with_echo "wget", url, "-O", file
|
238
|
+
|
239
|
+
# TODO: these are a bit distructive...
|
240
|
+
# system_with_echo "rake db:drop db:create"
|
241
|
+
# system_with_echo "gunzip -c #{file} | #{HerokuRails::Config.root}/script/dbconsole"
|
242
|
+
# system_with_echo "rake jobs:clear"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
env:
|
2
|
+
production: awesomeapp
|
3
|
+
staging: awesomeapp-staging
|
4
|
+
|
5
|
+
stacks:
|
6
|
+
staging: bamboo-ree-1.8.7
|
7
|
+
|
8
|
+
config:
|
9
|
+
production:
|
10
|
+
CONFIG_VAR1: "config1-production"
|
11
|
+
staging:
|
12
|
+
CONFIG_VAR1: "config1-staging"
|
13
|
+
STAGING_CONFIG: "special-staging"
|
14
|
+
|
15
|
+
collaborators:
|
16
|
+
staging:
|
17
|
+
- "staging-user@somedomain.com"
|
18
|
+
production:
|
19
|
+
- "production-user@somedomain.com"
|
20
|
+
|
21
|
+
domains:
|
22
|
+
staging:
|
23
|
+
- "staging.awesomeapp.com"
|
24
|
+
production:
|
25
|
+
- "awesomeapp.com"
|
26
|
+
- "www.awesomeapp.com"
|
27
|
+
|
28
|
+
addons:
|
29
|
+
production:
|
30
|
+
# list production env specific addons here
|
31
|
+
- ssl:piggyback
|
32
|
+
- cron:daily
|
@@ -0,0 +1,16 @@
|
|
1
|
+
stacks: bamboo-mri-1.9.2
|
2
|
+
|
3
|
+
config:
|
4
|
+
BUNDLE_WITHOUT: "test:development"
|
5
|
+
CONFIG_VAR1: "config1"
|
6
|
+
CONFIG_VAR2: "config2"
|
7
|
+
|
8
|
+
collaborators:
|
9
|
+
- "all-user1@somedomain.com"
|
10
|
+
- "all-user2@somedomain.com"
|
11
|
+
- "all-user2@somedomain.com"
|
12
|
+
|
13
|
+
addons:
|
14
|
+
# add any other addons here
|
15
|
+
- scheduler:standard
|
16
|
+
- newrelic:bronze
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module HerokuRails
|
4
|
+
describe Config do
|
5
|
+
before(:each) do
|
6
|
+
config_files = {:default => config_path("heroku-config.yml"), :apps => [config_path("awesomeapp.yml"), config_path("mediocreapp.yml")]}
|
7
|
+
@config = Config.new(config_files)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should read the configuration file" do
|
11
|
+
@config.settings.should_not be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#apps" do
|
15
|
+
it "should return the list of apps defined" do
|
16
|
+
@config.apps.should have(2).apps
|
17
|
+
@config.apps.should include("awesomeapp")
|
18
|
+
@config.apps.should include("mediocreapp")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#app_names" do
|
23
|
+
it "should return the list of apps defined" do
|
24
|
+
@config.app_names.should have(2).names
|
25
|
+
@config.apps.should include("awesomeapp")
|
26
|
+
@config.apps.should include("mediocreapp")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#app_environments" do
|
31
|
+
it "should return a list of the environments defined" do
|
32
|
+
@config.app_environments.should have(3).environments
|
33
|
+
@config.app_environments.should include("awesomeapp:production")
|
34
|
+
@config.app_environments.should include("awesomeapp:staging")
|
35
|
+
@config.app_environments.should include("awesomeapp:staging")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#stack" do
|
40
|
+
it "should return the associated stack for awesomeapp:staging" do
|
41
|
+
@config.stack("awesomeapp:staging").should == "bamboo-ree-1.8.7"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return the default stack for awesomeapp:production" do
|
45
|
+
@config.stack("awesomeapp:production").should == "bamboo-mri-1.9.2"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should default to the all setting if not explicitly defined" do
|
49
|
+
@config.stack("mediocreapp").should == "bamboo-mri-1.9.2"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#config" do
|
54
|
+
context "staging environment" do
|
55
|
+
before(:each) do
|
56
|
+
@config = @config.config("awesomeapp:staging")
|
57
|
+
end
|
58
|
+
it "should include configs defined in 'staging'" do
|
59
|
+
@config["STAGING_CONFIG"].should == "special-staging"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should include configs defined in 'all'" do
|
63
|
+
@config["BUNDLE_WITHOUT"].should == "test:development"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should use configs defined in 'staging' ahead of configs defined in 'all'" do
|
67
|
+
@config["CONFIG_VAR1"].should == "config1-staging"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#collaborators" do
|
73
|
+
context "awesomeapp:staging" do
|
74
|
+
before(:each) do
|
75
|
+
@collaborators = @config.collaborators('awesomeapp:staging')
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should include the collaborators defined in 'all'" do
|
79
|
+
@collaborators.should include('all-user1@somedomain.com')
|
80
|
+
@collaborators.should include('all-user2@somedomain.com')
|
81
|
+
@collaborators.should have(3).collaborators
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should include collaborators defined in 'staging'" do
|
85
|
+
@collaborators.should include('staging-user@somedomain.com')
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should not include collaborators defined in 'production'" do
|
89
|
+
@collaborators.should_not include('production-user@somedomain.com')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "mediocreapp:development" do
|
94
|
+
before(:each) do
|
95
|
+
@collaborators = @config.collaborators('mediocreapp:development')
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should include the collaborators defined in 'all'" do
|
99
|
+
@collaborators.should include('all-user1@somedomain.com')
|
100
|
+
@collaborators.should include('all-user2@somedomain.com')
|
101
|
+
@collaborators.should have(3).collaborators
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should include collaborators defined in 'development'" do
|
105
|
+
@collaborators.should include('mediocre-user@example.com')
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should not include collaborators defined other apps" do
|
109
|
+
@collaborators.should_not include("staging-user@somedomain.com")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#domains" do
|
115
|
+
context "staging environment" do
|
116
|
+
before(:each) do
|
117
|
+
@domains = @config.domains('awesomeapp:staging')
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should include the domains defined in 'staging'" do
|
121
|
+
@domains.should include('staging.awesomeapp.com')
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should not include the domains defined in 'production'" do
|
125
|
+
@domains.should_not include('awesomeapp.com')
|
126
|
+
@domains.should_not include('www.awesomeapp.com')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "production environment" do
|
131
|
+
it "should include the domains defined in 'production'" do
|
132
|
+
@domains = @config.domains('awesomeapp:production')
|
133
|
+
@domains.should include('awesomeapp.com')
|
134
|
+
@domains.should include('www.awesomeapp.com')
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "#addons" do
|
140
|
+
context "staging environment" do
|
141
|
+
before(:each) do
|
142
|
+
@addons = @config.addons('awesomeapp:staging')
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should include addons defined in 'all'" do
|
146
|
+
@addons.should include('scheduler:standard')
|
147
|
+
@addons.should include('newrelic:bronze')
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should not include addons defined in 'production'" do
|
151
|
+
@addons.should_not include('ssl:piggyback')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|