heroku_san 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Change log (curated)
2
+
3
+ ## v2.1.0
4
+
5
+ * Documentation update
6
+ * Push `REVISION` to Heroku example
7
+ * Bug fixes
8
+
9
+ ### New tasks
10
+
11
+ * rake logs:tail
12
+ * rake shell
13
+ * All HerokuSan tasks inside heroku: namespace, with aliases in the global namespace
14
+
15
+ ### New methods
16
+
17
+ * `Stage#deploy`
18
+ * `Stage#maintenance` can now take a block, and ensures that maintenance mode is off afterwards.
19
+ * `Stage#push_config`
20
+
21
+ ## v2.0.0
22
+
23
+ * Major rewrite into classes `Project` & `Stage`, with helper `Git` module
24
+ * Tests for _everything_
25
+ * Examples directory (e.g. `auto-tagger`)
26
+ * Removed dependencies on Rails
27
+ * `tasks.rb` is greatly simplified, mostly API calls into the `Stage` class
28
+ * Support for tagging releases and deploying apps using a tag glob
29
+ * Support for Heroku stacks (aspen, bamboo & cedar)
30
+
31
+ ## v1.3.0
32
+
33
+ N/A
data/README.rdoc CHANGED
@@ -68,37 +68,47 @@ Need to add remotes for each app?
68
68
 
69
69
  A full list of tasks provided:
70
70
 
71
- rake after_deploy # Callback after deploys
72
- rake all # Select all Heroku apps for later command
73
- rake before_deploy # Callback before deploys
74
- rake capture # Captures a bundle on Heroku
75
- rake console # Opens a remote console
76
- rake db:pull # Pull the Heroku database
77
- rake db:push # Push local database to Heroku database
78
- rake deploy[commit] # Pushes the given commit, migrates and restarts (default: HEAD)
79
- rake deploy:force[commit] # Force-pushes the given commit, migrates and restarts (default: HEAD)
80
- rake heroku:apps # Lists configured apps
81
- rake heroku:apps:local # Lists configured apps without hitting Heroku
82
- rake heroku:config # Add config:vars to each application
83
- rake heroku:config:list # Lists config variables as set on Heroku
84
- rake heroku:config:list:local # Lists local config variables without setting them
85
- rake heroku:create # Creates the Heroku app
86
- rake heroku:create_config # Creates an example configuration file
87
- rake heroku:gems # Generate the Heroku gems manifest from gem dependencies
88
- rake heroku:maintenance # Enable maintenance mode
89
- rake heroku:maintenance_off # Disable maintenance mode
90
- rake heroku:push[commit] # Pushes the given commit (default: HEAD)
91
- rake heroku:push:force[commit] # Force-pushes the given commit (default: HEAD)
92
- rake heroku:rack_env # Add proper RACK_ENV to each application
93
- rake heroku:rake[task] # Runs a rake task remotely
94
- rake heroku:remotes # Add git remotes for all apps in this project
95
- rake heroku:share # Adds a collaborator
96
- rake heroku:unshare # Removes a collaborator
97
- rake logs # Shows the Heroku logs
98
- rake migrate # Migrates and restarts remote servers
99
- rake restart # Restarts remote servers
100
-
101
- Frequently used tasks are not namespaced, everything else lives under heroku.
71
+ rake heroku:apps # Lists configured apps
72
+ rake heroku:apps:local # Lists configured apps without hitting heroku
73
+ rake heroku:config # Add config:vars to each application.
74
+ rake heroku:config:list # Lists config variables as set on Heroku
75
+ rake heroku:config:list:local # Lists local config variables without setting them
76
+ rake heroku:config:rack_env # Add proper RACK_ENV to each application
77
+ rake heroku:console # Opens a remote console
78
+ rake heroku:create # Creates the Heroku app
79
+ rake heroku:create_config # Creates an example configuration file
80
+ rake heroku:db:migrate # Migrates and restarts remote servers
81
+ rake heroku:db:pull # Pull database from stage to local dev database
82
+ rake heroku:deploy[commit] # Pushes the given commit, migrates and restarts (default: HEAD)
83
+ rake heroku:deploy:after # Callback after deploys
84
+ rake heroku:deploy:before # Callback before deploys
85
+ rake heroku:deploy:force[commit] # Force-pushes the given commit, migrates and restarts (default: HEAD)
86
+ rake heroku:logs # Shows the Heroku logs
87
+ rake heroku:logs:tail # Tail the Heroku logs (requires logging:expanded)
88
+ rake heroku:maintenance # Enable maintenance mode
89
+ rake heroku:maintenance_off # Disable maintenance mode
90
+ rake heroku:maintenance_on # Enable maintenance mode
91
+ rake heroku:push[commit] # Pushes the given commit (default: HEAD)
92
+ rake heroku:push:force[commit] # Force-pushes the given commit (default: HEAD)
93
+ rake heroku:rake[task] # Runs a rake task remotely
94
+ rake heroku:remotes # Add git remotes for all apps in this project
95
+ rake heroku:restart # Restarts remote servers
96
+ rake heroku:share # Adds a collaborator (asks for email)
97
+ rake heroku:unshare # Removes a collaborator (asks for email)
98
+ rake heroku:stage:all # Select all Heroku apps for later command
99
+
100
+ Frequently used tasks are aliased into the global namespace:
101
+
102
+ task :all => 'heroku:stage:all'
103
+ task :deploy => 'heroku:deploy'
104
+ task 'deploy:force' => 'heroku:deploy:force'
105
+ task :before_deploy => 'heroku:deploy:before'
106
+ task :after_deploy => 'heroku:deploy:after'
107
+ task :console => 'heroku:console'
108
+ task :restart => 'heroku:restart'
109
+ task :migrate => 'heroku:db:migrate'
110
+ task :logs => 'heroku:logs:default'
111
+ task 'logs:tail' => 'heroku:logs:tail'
102
112
 
103
113
  == Links
104
114
 
@@ -0,0 +1,8 @@
1
+ # Adding this to your :after_deploy task this will add an environment variable,
2
+ # in this case, "REVISION", to your Heroku environment with the current revision.
3
+ task :after_deploy do
4
+ each_heroku_app do |stage|
5
+ revision = stage.revision.split.first
6
+ stage.push_config('REVISION' => revision) if revision
7
+ end
8
+ end
@@ -18,6 +18,8 @@ Feature: Command Line
18
18
  app: awesomeapp-staging
19
19
  demo:
20
20
  app: awesomeapp-demo
21
+ development:
22
+ app:
21
23
  """
22
24
 
23
25
  When I run `rake --trace heroku:apps:local`
data/lib/git.rb CHANGED
@@ -23,17 +23,25 @@ module Git
23
23
  end
24
24
  end
25
25
 
26
- def git_tag(glob)
27
- return nil if glob.nil?
28
- %x{git tag -l '#{glob}'}.split("\n").last
26
+ def git_parsed_tag(tag)
27
+ git_rev_parse(git_tag(tag))
29
28
  end
30
29
 
31
30
  def git_rev_parse(ref)
32
31
  return nil if ref.nil?
33
32
  %x{git rev-parse #{ref}}.split("\n").first
33
+ end
34
+
35
+ def git_tag(glob)
36
+ return nil if glob.nil?
37
+ %x{git tag -l '#{glob}'}.split("\n").last
34
38
  end
35
39
 
36
- def git_parsed_tag(tag)
37
- git_rev_parse(git_tag(tag))
40
+ def git_revision(repo)
41
+ %x{git ls-remote --heads #{repo} master}.split.first
42
+ end
43
+
44
+ def git_named_rev(ref)
45
+ %x{git name-rev #{ref}}.chomp
38
46
  end
39
47
  end
data/lib/heroku_san.rb CHANGED
@@ -5,5 +5,6 @@ require 'heroku_san/project'
5
5
 
6
6
  module HerokuSan
7
7
  class NoApps < StandardError; end
8
+ class MissingApp < StandardError; end
8
9
  class Deprecated < StandardError; end
9
10
  end
@@ -1,6 +1,7 @@
1
1
  module HerokuSan
2
2
  class Stage
3
3
  attr_reader :name
4
+ include Git
4
5
 
5
6
  def initialize(stage, options = {})
6
7
  @name = stage
@@ -8,7 +9,7 @@ module HerokuSan
8
9
  end
9
10
 
10
11
  def app
11
- @options['app']
12
+ @options['app'] or raise MissingApp, "#{name}: is missing the app: configuration value. I don't know what to access on Heroku."
12
13
  end
13
14
 
14
15
  def repo
@@ -35,14 +36,28 @@ module HerokuSan
35
36
  end
36
37
  end
37
38
 
39
+ def deploy(sha = nil, force = false)
40
+ sha ||= git_parsed_tag(tag)
41
+ git_push(sha, repo, force ? %w[--force] : [])
42
+ end
43
+
38
44
  def migrate
39
45
  run 'rake', 'db:migrate'
40
46
  sh_heroku "restart"
41
47
  end
42
48
 
43
- def maintenance(action)
44
- raise ArgumentError, "Action #{action.inspect} must be one of (:on, :off)", caller if ![:on, :off].include?(action)
45
- sh_heroku "maintenance:#{action}"
49
+ def maintenance(action = nil)
50
+ if block_given?
51
+ sh_heroku "maintenance:on"
52
+ begin
53
+ yield
54
+ ensure
55
+ sh_heroku "maintenance:off"
56
+ end
57
+ else
58
+ raise ArgumentError, "Action #{action.inspect} must be one of (:on, :off)", caller if ![:on, :off].include?(action)
59
+ sh_heroku "maintenance:#{action}"
60
+ end
46
61
  end
47
62
 
48
63
  def create
@@ -60,18 +75,27 @@ module HerokuSan
60
75
  def long_config
61
76
  sh_heroku 'config --long'
62
77
  end
78
+
79
+ def push_config(options = {})
80
+ vars = (options == {} ? config : options).map {|var,value| "#{var}=#{Shellwords.escape(value)}"}.join(' ')
81
+ sh_heroku "config:add #{vars}"
82
+ end
63
83
 
64
84
  def restart
65
85
  sh_heroku 'restart'
66
86
  end
67
87
 
68
- def logs
69
- sh_heroku 'logs'
88
+ def logs(tail = false)
89
+ sh_heroku 'logs' + (tail ? ' --tail' : '')
90
+ end
91
+
92
+ def revision
93
+ git_named_rev(git_revision(repo))
70
94
  end
71
95
 
72
96
  private
73
97
 
74
- def sh_heroku command
98
+ def sh_heroku(command)
75
99
  sh "heroku #{command} --app #{app}"
76
100
  end
77
101
  end
@@ -1,3 +1,3 @@
1
1
  module HerokuSan
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
data/lib/tasks.rb CHANGED
@@ -5,17 +5,18 @@ include Git
5
5
 
6
6
  @heroku_san.all.each do |stage|
7
7
  desc "Select #{stage} Heroku app for later commands"
8
- task stage do
8
+ task "heroku:stage:#{stage}" do
9
9
  @heroku_san << stage
10
10
  end
11
- end
12
-
13
- desc 'Select all Heroku apps for later command'
14
- task :all do
15
- @heroku_san << @heroku_san.all
11
+ task stage => "heroku:stage:#{stage}"
16
12
  end
17
13
 
18
14
  namespace :heroku do
15
+ desc 'Select all Heroku apps for later command'
16
+ task 'stage:all' do
17
+ @heroku_san << @heroku_san.all
18
+ end
19
+
19
20
  desc "Creates the Heroku app"
20
21
  task :create do
21
22
  each_heroku_app do |stage|
@@ -23,7 +24,7 @@ namespace :heroku do
23
24
  end
24
25
  end
25
26
 
26
- desc "Generate the Heroku gems manifest from gem dependencies"
27
+ #desc "Generate the Heroku gems manifest from gem dependencies"
27
28
  task :gems => 'gems:base' do
28
29
  raise HerokuSan::Deprecated
29
30
  end
@@ -35,7 +36,7 @@ namespace :heroku do
35
36
  end
36
37
  end
37
38
 
38
- desc 'Adds a collaborator'
39
+ desc 'Adds a collaborator (asks for email)'
39
40
  task :share do
40
41
  print "Email address of collaborator to add: "
41
42
  $stdout.flush
@@ -45,7 +46,7 @@ namespace :heroku do
45
46
  end
46
47
  end
47
48
 
48
- desc 'Removes a collaborator'
49
+ desc 'Removes a collaborator (asks for email)'
49
50
  task :unshare do
50
51
  print "Email address of collaborator to remove: "
51
52
  $stdout.flush
@@ -58,15 +59,10 @@ namespace :heroku do
58
59
  desc 'Lists configured apps'
59
60
  task :apps => :all do
60
61
  each_heroku_app do |stage|
62
+ rev = stage.revision
61
63
  puts "#{stage.name} is shorthand for the Heroku app #{stage.app} located at:"
62
64
  puts " #{stage.repo}"
63
- print " @ "
64
- rev = `git ls-remote -h #{stage.repo}`.split(' ').first
65
- if rev.blank?
66
- puts 'not deployed'
67
- else
68
- puts `git name-rev #{rev}`
69
- end
65
+ puts " @ #{rev.blank? ? 'not deployed' : rev}"
70
66
  puts
71
67
  end
72
68
  end
@@ -83,26 +79,10 @@ namespace :heroku do
83
79
  end
84
80
  end
85
81
 
86
- desc 'Add proper RACK_ENV to each application'
87
- task :rack_env => :all do
88
- each_heroku_app do |stage|
89
- command = "heroku config --app #{stage.app}"
90
- puts command
91
- config = Hash[`#{command}`.scan(/^(.+?)\s*=>\s*(.+)$/)]
92
- if config['RACK_ENV'] != stage.name
93
- sh "heroku config:add --app #{stage.app} RACK_ENV=#{stage.name}"
94
- end
95
- end
96
- end
97
-
98
82
  desc 'Add config:vars to each application.'
99
83
  task :config do
100
84
  each_heroku_app do |stage|
101
- command = "heroku config:add --app #{stage.app}"
102
- stage.config.each do |var, value|
103
- command += " #{var}=#{value}"
104
- end
105
- sh(command)
85
+ stage.push_config
106
86
  end
107
87
  end
108
88
 
@@ -122,6 +102,18 @@ namespace :heroku do
122
102
  end
123
103
 
124
104
  namespace :config do
105
+ desc 'Add proper RACK_ENV to each application'
106
+ task :rack_env => :all do
107
+ each_heroku_app do |stage|
108
+ command = "heroku config --app #{stage.app}"
109
+ puts command
110
+ config = Hash[`#{command}`.scan(/^(.+?)\s*=>\s*(.+)$/)]
111
+ if config['RACK_ENV'] != stage.name
112
+ stage.push_config RACK_ENV: stage.name
113
+ end
114
+ end
115
+ end
116
+
125
117
  desc "Lists config variables as set on Heroku"
126
118
  task :list do
127
119
  each_heroku_app do |stage|
@@ -152,7 +144,7 @@ namespace :heroku do
152
144
  desc "Pushes the given commit (default: HEAD)"
153
145
  task :push, :commit do |t, args|
154
146
  each_heroku_app do |stage|
155
- git_push(args[:commit] || git_parsed_tag(stage.tag), stage.repo)
147
+ stage.deploy(args[:commit])
156
148
  end
157
149
  end
158
150
 
@@ -160,7 +152,7 @@ namespace :heroku do
160
152
  desc "Force-pushes the given commit (default: HEAD)"
161
153
  task :force, :commit do |t, args|
162
154
  each_heroku_app do |stage|
163
- git_push(args[:commit] || git_parsed_tag(stage.tag), stage.repo, %w[--force])
155
+ stage.deploy(args[:commit], :force)
164
156
  end
165
157
  end
166
158
  end
@@ -185,89 +177,122 @@ namespace :heroku do
185
177
  stage.maintenance :off
186
178
  end
187
179
  end
188
- end
189
-
190
- desc "Pushes the given commit, migrates and restarts (default: HEAD)"
191
- task :deploy, [:commit] => [:before_deploy] do |t, args|
192
- each_heroku_app do |stage|
193
- git_push(args[:commit] || git_parsed_tag(stage.tag), stage.repo)
194
- stage.migrate
195
- end
196
- Rake::Task[:after_deploy].execute
197
- end
198
180
 
199
- namespace :deploy do
200
- desc "Force-pushes the given commit, migrates and restarts (default: HEAD)"
201
- task :force, [:commit] => [:before_deploy] do |t, args|
181
+ desc "Pushes the given commit, migrates and restarts (default: HEAD)"
182
+ task :deploy, [:commit] => [:before_deploy] do |t, args|
202
183
  each_heroku_app do |stage|
203
- git_push(args[:commit] || git_parsed_tag(stage.tag), stage.repo, %w[--force])
184
+ stage.deploy(args[:commit])
204
185
  stage.migrate
205
186
  end
206
187
  Rake::Task[:after_deploy].execute
207
188
  end
208
- end
209
189
 
210
- task :force_deploy do
211
- raise Deprecated
212
- end
190
+ namespace :deploy do
191
+ desc "Force-pushes the given commit, migrates and restarts (default: HEAD)"
192
+ task :force, [:commit] => [:before_deploy] do |t, args|
193
+ each_heroku_app do |stage|
194
+ stage.deploy(args[:commit], :force)
195
+ stage.migrate
196
+ end
197
+ Rake::Task[:after_deploy].execute
198
+ end
213
199
 
214
- desc "Callback before deploys"
215
- task :before_deploy do
216
- end
200
+ desc "Callback before deploys"
201
+ task :before do
202
+ end
217
203
 
218
- desc "Callback after deploys"
219
- task :after_deploy do
220
- end
204
+ desc "Callback after deploys"
205
+ task :after do
206
+ end
221
207
 
222
- desc "Captures a bundle on Heroku"
223
- task :capture do
224
- raise Deprecated
225
- end
208
+ end
226
209
 
227
- desc "Opens a remote console"
228
- task :console do
229
- each_heroku_app do |stage|
230
- stage.run 'console'
210
+ task :force_deploy do
211
+ raise HerokuSan::Deprecated
231
212
  end
232
- end
233
213
 
234
- desc "Restarts remote servers"
235
- task :restart do
236
- each_heroku_app do |stage|
237
- stage.restart
214
+ #desc "Captures a bundle on Heroku"
215
+ task :capture do
216
+ raise HerokuSan::Deprecated
238
217
  end
239
- end
240
218
 
241
- desc "Migrates and restarts remote servers"
242
- task :migrate do
243
- each_heroku_app do |stage|
244
- stage.migrate
219
+ desc "Opens a remote console"
220
+ task :console do
221
+ each_heroku_app do |stage|
222
+ stage.run 'console'
223
+ end
245
224
  end
246
- end
247
225
 
248
- desc "Shows the Heroku logs"
249
- task :logs do
250
- each_heroku_app do |stage|
251
- stage.logs
226
+ desc "Restarts remote servers"
227
+ task :restart do
228
+ each_heroku_app do |stage|
229
+ stage.restart
230
+ end
252
231
  end
253
- end
254
232
 
255
- namespace :db do
256
- task :pull do
233
+ namespace :logs do
234
+ task :default do
235
+ each_heroku_app do |stage|
236
+ stage.logs
237
+ end
238
+ end
239
+
240
+ desc "Tail the Heroku logs (requires logging:expanded)"
241
+ task :tail do
242
+ each_heroku_app do |stage|
243
+ stage.logs(:tail)
244
+ end
245
+ end
246
+ end
247
+
248
+ desc "Shows the Heroku logs"
249
+ task :logs => 'logs:default'
250
+
251
+ namespace :db do
252
+ desc "Migrates and restarts remote servers"
253
+ task :migrate do
254
+ each_heroku_app do |stage|
255
+ stage.migrate
256
+ end
257
+ end
258
+
259
+ desc "Pull database from stage to local dev database"
260
+ task :pull do
261
+ each_heroku_app do |stage|
262
+ sh "heroku pgdumps:capture --app #{stage.app}"
263
+ dump = `heroku pgdumps --app #{stage.app}`.split("\n").last.split(" ").first
264
+ sh "mkdir -p #{Rails.root}/db/dumps"
265
+ file = "#{Rails.root}/db/dumps/#{dump}.sql.gz"
266
+ url = `heroku pgdumps:url --app #{stage.app} #{dump}`.chomp
267
+ sh "wget", url, "-O", file
268
+ sh "rake db:drop db:create"
269
+ sh "gunzip -c #{file} | #{Rails.root}/script/dbconsole"
270
+ sh "rake jobs:clear"
271
+ end
272
+ end
273
+ end
274
+
275
+ desc "Run a bash shell on Heroku"
276
+ task :shell do
257
277
  each_heroku_app do |stage|
258
- sh "heroku pgdumps:capture --app #{stage.app}"
259
- dump = `heroku pgdumps --app #{stage.app}`.split("\n").last.split(" ").first
260
- sh "mkdir -p #{Rails.root}/db/dumps"
261
- file = "#{Rails.root}/db/dumps/#{dump}.sql.gz"
262
- url = `heroku pgdumps:url --app #{stage.app} #{dump}`.chomp
263
- sh "wget", url, "-O", file
264
- sh "rake db:drop db:create"
265
- sh "gunzip -c #{file} | #{Rails.root}/script/dbconsole"
266
- sh "rake jobs:clear"
278
+ stage.run 'bash'
267
279
  end
268
280
  end
269
281
  end
270
282
 
283
+ task :all => 'heroku:stage:all'
284
+ task :deploy => 'heroku:deploy'
285
+ task 'deploy:force' => 'heroku:deploy:force'
286
+ task :before_deploy => 'heroku:deploy:before'
287
+ task :after_deploy => 'heroku:deploy:after'
288
+ task :console => 'heroku:console'
289
+ task :restart => 'heroku:restart'
290
+ task :migrate => 'heroku:db:migrate'
291
+ task :logs => 'heroku:logs:default'
292
+ task 'logs:tail' => 'heroku:logs:tail'
293
+ task 'heroku:rack_env' => 'heroku:config:rack_env'
294
+ task :shell => 'heroku:shell'
295
+
271
296
  def each_heroku_app(&block)
272
297
  @heroku_san.each_app(&block)
273
298
  puts
data/spec/git_spec.rb CHANGED
@@ -52,4 +52,23 @@ describe GitTest do
52
52
  subject.git_rev_parse(nil).should == nil
53
53
  end
54
54
  end
55
+
56
+ describe "#git_revision" do
57
+ it "returns the current revision of the repository (on Heroku)" do
58
+ subject.should_receive("`").with("git ls-remote --heads staging master") { "sha\n" }
59
+ subject.git_revision('staging').should == 'sha'
60
+ end
61
+
62
+ it "returns nil if there is no revision (i.e. not deployed yet)" do
63
+ subject.should_receive("`").with("git ls-remote --heads staging master") { "\n" }
64
+ subject.git_revision('staging').should == nil
65
+ end
66
+ end
67
+
68
+ describe "#git_named_rev" do
69
+ it "returns symbolic names for given rev" do
70
+ subject.should_receive("`").with("git name-rev sha") {"sha production/123456\n"}
71
+ subject.git_named_rev('sha').should == 'sha production/123456'
72
+ end
73
+ end
55
74
  end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe HerokuSan::Stage do
4
+ include Git
4
5
  subject { HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "bamboo-ree-1.8.7"})}
5
6
 
6
7
  context "initializes" do
@@ -19,6 +20,16 @@ describe HerokuSan::Stage do
19
20
  its(:repo) { should == 'git@heroku.com:awesomeapp-demo.git' }
20
21
  end
21
22
 
23
+ describe "#app" do
24
+ its(:app) { should == 'awesomeapp'}
25
+ context "blank app" do
26
+ subject { HerokuSan::Stage.new('production') }
27
+ it "should raise an error" do
28
+ expect { subject.app }.to raise_error(HerokuSan::MissingApp, /production: is missing the app: configuration value\./)
29
+ end
30
+ end
31
+ end
32
+
22
33
  context "celadon cedar stack has a different API" do
23
34
  describe "#stack" do
24
35
  it "returns the name of the stack from Heroku" do
@@ -54,6 +65,28 @@ EOT
54
65
  end
55
66
  end
56
67
 
68
+ describe "#deploy" do
69
+ it "deploys to heroku" do
70
+ subject.should_receive(:git_push).with(git_parsed_tag(subject.tag), subject.repo, [])
71
+ subject.deploy
72
+ end
73
+
74
+ it "deploys with a custom sha" do
75
+ subject.should_receive(:git_push).with('deadbeef', subject.repo, [])
76
+ subject.deploy('deadbeef')
77
+ end
78
+
79
+ it "deploys with --force" do
80
+ subject.should_receive(:git_push).with(git_parsed_tag(subject.tag), subject.repo, %w[--force])
81
+ subject.deploy(nil, :force)
82
+ end
83
+
84
+ it "deploys with a custom sha & --force" do
85
+ subject.should_receive(:git_push).with('deadbeef', subject.repo, %w[--force])
86
+ subject.deploy('deadbeef', :force)
87
+ end
88
+ end
89
+
57
90
  describe "#migrate" do
58
91
  it "runs rake db:migrate" do
59
92
  subject.should_receive(:sh).with("heroku run:rake db:migrate --app awesomeapp")
@@ -62,7 +95,7 @@ EOT
62
95
  end
63
96
  end
64
97
 
65
- describe "#maintenance" do
98
+ describe "#maintenance" do
66
99
  it ":on" do
67
100
  subject.should_receive(:sh).with("heroku maintenance:on --app awesomeapp")
68
101
  subject.maintenance :on
@@ -78,6 +111,27 @@ EOT
78
111
  subject.maintenance :busy
79
112
  end.to raise_error ArgumentError, "Action #{:busy.inspect} must be one of (:on, :off)"
80
113
  end
114
+
115
+ context "with a block" do
116
+ it "wraps it in a maitenance mode" do
117
+ subject.should_receive(:sh).with("heroku maintenance:on --app awesomeapp")
118
+ reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now)
119
+ subject.should_receive(:sh).with("heroku maintenance:off --app awesomeapp")
120
+ subject.maintenance do
121
+ reactor.scram(:now)
122
+ end
123
+ end
124
+ it "ensures that maintenance mode is turned off" do
125
+ subject.should_receive(:sh).with("heroku maintenance:on --app awesomeapp")
126
+ reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).and_raise(RuntimeError)
127
+ subject.should_receive(:sh).with("heroku maintenance:off --app awesomeapp")
128
+ expect {
129
+ subject.maintenance do
130
+ reactor.scram(:now)
131
+ end
132
+ }.to raise_error
133
+ end
134
+ end
81
135
  end
82
136
 
83
137
  describe "#create" do
@@ -133,6 +187,39 @@ EOT
133
187
  subject.should_receive(:sh).with("heroku logs --app awesomeapp")
134
188
  subject.logs
135
189
  end
190
+ it "tails log files" do
191
+ subject.should_receive(:sh).with("heroku logs --tail --app awesomeapp")
192
+ subject.logs(:tail)
193
+ end
136
194
  end
137
195
 
196
+ describe "#push_config" do
197
+ it "updates the configuration settings on Heroku" do
198
+ subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {FOO: 'bar', DOG: 'emu'}})
199
+ subject.should_receive(:sh).with("heroku config:add FOO=bar DOG=emu --app awesomeapp")
200
+ subject.push_config
201
+ end
202
+ it "properly escapes variables" do
203
+ subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {FOO: ' bar\emu bat zebra '}})
204
+ subject.should_receive(:sh).with("heroku config:add FOO=#{Shellwords.escape(' bar\emu bat zebra ')} --app awesomeapp")
205
+ subject.push_config
206
+ end
207
+ it "pushes the options hash" do
208
+ subject.should_receive(:sh).with("heroku config:add RACK_ENV=magic --app awesomeapp")
209
+ subject.push_config(RACK_ENV: 'magic')
210
+ end
211
+ end
212
+
213
+ describe "#revision" do
214
+ it "returns the named remote revision for the stage" do
215
+ subject.should_receive(:git_revision).with(subject.repo) {"sha"}
216
+ subject.should_receive(:git_named_rev).with('sha') {"sha production/123456"}
217
+ subject.revision.should == 'sha production/123456'
218
+ end
219
+ it "returns nil if the stage has never been deployed" do
220
+ subject.should_receive(:git_revision).with(subject.repo) {nil}
221
+ subject.should_receive(:git_named_rev).with(nil) {''}
222
+ subject.revision.should == ''
223
+ end
224
+ end
138
225
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku_san
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,11 +12,11 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2012-03-03 00:00:00.000000000Z
15
+ date: 2012-03-06 00:00:00.000000000Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rails
19
- requirement: &2151831540 !ruby/object:Gem::Requirement
19
+ requirement: &2153413220 !ruby/object:Gem::Requirement
20
20
  none: false
21
21
  requirements:
22
22
  - - ! '>='
@@ -24,10 +24,10 @@ dependencies:
24
24
  version: '2'
25
25
  type: :runtime
26
26
  prerelease: false
27
- version_requirements: *2151831540
27
+ version_requirements: *2153413220
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: heroku
30
- requirement: &2151830840 !ruby/object:Gem::Requirement
30
+ requirement: &2153412680 !ruby/object:Gem::Requirement
31
31
  none: false
32
32
  requirements:
33
33
  - - ! '>='
@@ -35,10 +35,10 @@ dependencies:
35
35
  version: '2'
36
36
  type: :runtime
37
37
  prerelease: false
38
- version_requirements: *2151830840
38
+ version_requirements: *2153412680
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: rake
41
- requirement: &2151830320 !ruby/object:Gem::Requirement
41
+ requirement: &2153412280 !ruby/object:Gem::Requirement
42
42
  none: false
43
43
  requirements:
44
44
  - - ! '>='
@@ -46,10 +46,10 @@ dependencies:
46
46
  version: '0'
47
47
  type: :runtime
48
48
  prerelease: false
49
- version_requirements: *2151830320
49
+ version_requirements: *2153412280
50
50
  - !ruby/object:Gem::Dependency
51
51
  name: aruba
52
- requirement: &2151829520 !ruby/object:Gem::Requirement
52
+ requirement: &2153411640 !ruby/object:Gem::Requirement
53
53
  none: false
54
54
  requirements:
55
55
  - - ! '>='
@@ -57,10 +57,10 @@ dependencies:
57
57
  version: '0'
58
58
  type: :development
59
59
  prerelease: false
60
- version_requirements: *2151829520
60
+ version_requirements: *2153411640
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: cucumber
63
- requirement: &2151829020 !ruby/object:Gem::Requirement
63
+ requirement: &2153411060 !ruby/object:Gem::Requirement
64
64
  none: false
65
65
  requirements:
66
66
  - - ! '>='
@@ -68,10 +68,10 @@ dependencies:
68
68
  version: '0'
69
69
  type: :development
70
70
  prerelease: false
71
- version_requirements: *2151829020
71
+ version_requirements: *2153411060
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: rake
74
- requirement: &2151828020 !ruby/object:Gem::Requirement
74
+ requirement: &2153410400 !ruby/object:Gem::Requirement
75
75
  none: false
76
76
  requirements:
77
77
  - - ! '>='
@@ -79,10 +79,10 @@ dependencies:
79
79
  version: '0'
80
80
  type: :development
81
81
  prerelease: false
82
- version_requirements: *2151828020
82
+ version_requirements: *2153410400
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: bundler
85
- requirement: &2151826960 !ruby/object:Gem::Requirement
85
+ requirement: &2153409500 !ruby/object:Gem::Requirement
86
86
  none: false
87
87
  requirements:
88
88
  - - ~>
@@ -90,7 +90,7 @@ dependencies:
90
90
  version: '1.0'
91
91
  type: :development
92
92
  prerelease: false
93
- version_requirements: *2151826960
93
+ version_requirements: *2153409500
94
94
  description: Manage multiple Heroku instances/apps for a single Rails app using Rake
95
95
  email: elijah.miller@gmail.com
96
96
  executables: []
@@ -101,6 +101,7 @@ files:
101
101
  - .gitignore
102
102
  - .gitmodules
103
103
  - .rvmrc
104
+ - CHANGELOG.md
104
105
  - Gemfile
105
106
  - LICENSE
106
107
  - README.rdoc
@@ -108,6 +109,7 @@ files:
108
109
  - autotest/discover.rb
109
110
  - cucumber.yml
110
111
  - examples/auto_tagger.rake
112
+ - examples/push_revision.rake
111
113
  - features/config.feature
112
114
  - features/extended-config.feature
113
115
  - features/remote.feature