heroku_san 2.2.2 → 3.0.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.md +19 -8
- data/README.rdoc +30 -8
- data/features/config.feature +11 -2
- data/features/extended-config.feature +1 -1
- data/features/remote.feature +25 -1
- data/features/step_definitions/remote_steps.rb +106 -48
- data/heroku_san.gemspec +1 -0
- data/lib/generators/heroku_san_generator.rb +7 -0
- data/lib/heroku_san/deploy/base.rb +14 -0
- data/lib/heroku_san/deploy/rails.rb +13 -0
- data/lib/heroku_san/deploy/sinatra.rb +11 -0
- data/lib/heroku_san/project.rb +5 -2
- data/lib/heroku_san/stage.rb +13 -19
- data/lib/{tasks.rb → heroku_san/tasks.rb} +18 -13
- data/lib/heroku_san/version.rb +1 -1
- data/lib/heroku_san.rb +5 -2
- data/lib/railtie.rb +1 -2
- data/lib/tasks/heroku.rake +1 -1
- data/spec/heroku_san/deploy/base_spec.rb +33 -0
- data/spec/heroku_san/deploy/rails_spec.rb +17 -0
- data/spec/heroku_san/project_spec.rb +86 -75
- data/spec/heroku_san/stage_spec.rb +24 -5
- metadata +147 -132
- data/.rvmrc +0 -1
@@ -1,12 +1,18 @@
|
|
1
|
+
require 'heroku_san'
|
1
2
|
require 'git'
|
2
3
|
include Git
|
3
4
|
|
4
|
-
|
5
|
+
if defined?(Rails)
|
6
|
+
HerokuSan.project ||= HerokuSan::Project.new(
|
7
|
+
Rails.root.join("config", "heroku.yml"),
|
8
|
+
:deploy => HerokuSan::Deploy::Rails
|
9
|
+
)
|
10
|
+
end
|
5
11
|
|
6
|
-
|
12
|
+
HerokuSan.project.all.each do |stage|
|
7
13
|
desc "Select #{stage} Heroku app for later commands"
|
8
14
|
task "heroku:stage:#{stage}" do
|
9
|
-
|
15
|
+
HerokuSan.project << stage
|
10
16
|
end
|
11
17
|
task stage => "heroku:stage:#{stage}"
|
12
18
|
end
|
@@ -14,7 +20,7 @@ end
|
|
14
20
|
namespace :heroku do
|
15
21
|
desc 'Select all Heroku apps for later command'
|
16
22
|
task 'stage:all' do
|
17
|
-
|
23
|
+
HerokuSan.project << HerokuSan.project.all
|
18
24
|
end
|
19
25
|
|
20
26
|
desc "Creates the Heroku app"
|
@@ -108,8 +114,9 @@ namespace :heroku do
|
|
108
114
|
|
109
115
|
desc 'Creates an example configuration file'
|
110
116
|
task :create_config do
|
111
|
-
|
112
|
-
|
117
|
+
# FIXME: sh "rails generate heroku_san"
|
118
|
+
filename = %Q{#{HerokuSan.project.config_file.to_s}}
|
119
|
+
if HerokuSan.project.create_config
|
113
120
|
puts "Copied example config to #{filename.inspect}"
|
114
121
|
if ENV['EDITOR'] && ENV['EDITOR'] != ''
|
115
122
|
sh "#{ENV['EDITOR']} #{filename}"
|
@@ -163,7 +170,7 @@ namespace :heroku do
|
|
163
170
|
desc "Pushes the given commit (default: HEAD)"
|
164
171
|
task :push, :commit do |t, args|
|
165
172
|
each_heroku_app do |stage|
|
166
|
-
stage.
|
173
|
+
stage.push(args[:commit])
|
167
174
|
end
|
168
175
|
end
|
169
176
|
|
@@ -171,7 +178,7 @@ namespace :heroku do
|
|
171
178
|
desc "Force-pushes the given commit (default: HEAD)"
|
172
179
|
task :force, :commit do |t, args|
|
173
180
|
each_heroku_app do |stage|
|
174
|
-
stage.
|
181
|
+
stage.push(args[:commit], :force)
|
175
182
|
end
|
176
183
|
end
|
177
184
|
end
|
@@ -203,8 +210,7 @@ namespace :heroku do
|
|
203
210
|
desc "Pushes the given commit, migrates and restarts (default: HEAD)"
|
204
211
|
task :deploy, [:commit] => [:before_deploy] do |t, args|
|
205
212
|
each_heroku_app do |stage|
|
206
|
-
stage.deploy(args
|
207
|
-
stage.migrate
|
213
|
+
stage.deploy(args)
|
208
214
|
end
|
209
215
|
Rake::Task[:after_deploy].execute
|
210
216
|
end
|
@@ -213,8 +219,7 @@ namespace :heroku do
|
|
213
219
|
desc "Force-pushes the given commit, migrates and restarts (default: HEAD)"
|
214
220
|
task :force, [:commit] => [:before_deploy] do |t, args|
|
215
221
|
each_heroku_app do |stage|
|
216
|
-
stage.deploy(args
|
217
|
-
stage.migrate
|
222
|
+
stage.deploy(args.merge(:force => true))
|
218
223
|
end
|
219
224
|
Rake::Task[:after_deploy].execute
|
220
225
|
end
|
@@ -325,7 +330,7 @@ alias_task 'heroku:rack_env' => 'heroku:config:rack_env'
|
|
325
330
|
alias_task :shell => 'heroku:shell'
|
326
331
|
|
327
332
|
def each_heroku_app(&block)
|
328
|
-
|
333
|
+
HerokuSan.project.each_app(&block)
|
329
334
|
puts
|
330
335
|
rescue HerokuSan::NoApps => e
|
331
336
|
puts "You must first specify at least one Heroku app:
|
data/lib/heroku_san/version.rb
CHANGED
data/lib/heroku_san.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
require 'railtie' if defined?(Rails) && Rails::VERSION::MAJOR
|
1
|
+
require 'railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
|
2
2
|
require 'git'
|
3
3
|
require 'heroku_san/stage'
|
4
4
|
require 'heroku_san/project'
|
5
|
+
require 'heroku_san/deploy/rails'
|
6
|
+
require 'heroku_san/deploy/sinatra'
|
5
7
|
|
6
8
|
module HerokuSan
|
9
|
+
mattr_accessor :project
|
7
10
|
class NoApps < StandardError; end
|
8
11
|
class MissingApp < StandardError; end
|
9
12
|
class Deprecated < StandardError; end
|
10
|
-
end
|
13
|
+
end
|
data/lib/railtie.rb
CHANGED
data/lib/tasks/heroku.rake
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../heroku_san', 'tasks'))
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module HerokuSan
|
4
|
+
module Deploy
|
5
|
+
describe Base do
|
6
|
+
let(:stage) { HerokuSan::Stage.new('test', {"app" => "awesomeapp", "deploy" => 'HerokuSan::Deploy::Base'}) }
|
7
|
+
|
8
|
+
it "calls push" do
|
9
|
+
subject = described_class.new(stage, {})
|
10
|
+
stage.should_receive(:push).with(nil, nil)
|
11
|
+
subject.deploy
|
12
|
+
end
|
13
|
+
|
14
|
+
it "calls push(sha)" do
|
15
|
+
subject = described_class.new(stage, {:commit => 'sha'})
|
16
|
+
stage.should_receive(:push).with('sha', nil)
|
17
|
+
subject.deploy
|
18
|
+
end
|
19
|
+
|
20
|
+
it "calls push(nil, :force)" do
|
21
|
+
subject = described_class.new(stage, {:force => true})
|
22
|
+
stage.should_receive(:push).with(nil, true)
|
23
|
+
subject.deploy
|
24
|
+
end
|
25
|
+
|
26
|
+
it "calls push(sha, :force)" do
|
27
|
+
subject = described_class.new(stage, {:commit => 'sha', :force => true})
|
28
|
+
stage.should_receive(:push).with('sha', true)
|
29
|
+
subject.deploy
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module HerokuSan
|
4
|
+
module Deploy
|
5
|
+
describe Rails do
|
6
|
+
let(:stage) { HerokuSan::Stage.new('test', {"app" => "awesomeapp", "deploy" => 'HerokuSan::Deploy::Rails'}) }
|
7
|
+
|
8
|
+
it "calls migrate" do
|
9
|
+
subject = described_class.new(stage, {})
|
10
|
+
stage.should_receive(:push) # "mock" super
|
11
|
+
stage.should_receive(:rake).with('db:migrate')
|
12
|
+
stage.should_receive(:restart)
|
13
|
+
subject.deploy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -2,96 +2,106 @@ require 'spec_helper'
|
|
2
2
|
require 'tmpdir'
|
3
3
|
|
4
4
|
describe HerokuSan::Project do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
context "using the example config file" do
|
11
|
-
let(:heroku_config_file) { File.join(SPEC_ROOT, "fixtures", "example.yml") }
|
12
|
-
let(:template_config_file) {
|
13
|
-
path = File.join(SPEC_ROOT, "..", "lib/templates", "heroku.example.yml")
|
14
|
-
(File.respond_to? :realpath) ? File.realpath(path) : path
|
15
|
-
}
|
16
|
-
let(:heroku_san) { HerokuSan::Project.new(heroku_config_file) }
|
17
|
-
subject { heroku_san }
|
18
|
-
|
19
|
-
its(:all) { should =~ %w[production staging demo] }
|
20
|
-
|
21
|
-
context "using the heroku_san format" do
|
22
|
-
let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "old_format.yml")) }
|
5
|
+
let(:heroku_config_file) { File.join(SPEC_ROOT, "fixtures", "example.yml") }
|
6
|
+
let(:heroku_san) { HerokuSan::Project.new(heroku_config_file) }
|
7
|
+
subject { heroku_san }
|
23
8
|
|
24
|
-
|
25
|
-
|
9
|
+
describe ".new" do
|
10
|
+
its(:all) { should =~ %w[production staging demo] }
|
11
|
+
specify "with a missing config file has no stages" do
|
12
|
+
heroku_san = HerokuSan::Project.new("/u/should/never/get/here")
|
13
|
+
heroku_san.all.should == []
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "with a deploy option configures each stage with the strategy" do
|
17
|
+
heroku_san = HerokuSan::Project.new(heroku_config_file, :deploy => HerokuSan::Deploy::Base)
|
18
|
+
heroku_san << heroku_san.all
|
19
|
+
heroku_san.each_app do |stage|
|
20
|
+
stage.instance_variable_get('@options')['deploy'].should == HerokuSan::Deploy::Base
|
26
21
|
end
|
27
22
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#apps constructs the deploy list" do
|
26
|
+
it "appends known shorthands to apps" do
|
27
|
+
heroku_san.apps.should == []
|
28
|
+
heroku_san << 'production'
|
29
|
+
heroku_san.apps.should == %w[production]
|
30
|
+
heroku_san << 'staging'
|
31
|
+
heroku_san.apps.should == %w[production staging]
|
32
|
+
heroku_san << 'unknown'
|
33
|
+
heroku_san.apps.should == %w[production staging]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "appends .all (or any array)" do
|
37
|
+
heroku_san << heroku_san.all
|
38
|
+
heroku_san.apps.should == heroku_san.all
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "extra (default) behaviors" do
|
42
|
+
specify "on a git branch that matches an app name" do
|
43
|
+
heroku_san.should_receive(:git_active_branch) { "staging" }
|
44
|
+
$stdout.should_receive(:puts).with("Defaulting to 'staging' as it matches the current branch")
|
45
|
+
expect {
|
46
|
+
heroku_san.apps.should == %w[staging]
|
47
|
+
}.to change{heroku_san.instance_variable_get('@apps')}.from([]).to(%w[staging])
|
38
48
|
end
|
39
49
|
|
40
|
-
|
41
|
-
heroku_san
|
42
|
-
heroku_san.apps.should ==
|
50
|
+
specify "on a git branch that doesn't matches an app name" do
|
51
|
+
heroku_san.should_receive(:git_active_branch) { "master" }
|
52
|
+
heroku_san.apps.should == %w[]
|
43
53
|
end
|
44
54
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
$stdout.should_receive(:puts).with(
|
55
|
+
context "with only a single configured app" do
|
56
|
+
let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "single_app.yml")) }
|
57
|
+
it "returns the app" do
|
58
|
+
$stdout.should_receive(:puts).with('Defaulting to "production" since only one app is defined')
|
49
59
|
expect {
|
50
|
-
heroku_san.apps.should == %w[
|
51
|
-
}.to change{heroku_san.instance_variable_get('@apps')}.from([]).to(%w[
|
52
|
-
end
|
53
|
-
|
54
|
-
specify "on a git branch that doesn't matches an app name" do
|
55
|
-
heroku_san.should_receive(:git_active_branch) { "master" }
|
56
|
-
heroku_san.apps.should == %w[]
|
57
|
-
end
|
58
|
-
|
59
|
-
context "with only a single configured app" do
|
60
|
-
let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "single_app.yml")) }
|
61
|
-
it "returns the app" do
|
62
|
-
$stdout.should_receive(:puts).with('Defaulting to "production" since only one app is defined')
|
63
|
-
expect {
|
64
|
-
heroku_san.apps.should == %w[production]
|
65
|
-
}.to change{heroku_san.instance_variable_get('@apps')}.from([]).to(%w[production])
|
66
|
-
end
|
60
|
+
heroku_san.apps.should == %w[production]
|
61
|
+
}.to change{heroku_san.instance_variable_get('@apps')}.from([]).to(%w[production])
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#each_app" do
|
68
|
+
it "raises an error is no apps were specified" do
|
69
|
+
expect { heroku_san.each_app do true; end }.to raise_error HerokuSan::NoApps
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
heroku_san << 'production'
|
78
|
-
block = double('block')
|
79
|
-
block.should_receive(:action).with(heroku_san['production'])
|
80
|
-
heroku_san.each_app do |stage|
|
81
|
-
block.action(stage)
|
82
|
-
end
|
72
|
+
it "yields to a block with args" do
|
73
|
+
heroku_san << 'production'
|
74
|
+
block = double('block')
|
75
|
+
block.should_receive(:action).with(heroku_san['production'])
|
76
|
+
heroku_san.each_app do |stage|
|
77
|
+
block.action(stage)
|
83
78
|
end
|
84
79
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#[]" do
|
83
|
+
it "returns a config section" do
|
84
|
+
heroku_san.all.each do |app|
|
85
|
+
heroku_san[app].should be_a HerokuSan::Stage
|
91
86
|
end
|
92
87
|
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "using the heroku_san format" do
|
91
|
+
let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "old_format.yml")) }
|
92
|
+
|
93
|
+
it "returns a list of apps" do
|
94
|
+
heroku_san.all.should =~ %w[production staging demo]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#create_config" do
|
99
|
+
context "unknown project" do
|
100
|
+
let(:template_config_file) do
|
101
|
+
path = File.join(SPEC_ROOT, "..", "lib/templates", "heroku.example.yml")
|
102
|
+
(File.respond_to? :realpath) ? File.realpath(path) : path
|
103
|
+
end
|
93
104
|
|
94
|
-
describe "#create_config" do
|
95
105
|
it "creates a new file using the example file" do
|
96
106
|
Dir.mktmpdir do |dir|
|
97
107
|
tmp_config_file = File.join dir, 'config.yml'
|
@@ -100,11 +110,12 @@ describe HerokuSan::Project do
|
|
100
110
|
heroku_san.create_config.should be_true
|
101
111
|
end
|
102
112
|
end
|
103
|
-
|
113
|
+
|
104
114
|
it "does not overwrite an existing file" do
|
105
115
|
FileUtils.should_not_receive(:cp)
|
106
116
|
heroku_san.create_config.should be_false
|
107
117
|
end
|
108
118
|
end
|
109
|
-
end
|
119
|
+
end
|
120
|
+
|
110
121
|
end
|
@@ -83,25 +83,25 @@ describe HerokuSan::Stage do
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
describe "#
|
86
|
+
describe "#push" do
|
87
87
|
it "deploys to heroku" do
|
88
88
|
subject.should_receive(:git_push).with(git_parsed_tag(subject.tag), subject.repo, [])
|
89
|
-
subject.
|
89
|
+
subject.push
|
90
90
|
end
|
91
91
|
|
92
92
|
it "deploys with a custom sha" do
|
93
93
|
subject.should_receive(:git_push).with('deadbeef', subject.repo, [])
|
94
|
-
subject.
|
94
|
+
subject.push('deadbeef')
|
95
95
|
end
|
96
96
|
|
97
97
|
it "deploys with --force" do
|
98
98
|
subject.should_receive(:git_push).with(git_parsed_tag(subject.tag), subject.repo, %w[--force])
|
99
|
-
subject.
|
99
|
+
subject.push(nil, :force)
|
100
100
|
end
|
101
101
|
|
102
102
|
it "deploys with a custom sha & --force" do
|
103
103
|
subject.should_receive(:git_push).with('deadbeef', subject.repo, %w[--force])
|
104
|
-
subject.
|
104
|
+
subject.push('deadbeef', :force)
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
@@ -114,6 +114,25 @@ describe HerokuSan::Stage do
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
describe "#deploy" do
|
118
|
+
context "using the default strategy" do
|
119
|
+
it "(rails) pushes & migrates" do
|
120
|
+
HerokuSan::Deploy::Rails.any_instance.should_receive(:deploy)
|
121
|
+
subject.deploy
|
122
|
+
end
|
123
|
+
end
|
124
|
+
context "using a custom strategy" do
|
125
|
+
class TestDeployStrategy < HerokuSan::Deploy::Base
|
126
|
+
def deploy; end
|
127
|
+
end
|
128
|
+
subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "deploy" => TestDeployStrategy})
|
129
|
+
it "(custom) calls deploy" do
|
130
|
+
TestDeployStrategy.any_instance.should_receive(:deploy)
|
131
|
+
subject.deploy
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
117
136
|
describe "#maintenance" do
|
118
137
|
it ":on" do
|
119
138
|
with_app(subject, 'name' => subject.app )do |app_data|
|