kumade 0.2.2 → 0.3.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/.travis.yml +1 -0
- data/README.md +15 -82
- data/features/kumade_executable.feature +4 -4
- data/kumade.gemspec +4 -3
- data/lib/kumade.rb +4 -20
- data/lib/kumade/base.rb +30 -0
- data/lib/kumade/deployer.rb +26 -82
- data/lib/kumade/deployment_error.rb +4 -0
- data/lib/kumade/git.rb +73 -0
- data/lib/kumade/runner.rb +25 -12
- data/lib/kumade/version.rb +1 -1
- data/lib/tasks/deploy.rake +1 -1
- data/spec/kumade/base_spec.rb +18 -0
- data/spec/kumade/deployer_spec.rb +68 -175
- data/spec/kumade/git_spec.rb +51 -0
- data/spec/kumade/runner_spec.rb +35 -8
- data/spec/spec_helper.rb +2 -2
- metadata +38 -28
- data/spec/kumade_spec.rb +0 -49
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -3,6 +3,11 @@ Kumade is a set of basic Rake tasks for deploying to Heroku. It aims to
|
|
3
3
|
provide most of what you want. Unlike other Heroku deploy gems, it is
|
4
4
|
well-tested.
|
5
5
|
|
6
|
+
## Development
|
7
|
+
Development is happening very fast, and the internals are in constant flux. The
|
8
|
+
public API is constant (e.g. `kumade production` will work), but you may have to
|
9
|
+
rebase against master a couple times before your pull request can be merged.
|
10
|
+
|
6
11
|
## What does Kumade do?
|
7
12
|
Before deploying, Kumade ensures the git repo is clean and that all tests pass.
|
8
13
|
After that, it packages assets using
|
@@ -28,6 +33,14 @@ For example, if you have a remote named "bamboo":
|
|
28
33
|
|
29
34
|
$ bundle exec kumade bamboo
|
30
35
|
|
36
|
+
or
|
37
|
+
|
38
|
+
# in your Rakefile:
|
39
|
+
require 'kumade'
|
40
|
+
|
41
|
+
# kumade auto-generates a deploy:ENV task for every Heroku environment
|
42
|
+
$ rake deploy:bamboo
|
43
|
+
|
31
44
|
which will autodetect the name of the Heroku app that the bamboo remote points
|
32
45
|
to and deploy to it.
|
33
46
|
|
@@ -40,93 +53,13 @@ The default is to deploy to staging:
|
|
40
53
|
|
41
54
|
$ bundle exec kumade # equivalent to "bundle exec kumade staging"
|
42
55
|
|
56
|
+
|
43
57
|
## Does it support the Cedar stack?
|
44
58
|
|
45
59
|
Yes. To indicate that a particular app is using Cedar, run with the -c flag:
|
46
60
|
|
47
61
|
bundle exec kumade bamboo -c
|
48
62
|
|
49
|
-
## Sample Output
|
50
|
-
|
51
|
-
### Normal mode
|
52
|
-
|
53
|
-
$ kumade heroku-staging
|
54
|
-
==> Deploying to: heroku-staging
|
55
|
-
==> heroku-staging is a Heroku remote
|
56
|
-
==> Git repo is clean
|
57
|
-
/Users/gabe/.rvm/rubies/ree-1.8.7-2011.03/bin/ruby -S bundle exec rspec [blah blah]
|
58
|
-
....
|
59
|
-
rake output removed
|
60
|
-
...
|
61
|
-
==> Rake passed
|
62
|
-
==> Packaged assets with Jammit
|
63
|
-
==> + git add /Users/gabe/thoughtbot/sushi/public/assets && git commit -m 'Assets'
|
64
|
-
[master bc8932b] Assets
|
65
|
-
4 files changed, 0 insertions(+), 0 deletions(-)
|
66
|
-
==> - true
|
67
|
-
==> Added and committed all assets
|
68
|
-
==> + git push origin master
|
69
|
-
Counting objects: 15, done.
|
70
|
-
Delta compression using up to 2 threads.
|
71
|
-
Compressing objects: 100% (8/8), done.
|
72
|
-
Writing objects: 100% (8/8), 639 bytes, done.
|
73
|
-
Total 8 (delta 7), reused 0 (delta 0)
|
74
|
-
To git@github.com:sushi/sushi.git
|
75
|
-
a465afd..bc8932b master -> master
|
76
|
-
==> - true
|
77
|
-
==> Pushed master -> origin
|
78
|
-
==> + git push -f heroku-staging master
|
79
|
-
Counting objects: 15, done.
|
80
|
-
Delta compression using up to 2 threads.
|
81
|
-
Compressing objects: 100% (8/8), done.
|
82
|
-
Writing objects: 100% (8/8), 639 bytes, done.
|
83
|
-
Total 8 (delta 7), reused 0 (delta 0)
|
84
|
-
|
85
|
-
-----> Heroku receiving push
|
86
|
-
-----> Rails app detected
|
87
|
-
-----> Detected Rails is not set to serve static_assets
|
88
|
-
Installing rails3_serve_static_assets... done
|
89
|
-
-----> Configure Rails 3 to disable x-sendfile
|
90
|
-
Installing rails3_disable_x_sendfile... done
|
91
|
-
-----> Configure Rails to log to stdout
|
92
|
-
Installing rails_log_stdout... done
|
93
|
-
-----> Gemfile detected, running Bundler version 1.0.7
|
94
|
-
All dependencies are satisfied
|
95
|
-
-----> Compiled slug size is 65.5MB
|
96
|
-
-----> Launching... done, v172
|
97
|
-
http://staging-sushi.heroku.com deployed to Heroku
|
98
|
-
|
99
|
-
To git@heroku.com:staging-sushi.git
|
100
|
-
a465afd..bc8932b master -> master
|
101
|
-
==> - true
|
102
|
-
==> Force pushed master -> heroku-staging
|
103
|
-
==> + bundle exec heroku rake db:migrate --app staging-sushi
|
104
|
-
... Postgres output removed ...
|
105
|
-
==> - false
|
106
|
-
==> Migrated staging-sushi
|
107
|
-
==> Deployed to: heroku-staging
|
108
|
-
|
109
|
-
### Pretend mode
|
110
|
-
|
111
|
-
$ kumade heroku-staging -p
|
112
|
-
==> In Pretend Mode
|
113
|
-
==> Deploying to: heroku-staging
|
114
|
-
==> heroku-staging is a Heroku remote
|
115
|
-
==> Git repo is clean
|
116
|
-
==> Rake passed
|
117
|
-
==> Packaged assets with Jammit
|
118
|
-
==> Pushed master -> origin
|
119
|
-
==> Force pushed master -> heroku-staging
|
120
|
-
==> Migrated staging-sushi
|
121
|
-
==> Deployed to: heroku-staging
|
122
|
-
|
123
|
-
### Pretend Mode with a non-Heroku remote
|
124
|
-
|
125
|
-
$ kumade origin -p
|
126
|
-
==> In Pretend Mode
|
127
|
-
==> Deploying to: origin
|
128
|
-
==> ! Cannot deploy: "origin" remote does not point to Heroku
|
129
|
-
|
130
63
|
## Compatibility
|
131
64
|
|
132
65
|
Tested against:
|
@@ -137,7 +70,7 @@ Tested against:
|
|
137
70
|
|
138
71
|
## Misc Features
|
139
72
|
|
140
|
-
Want to run a task before bundling your assets on deploy? In your
|
73
|
+
Want to run a task before bundling your assets on deploy? In your Rails app's rake tasks, drop in:
|
141
74
|
|
142
75
|
``` ruby
|
143
76
|
namespace :kumade do
|
@@ -26,8 +26,8 @@ Feature: Kumade executable
|
|
26
26
|
==> Pushed master -> origin
|
27
27
|
run git branch deploy
|
28
28
|
run git push -f pretend-staging deploy:master
|
29
|
-
==>
|
30
|
-
==> Migrated pretend-staging
|
29
|
+
==> Pushed deploy:master -> pretend-staging
|
30
|
+
==> Migrated pretend-staging
|
31
31
|
run git checkout master && git branch -D deploy
|
32
32
|
==> Deployed to: pretend-staging
|
33
33
|
"""
|
@@ -57,8 +57,8 @@ Feature: Kumade executable
|
|
57
57
|
==> Pushed new_branch -> origin
|
58
58
|
run git branch deploy
|
59
59
|
run git push -f pretend-staging deploy:master
|
60
|
-
==>
|
61
|
-
==> Migrated pretend-staging
|
60
|
+
==> Pushed deploy:master -> pretend-staging
|
61
|
+
==> Migrated pretend-staging
|
62
62
|
run git checkout new_branch && git branch -D deploy
|
63
63
|
==> Deployed to: pretend-staging
|
64
64
|
"""
|
data/kumade.gemspec
CHANGED
@@ -7,8 +7,8 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Kumade::VERSION
|
8
8
|
s.authors = ["Gabe Berke-Williams", "thoughtbot"]
|
9
9
|
s.email = ["gabe@thoughtbot.com", "support@thoughtbot.com"]
|
10
|
-
s.homepage = ""
|
11
|
-
s.summary = %q{
|
10
|
+
s.homepage = "http://thoughtbot.com/community/"
|
11
|
+
s.summary = %q{A well-tested script for easy deploying to Heroku}
|
12
12
|
s.description = s.summary
|
13
13
|
|
14
14
|
s.files = `git ls-files`.split("\n")
|
@@ -18,8 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.add_dependency('heroku', '~> 2.0')
|
20
20
|
s.add_dependency('thor', '~> 0.14')
|
21
|
+
s.add_dependency('rake', '>= 0.8.7')
|
21
22
|
|
22
|
-
s.add_development_dependency('rake', '
|
23
|
+
s.add_development_dependency('rake', '>= 0.8.7')
|
23
24
|
s.add_development_dependency('rspec', '~> 2.6.0')
|
24
25
|
s.add_development_dependency('cucumber', '~> 1.0.2')
|
25
26
|
s.add_development_dependency('aruba', '~> 0.4.3')
|
data/lib/kumade.rb
CHANGED
@@ -1,29 +1,13 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'thor'
|
3
|
+
require 'stringio'
|
3
4
|
|
5
|
+
require 'kumade/base'
|
6
|
+
require 'kumade/git'
|
4
7
|
require 'kumade/deployer'
|
5
8
|
require 'kumade/runner'
|
6
9
|
require 'kumade/railtie'
|
10
|
+
require 'kumade/deployment_error'
|
7
11
|
|
8
12
|
module Kumade
|
9
|
-
def self.app_for(environment)
|
10
|
-
heroku_git_url = `git config --get remote.#{environment}.url`.strip
|
11
|
-
if heroku_git_url =~ /^git@heroku\.com:(.+)\.git$/
|
12
|
-
$1
|
13
|
-
else
|
14
|
-
nil
|
15
|
-
end
|
16
|
-
end
|
17
|
-
def self.environments
|
18
|
-
url_remotes = `git remote`.strip.split("\n").map{|remote| [remote, `git config --get remote.#{remote}.url`.strip] }.select{|remote| remote.last =~ /^git@heroku\.com:(.+)\.git$/}.map{|remote| remote.first}
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.on_cedar!(app)
|
22
|
-
@cedar_apps ||= []
|
23
|
-
@cedar_apps << app
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.cedar?(app)
|
27
|
-
@cedar_apps.include?(app)
|
28
|
-
end
|
29
13
|
end
|
data/lib/kumade/base.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Kumade
|
2
|
+
class Base < Thor::Shell::Color
|
3
|
+
def initialize
|
4
|
+
super()
|
5
|
+
end
|
6
|
+
|
7
|
+
def run_or_error(commands, error_message)
|
8
|
+
all_commands = [commands].flatten.join(' && ')
|
9
|
+
if @pretending
|
10
|
+
say_status(:run, all_commands)
|
11
|
+
else
|
12
|
+
error(error_message) unless run(all_commands)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(command, config = {})
|
17
|
+
say_status :run, command
|
18
|
+
config[:capture] ? `#{command}` : system("#{command}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def error(message)
|
22
|
+
say("==> ! #{message}", :red)
|
23
|
+
raise Kumade::DeploymentError.new(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def success(message)
|
27
|
+
say("==> #{message}", :green)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/kumade/deployer.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
module Kumade
|
2
|
-
class Deployer <
|
2
|
+
class Deployer < Base
|
3
3
|
DEPLOY_BRANCH = "deploy"
|
4
|
-
attr_reader :environment, :pretending
|
4
|
+
attr_reader :environment, :pretending, :git
|
5
5
|
|
6
|
-
def initialize(environment = 'staging', pretending = false
|
6
|
+
def initialize(environment = 'staging', pretending = false)
|
7
7
|
super()
|
8
8
|
@environment = environment
|
9
9
|
@pretending = pretending
|
10
|
-
@
|
11
|
-
@
|
10
|
+
@git = Git.new(pretending, environment)
|
11
|
+
@branch = @git.current_branch
|
12
12
|
end
|
13
13
|
|
14
14
|
def deploy
|
@@ -26,50 +26,44 @@ module Kumade
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def sync_github
|
29
|
-
|
30
|
-
"Failed to push #{@branch} -> origin")
|
31
|
-
success("Pushed #{@branch} -> origin")
|
29
|
+
git.push(@branch)
|
32
30
|
end
|
33
31
|
|
34
32
|
def sync_heroku
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
run_or_error("git push -f #{environment} #{DEPLOY_BRANCH}:master",
|
39
|
-
"Failed to force push #{DEPLOY_BRANCH} -> #{environment}/master")
|
40
|
-
success("Force pushed #{@branch} -> #{environment}")
|
33
|
+
git.create(DEPLOY_BRANCH)
|
34
|
+
git.push("#{DEPLOY_BRANCH}:master", environment, true)
|
41
35
|
end
|
42
36
|
|
43
37
|
def heroku_migrate
|
44
|
-
|
45
|
-
|
46
|
-
heroku("rake db:migrate", app) unless pretending
|
47
|
-
success("Migrated #{app}")
|
38
|
+
heroku("rake db:migrate") unless pretending
|
39
|
+
success("Migrated #{environment}")
|
48
40
|
end
|
49
41
|
|
50
42
|
def post_deploy
|
51
|
-
|
52
|
-
"Failed to clean up #{DEPLOY_BRANCH} branch")
|
43
|
+
git.delete(DEPLOY_BRANCH, @branch)
|
53
44
|
end
|
54
45
|
|
55
|
-
def heroku(command
|
56
|
-
heroku_command = if
|
46
|
+
def heroku(command)
|
47
|
+
heroku_command = if cedar?
|
57
48
|
"bundle exec heroku run"
|
58
49
|
else
|
59
50
|
"bundle exec heroku"
|
60
51
|
end
|
61
|
-
run_or_error("#{heroku_command} #{command} --
|
52
|
+
run_or_error("#{heroku_command} #{command} --remote #{environment}",
|
62
53
|
"Failed to run #{command} on Heroku")
|
63
54
|
end
|
64
55
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
success("Git repo is clean")
|
56
|
+
def cedar?
|
57
|
+
return @cedar unless @cedar.nil?
|
58
|
+
@cedar = heroku("stack").split("\n").grep(/\*/).any? do |line|
|
59
|
+
line.include?("cedar")
|
70
60
|
end
|
71
61
|
end
|
72
62
|
|
63
|
+
def ensure_clean_git
|
64
|
+
git.ensure_clean_git
|
65
|
+
end
|
66
|
+
|
73
67
|
def package_assets
|
74
68
|
invoke_custom_task if custom_task?
|
75
69
|
package_with_jammit if jammit_installed?
|
@@ -100,7 +94,7 @@ module Kumade
|
|
100
94
|
else
|
101
95
|
begin
|
102
96
|
run "bundle exec rake more:generate"
|
103
|
-
if
|
97
|
+
if git.dirty?
|
104
98
|
success(success_message)
|
105
99
|
git_add_and_commit_all_assets_in(more_assets_path)
|
106
100
|
end
|
@@ -116,10 +110,7 @@ module Kumade
|
|
116
110
|
end
|
117
111
|
|
118
112
|
def git_add_and_commit_all_assets_in(dir)
|
119
|
-
|
120
|
-
"Cannot deploy: couldn't commit assets"
|
121
|
-
|
122
|
-
success "Added and committed all assets"
|
113
|
+
git.add_and_commit_all_in(dir, DEPLOY_BRANCH, 'Compiled assets', "Added and committed all assets", "couldn't commit assets")
|
123
114
|
end
|
124
115
|
|
125
116
|
def jammit_assets_path
|
@@ -157,44 +148,9 @@ module Kumade
|
|
157
148
|
Rake::Task.task_defined?("kumade:before_asset_compilation")
|
158
149
|
end
|
159
150
|
|
160
|
-
def git_dirty?
|
161
|
-
`git diff --exit-code`
|
162
|
-
!$?.success?
|
163
|
-
end
|
164
|
-
|
165
|
-
def run_or_error(commands, error_message)
|
166
|
-
all_commands = [commands].flatten.join(' && ')
|
167
|
-
if pretending
|
168
|
-
say_status(:run, all_commands)
|
169
|
-
else
|
170
|
-
error(error_message) unless run(all_commands)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def run(command, config = {})
|
175
|
-
say_status :run, command
|
176
|
-
config[:capture] ? `#{command}` : system("#{command}")
|
177
|
-
end
|
178
|
-
|
179
|
-
def branch_exist?(branch)
|
180
|
-
branches = `git branch`
|
181
|
-
regex = Regexp.new('[\\n\\s\\*]+' + Regexp.escape(branch.to_s) + '\\n')
|
182
|
-
result = ((branches =~ regex) ? true : false)
|
183
|
-
return result
|
184
|
-
end
|
185
|
-
|
186
|
-
def error(message)
|
187
|
-
say("==> ! #{message}", :red)
|
188
|
-
exit 1
|
189
|
-
end
|
190
|
-
|
191
|
-
def success(message)
|
192
|
-
say("==> #{message}", :green)
|
193
|
-
end
|
194
|
-
|
195
151
|
def ensure_heroku_remote_exists
|
196
|
-
if remote_exists?(environment)
|
197
|
-
if
|
152
|
+
if git.remote_exists?(environment)
|
153
|
+
if git.heroku_remote?
|
198
154
|
success("#{environment} is a Heroku remote")
|
199
155
|
else
|
200
156
|
error(%{Cannot deploy: "#{environment}" remote does not point to Heroku})
|
@@ -203,17 +159,5 @@ module Kumade
|
|
203
159
|
error(%{Cannot deploy: "#{environment}" remote does not exist})
|
204
160
|
end
|
205
161
|
end
|
206
|
-
|
207
|
-
def remote_exists?(remote_name)
|
208
|
-
if pretending
|
209
|
-
true
|
210
|
-
else
|
211
|
-
`git remote` =~ /^#{remote_name}$/
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def current_branch
|
216
|
-
`git symbolic-ref HEAD`.sub("refs/heads/", "").strip
|
217
|
-
end
|
218
162
|
end
|
219
163
|
end
|
data/lib/kumade/git.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Kumade
|
2
|
+
class Git < Base
|
3
|
+
attr_reader :environment
|
4
|
+
def initialize(pretending, environment)
|
5
|
+
super()
|
6
|
+
@pretending = pretending
|
7
|
+
@environment = environment
|
8
|
+
end
|
9
|
+
|
10
|
+
def heroku_remote?
|
11
|
+
`git config --get remote.#{environment}.url`.strip =~ /^git@heroku\..+:(.+)\.git$/
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.environments
|
15
|
+
url_remotes = `git remote`.strip.split("\n").map{|remote| [remote, `git config --get remote.#{remote}.url`.strip] }.select{|remote| remote.last =~ /^git@heroku\.com:(.+)\.git$/}.map{|remote| remote.first}
|
16
|
+
end
|
17
|
+
|
18
|
+
def push(branch, remote = 'origin', force = false)
|
19
|
+
command = ["git push"]
|
20
|
+
command << "-f" if force
|
21
|
+
command << remote
|
22
|
+
command << branch
|
23
|
+
command = command.join(" ")
|
24
|
+
run_or_error([command], "Failed to push #{branch} -> #{remote}")
|
25
|
+
success("Pushed #{branch} -> #{remote}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def create(branch)
|
29
|
+
unless branch_exist?(branch)
|
30
|
+
run_or_error("git branch #{branch}", "Failed to create #{branch}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(branch_to_delete, branch_to_checkout)
|
35
|
+
run_or_error(["git checkout #{branch_to_checkout}", "git branch -D #{branch_to_delete}"],
|
36
|
+
"Failed to clean up #{branch_to_delete} branch")
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_and_commit_all_in(dir, branch, commit_message, success_output, error_output)
|
40
|
+
run_or_error ["git checkout -b #{branch}", "git add -f #{dir}", "git commit -m '#{commit_message}'"],
|
41
|
+
"Cannot deploy: #{error_output}"
|
42
|
+
success success_output
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_branch
|
46
|
+
`git symbolic-ref HEAD`.sub("refs/heads/", "").strip
|
47
|
+
end
|
48
|
+
|
49
|
+
def remote_exists?(remote_name)
|
50
|
+
if @pretending
|
51
|
+
true
|
52
|
+
else
|
53
|
+
`git remote` =~ /^#{remote_name}$/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def dirty?
|
58
|
+
! system("git diff --exit-code")
|
59
|
+
end
|
60
|
+
|
61
|
+
def ensure_clean_git
|
62
|
+
if ! @pretending && dirty?
|
63
|
+
error("Cannot deploy: repo is not clean.")
|
64
|
+
else
|
65
|
+
success("Git repo is clean")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def branch_exist?(branch)
|
70
|
+
system("git show-ref #{branch}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|