heroku-rails-saas 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,14 @@
1
+ env:
2
+ development: mediocreapp-dev1
3
+
4
+ config:
5
+ development:
6
+ CONFIG_VAR1: "config-for-mediocre"
7
+
8
+ collaborators:
9
+ development:
10
+ - "mediocre-user@example.com"
11
+
12
+ domains:
13
+ development:
14
+ - "mediocreapp.com"
@@ -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