recap 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/README.md +55 -10
- data/Rakefile +19 -5
- data/bin/recap +2 -2
- data/features/managing-processes.feature +1 -1
- data/features/setting-environment-variables.feature +26 -1
- data/features/steps/capistrano_steps.rb +10 -6
- data/features/support/project.rb +24 -5
- data/features/templates/project/Capfile.erb +1 -1
- data/lib/recap/recipes/rails.rb +6 -0
- data/lib/recap/recipes/ruby.rb +11 -0
- data/lib/recap/recipes/static.rb +3 -0
- data/lib/recap/recipes.rb +18 -0
- data/lib/recap/support/capistrano_extensions.rb +85 -0
- data/lib/recap/support/cli.rb +57 -0
- data/lib/recap/{compatibility.rb → support/compatibility.rb} +2 -2
- data/lib/recap/support/environment.rb +61 -0
- data/lib/recap/support/namespace.rb +47 -0
- data/lib/recap/support/shell_command.rb +35 -0
- data/lib/recap/support/templates/Capfile.erb +6 -0
- data/lib/recap/tasks/bootstrap.rb +77 -0
- data/lib/recap/{bundler.rb → tasks/bundler.rb} +15 -6
- data/lib/recap/{deploy.rb → tasks/deploy.rb} +30 -17
- data/lib/recap/tasks/env.rb +111 -0
- data/lib/recap/{foreman.rb → tasks/foreman.rb} +20 -12
- data/lib/recap/{preflight.rb → tasks/preflight.rb} +13 -11
- data/lib/recap/tasks/rails.rb +42 -0
- data/lib/recap/tasks.rb +16 -0
- data/lib/recap/version.rb +1 -1
- data/lib/recap.rb +119 -10
- data/recap.gemspec +3 -2
- data/spec/models/capistrano_extensions_spec.rb +41 -0
- data/spec/models/cli_spec.rb +25 -0
- data/spec/models/environment_spec.rb +14 -14
- data/spec/models/shell_command_spec.rb +55 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/tasks/bootstrap_spec.rb +9 -13
- data/spec/tasks/bundler_spec.rb +39 -7
- data/spec/tasks/deploy_spec.rb +42 -26
- data/spec/tasks/env_spec.rb +81 -5
- data/spec/tasks/foreman_spec.rb +10 -5
- data/spec/tasks/rails_spec.rb +80 -0
- metadata +65 -57
- data/doc/index.html +0 -235
- data/doc/lib/recap/bootstrap.html +0 -42
- data/doc/lib/recap/bundler.html +0 -168
- data/doc/lib/recap/capistrano_extensions.html +0 -208
- data/doc/lib/recap/cli.html +0 -42
- data/doc/lib/recap/compatibility.html +0 -73
- data/doc/lib/recap/deploy.html +0 -328
- data/doc/lib/recap/env.html +0 -108
- data/doc/lib/recap/foreman.html +0 -42
- data/doc/lib/recap/namespace.html +0 -42
- data/doc/lib/recap/preflight.html +0 -163
- data/doc/lib/recap/rails.html +0 -42
- data/doc/lib/recap/version.html +0 -42
- data/doc/lib/recap.html +0 -42
- data/index.rb +0 -62
- data/lib/recap/bootstrap.rb +0 -47
- data/lib/recap/capistrano_extensions.rb +0 -74
- data/lib/recap/cli.rb +0 -32
- data/lib/recap/deploy/templates/Capfile.erb +0 -6
- data/lib/recap/env.rb +0 -58
- data/lib/recap/environment.rb +0 -54
- data/lib/recap/namespace.rb +0 -37
- data/lib/recap/rails.rb +0 -24
- data/lib/recap/ruby.rb +0 -3
- data/lib/recap/static.rb +0 -1
data/lib/recap.rb
CHANGED
@@ -1,12 +1,121 @@
|
|
1
|
+
# This is the documentation for [recap](https://github.com/freerange/recap), a simple, opinionated
|
2
|
+
# set of capistrano deployment recipes.
|
3
|
+
#
|
4
|
+
# Inspired in part by
|
5
|
+
# [this blog post](https://github.com/blog/470-deployment-script-spring-cleaning), these recipes use
|
6
|
+
# git's strengths to deploy applications in a faster, simpler manner than the standard capistrano
|
7
|
+
# deployment.
|
8
|
+
#
|
9
|
+
# ### Aims and features
|
10
|
+
#
|
11
|
+
# Releases are managed using git. All code is deployed to a single directory, and git tags are
|
12
|
+
# used to manage different released versions. No `releases`, `current` or `shared` directories are
|
13
|
+
# created, avoiding unnecessary sym-linking. For more information on how releases work, see
|
14
|
+
# [recap/tasks/deploy.rb](recap/tasks/deploy.html).
|
15
|
+
#
|
16
|
+
# Deployments do the minimum work possible, using git to determine whether tasks need to run. e.g.
|
17
|
+
# the `bundle:install` task only runs if the app contains a `Gemfile.lock` file and it has changed
|
18
|
+
# since the last deployment. You can see how this works in
|
19
|
+
# [recap/tasks/bundler.rb](recap/tasks/bundler.html).
|
20
|
+
#
|
21
|
+
# Applications have their own user and group, owning all of that application's associated
|
22
|
+
# files and processes. This gives them a dedicated environment, allowing environment variables to
|
23
|
+
# be used for application specific configuration. Tasks such as `env`, `env:set` and `env:edit` make
|
24
|
+
# setting and changing these variables easy. [recap/tasks/env.rb](recap/tasks/env.html) has more
|
25
|
+
# information about using these environment variables.
|
26
|
+
#
|
27
|
+
# Personal accounts are used to deploy to the server, distinct from the application user. The right
|
28
|
+
# to deploy an application is granted simply by adding a user to the application group. Most tasks
|
29
|
+
# are run as the application user using `sudo su...`. To avoid having to enter a password when
|
30
|
+
# running them, these lines can be added to `/etc/sudoers.d/application`
|
31
|
+
# (change `application` to the name of your app).
|
32
|
+
#
|
33
|
+
# <pre>%application ALL=NOPASSWD: /sbin/start application*
|
34
|
+
# %application ALL=NOPASSWD: /sbin/stop application*
|
35
|
+
# %application ALL=NOPASSWD: /sbin/restart application*
|
36
|
+
# %application ALL=NOPASSWD: /bin/su - application*
|
37
|
+
# %application ALL=NOPASSWD: /bin/su application*</pre>
|
38
|
+
#
|
39
|
+
# ### Limitations and Constraints
|
40
|
+
#
|
41
|
+
# Recap has been developed and tested using Ubuntu 11.04. It may work well with
|
42
|
+
# other flavours of unix, but proceed with caution.
|
43
|
+
#
|
44
|
+
# Recap also uses a different file layout than other capistrano-based deployments, so other
|
45
|
+
# recipes may not work well with it. You can improve compatibility with other recipes using
|
46
|
+
# [recap/support/compatibility.rb](recap/support/compatibility.html).
|
47
|
+
#
|
48
|
+
# ### Getting started
|
49
|
+
#
|
50
|
+
# To use recap you'll need a project stored in `git`. You'll also need a server with `git` installed
|
51
|
+
# and if deploying a rails or ruby app, `bundler` and `ruby` too. Finally you need an account on the
|
52
|
+
# server which you can SSH into and which is a sudoer.
|
53
|
+
#
|
54
|
+
# #### Preparing your project
|
55
|
+
#
|
56
|
+
# To get a project ready to deploy with recap, you'll need to install the gem, most likely by adding
|
57
|
+
# an entry like the following to the `Gemfile`, then running `bundle install`.
|
58
|
+
#
|
59
|
+
# <pre>gem 'recap', '~>1.0.0'</pre>
|
60
|
+
#
|
61
|
+
# Once the gem is installed, generate a `Capfile` by running `recap setup` within your project
|
62
|
+
# folder. You can see the supported options with `recap help setup`. The generated `Capfile`
|
63
|
+
# will look something like this:
|
64
|
+
#
|
65
|
+
# <pre>require 'recap/recipes/rails'
|
66
|
+
#
|
67
|
+
# set :application, 'example-app'
|
68
|
+
# set :repository, 'git@example.com:example/example-app.git'
|
69
|
+
#
|
70
|
+
# server 'server.example.com', :app</pre>
|
71
|
+
#
|
72
|
+
# Edit the `Capfile` to point at your deployment server and your project should be ready. `cap -T`
|
73
|
+
# will show all the tasks now available.
|
74
|
+
#
|
75
|
+
# #### Preparing the server
|
76
|
+
#
|
77
|
+
# The next step is setting up the server. Running `cap bootstrap` will check your personal account
|
78
|
+
# on the server is configured correctly, and add an account for your application.
|
79
|
+
#
|
80
|
+
# This application account is dedicated to your app, so you can edit its `.profile` as much as you
|
81
|
+
# need (to add a particular version of `ruby` to the path, for example).
|
82
|
+
# [recap/tasks/env.rb](recap/tasks/env.html) information on how to use the `env:set` and `env:edit`
|
83
|
+
# tasks to set configuration variables.
|
84
|
+
#
|
85
|
+
# #### Preparing the app
|
86
|
+
#
|
87
|
+
# Running `cap deploy:setup` clones your code and sets up everything ready for the first deployment.
|
88
|
+
# Once this has been run, you might want to set up a virtual host entry for nginx or Apache to
|
89
|
+
# point at your app.
|
90
|
+
#
|
91
|
+
# #### Deploying
|
92
|
+
#
|
93
|
+
# Finally running `cap deploy` will deploy your app for the first time. Each time you make a change
|
94
|
+
# you want deployed, commit and push your changes to your `git` repository, and run `cap deploy` to
|
95
|
+
# push those changes to the server.
|
96
|
+
#
|
97
|
+
# ### Further information
|
98
|
+
#
|
99
|
+
# Recap has recipes to deploy static, ruby-based and rails apps which you can find out about in
|
100
|
+
# [recap/recipes](recap/recipes.html).
|
101
|
+
#
|
102
|
+
# For more information about all the capistrano tasks recap provides, see
|
103
|
+
# [recap/tasks](recap/tasks.html).
|
104
|
+
#
|
105
|
+
# ### Versioning and License ###
|
106
|
+
#
|
107
|
+
# recap uses [semantic versioning](http://semver.org/).
|
108
|
+
# The code is available [on github](https://github.com/freerange/recap) and released under the
|
109
|
+
# [MIT License](https://github.com/freerange/recap/blob/master/LICENSE)
|
110
|
+
|
1
111
|
module Recap
|
2
|
-
|
112
|
+
module Support
|
113
|
+
autoload :Compatibility, 'recap/support/compatibility'
|
114
|
+
autoload :Namespace, 'recap/support/namespace'
|
115
|
+
autoload :Environment, 'recap/support/environment'
|
116
|
+
autoload :ShellCommand, 'recap/support/shell_command'
|
117
|
+
autoload :CLI, 'recap/support/cli'
|
118
|
+
end
|
3
119
|
|
4
|
-
autoload :
|
5
|
-
|
6
|
-
autoload :Compatibility, 'recap/compatibility'
|
7
|
-
autoload :Deploy, 'recap/deploy'
|
8
|
-
autoload :Env, 'recap/env'
|
9
|
-
autoload :Environment, 'recap/environment'
|
10
|
-
autoload :Foreman, 'recap/foreman'
|
11
|
-
autoload :Rails, 'recap/rails'
|
12
|
-
end
|
120
|
+
autoload :Version, 'recap/version'
|
121
|
+
end
|
data/recap.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Recap::VERSION
|
8
8
|
s.authors = ["Tom Ward"]
|
9
9
|
s.email = ["tom@popdog.net"]
|
10
|
-
s.homepage = "http://
|
10
|
+
s.homepage = "http://gofreerange.com/recap"
|
11
11
|
s.summary = %q{GIT based deployment recipes for Capistrano}
|
12
12
|
s.description = %q{GIT based deployment recipes for Capistrano}
|
13
13
|
|
@@ -18,8 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.add_dependency('capistrano', '~>2.9.0')
|
20
20
|
s.add_dependency('thor')
|
21
|
+
s.add_dependency('open4')
|
21
22
|
s.add_development_dependency('rake', '~>0.9.2')
|
22
|
-
s.add_development_dependency('rocco', '~>0.
|
23
|
+
s.add_development_dependency('fl-rocco', '~>1.0.0')
|
23
24
|
s.add_development_dependency('rspec', '~>2.7.0')
|
24
25
|
s.add_development_dependency('mocha', '~>0.10.0')
|
25
26
|
s.add_development_dependency('vagrant', '~>0.9.7')
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Recap::Support::CapistranoExtensions do
|
4
|
+
let :config do
|
5
|
+
Capistrano::Configuration.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#edit_file" do
|
9
|
+
before do
|
10
|
+
Tempfile.any_instance.stubs(:path).returns('path/to/tempfile')
|
11
|
+
config.stubs(:as_app)
|
12
|
+
Recap::Support::ShellCommand.stubs(:execute_interactive)
|
13
|
+
config.stubs(:get)
|
14
|
+
config.stubs(:editor).returns("some-editor")
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'downloads the file to a temporary file for editing' do
|
18
|
+
config.expects(:get).with('remote/path/to/file', 'path/to/tempfile')
|
19
|
+
File.stubs(:read).with('path/to/tempfile')
|
20
|
+
config.edit_file('remote/path/to/file')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'opens the editor using `execute_interactive` so that Vi works' do
|
24
|
+
config.stubs(:editor).returns('vi')
|
25
|
+
File.stubs(:read).with('path/to/tempfile')
|
26
|
+
Recap::Support::ShellCommand.expects(:execute_interactive).with('vi path/to/tempfile')
|
27
|
+
config.edit_file('remote/path/to/file')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns the locally edited file contents' do
|
31
|
+
File.expects(:read).with('path/to/tempfile').returns('edited contents')
|
32
|
+
config.edit_file('remote/path/to/file').should eql('edited contents')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'fails if no EDITOR is set' do
|
36
|
+
config.stubs(:editor).returns(nil)
|
37
|
+
config.expects(:abort).with(regexp_matches(/To edit a remote file, either the EDITOR or DEPLOY_EDITOR environment variables must be set/))
|
38
|
+
config.edit_file('remote/path/to/file')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Recap::Support::CLI do
|
4
|
+
subject { Recap::Support::CLI.new }
|
5
|
+
|
6
|
+
describe "#setup" do
|
7
|
+
it 'determines the git repository URL' do
|
8
|
+
Recap::Support::ShellCommand.stubs(:execute).with('git remote -v').returns(%{
|
9
|
+
origin git@github.com:freerange/recap.git (fetch)
|
10
|
+
origin git@github.com:freerange/recap.git (push)
|
11
|
+
})
|
12
|
+
subject.stubs(:template)
|
13
|
+
subject.setup
|
14
|
+
subject.repository.should eql('git@github.com:freerange/recap.git')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'handles exception when no git repository present and uses <unkown>' do
|
18
|
+
Recap::Support::ShellCommand.stubs(:execute).with('git remote -v').raises
|
19
|
+
subject.stubs(:template)
|
20
|
+
subject.expects(:warn)
|
21
|
+
lambda { subject.setup }.should_not raise_error
|
22
|
+
subject.repository.should eql('<unknown>')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,29 +1,29 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Recap::Environment do
|
3
|
+
describe Recap::Support::Environment do
|
4
4
|
describe '#empty?' do
|
5
5
|
it 'returns true if no variables set' do
|
6
|
-
Recap::Environment.new.empty?.should be_true
|
6
|
+
Recap::Support::Environment.new.empty?.should be_true
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'returns false if no variables set' do
|
10
|
-
Recap::Environment.new('FIRST' => 'One').empty?.should be_false
|
10
|
+
Recap::Support::Environment.new('FIRST' => 'One').empty?.should be_false
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
describe '#include?(key)' do
|
15
15
|
it 'returns true if variables set' do
|
16
|
-
Recap::Environment.new('FIRST' => 'One').include?('FIRST').should be_true
|
16
|
+
Recap::Support::Environment.new('FIRST' => 'One').include?('FIRST').should be_true
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'returns false if variable has not been set' do
|
20
|
-
Recap::Environment.new('DIFFERENT' => 'One').include?('FIRST').should be_false
|
20
|
+
Recap::Support::Environment.new('DIFFERENT' => 'One').include?('FIRST').should be_false
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
describe '#get(name)' do
|
25
25
|
subject do
|
26
|
-
Recap::Environment.new('FIRST' => 'One')
|
26
|
+
Recap::Support::Environment.new('FIRST' => 'One')
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'returns value if variable set' do
|
@@ -37,7 +37,7 @@ describe Recap::Environment do
|
|
37
37
|
|
38
38
|
describe '#set(name, value)' do
|
39
39
|
subject do
|
40
|
-
Recap::Environment.new('FIRST' => 'One')
|
40
|
+
Recap::Support::Environment.new('FIRST' => 'One')
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'sets variable value' do
|
@@ -60,7 +60,7 @@ describe Recap::Environment do
|
|
60
60
|
|
61
61
|
describe '#each' do
|
62
62
|
subject do
|
63
|
-
Recap::Environment.new('FIRST' => 'One', 'SECOND' => 'Two', 'THIRD' => 'Three', 'FOURTH' => 'Four')
|
63
|
+
Recap::Support::Environment.new('FIRST' => 'One', 'SECOND' => 'Two', 'THIRD' => 'Three', 'FOURTH' => 'Four')
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'yields each variable and value in turn (ordered alphabetically)' do
|
@@ -74,7 +74,7 @@ describe Recap::Environment do
|
|
74
74
|
|
75
75
|
describe '#merge(variables)' do
|
76
76
|
subject do
|
77
|
-
Recap::Environment.new('FIRST' => 'One')
|
77
|
+
Recap::Support::Environment.new('FIRST' => 'One')
|
78
78
|
end
|
79
79
|
|
80
80
|
it 'sets each variable value' do
|
@@ -96,7 +96,7 @@ describe Recap::Environment do
|
|
96
96
|
|
97
97
|
describe '#to_s' do
|
98
98
|
subject do
|
99
|
-
Recap::Environment.new('FIRST' => 'One', 'SECOND' => 'Two', 'THIRD' => nil, 'FOURTH' => 'Four').to_s
|
99
|
+
Recap::Support::Environment.new('FIRST' => 'One', 'SECOND' => 'Two', 'THIRD' => nil, 'FOURTH' => 'Four').to_s
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'declares each variable on its own line' do
|
@@ -117,26 +117,26 @@ describe Recap::Environment do
|
|
117
117
|
|
118
118
|
describe '.from_string(declarations)' do
|
119
119
|
it 'builds instance using string representation' do
|
120
|
-
instance = Recap::Environment.from_string("FIRST=One\nSECOND=Two\n")
|
120
|
+
instance = Recap::Support::Environment.from_string("FIRST=One\nSECOND=Two\n")
|
121
121
|
instance.get('FIRST').should eql('One')
|
122
122
|
instance.get('SECOND').should eql('Two')
|
123
123
|
end
|
124
124
|
|
125
125
|
it 'handles variables with numbers and underscores in their names' do
|
126
|
-
instance = Recap::Environment.from_string("THIS_1=One\nThose_2=Two\n")
|
126
|
+
instance = Recap::Support::Environment.from_string("THIS_1=One\nThose_2=Two\n")
|
127
127
|
instance.get('THIS_1').should eql('One')
|
128
128
|
instance.get('Those_2').should eql('Two')
|
129
129
|
end
|
130
130
|
|
131
131
|
it 'gracefully ignores missing newline at end of string' do
|
132
|
-
instance = Recap::Environment.from_string("FIRST=One\nSECOND=Two")
|
132
|
+
instance = Recap::Support::Environment.from_string("FIRST=One\nSECOND=Two")
|
133
133
|
instance.get('FIRST').should eql('One')
|
134
134
|
instance.get('SECOND').should eql('Two')
|
135
135
|
end
|
136
136
|
|
137
137
|
it 'acts as the inverse of #to_s' do
|
138
138
|
string = "FIRST=One\nSECOND=Two\nTHIRD=three\n"
|
139
|
-
excercised = Recap::Environment.from_string(Recap::Environment.from_string(string).to_s).to_s
|
139
|
+
excercised = Recap::Support::Environment.from_string(Recap::Support::Environment.from_string(string).to_s).to_s
|
140
140
|
excercised.should eql(string)
|
141
141
|
end
|
142
142
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Recap::Support::ShellCommand do
|
4
|
+
subject { Recap::Support::ShellCommand }
|
5
|
+
|
6
|
+
it 'returns stdout output if execution succeeds' do
|
7
|
+
subject.execute("echo 'foo'").should eql("foo\n")
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns stdout output from last command if execution of multiple commands succeeds' do
|
11
|
+
subject.execute("echo 'foo'", "echo 'bar'").should eql("bar\n")
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'does not raise error if execution succeeds' do
|
15
|
+
lambda {
|
16
|
+
subject.execute("true")
|
17
|
+
}.should_not raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not raise error if execution of multiple commands succeeds' do
|
21
|
+
lambda {
|
22
|
+
subject.execute("true", "true")
|
23
|
+
}.should_not raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises error if execution fails' do
|
27
|
+
lambda {
|
28
|
+
subject.execute("false")
|
29
|
+
}.should raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises error if execution of any command fails' do
|
33
|
+
lambda {
|
34
|
+
subject.execute("true", "false", "true")
|
35
|
+
}.should raise_error
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'includes exist status in error message if execution fails' do
|
39
|
+
lambda {
|
40
|
+
subject.execute("false")
|
41
|
+
}.should raise_error(RuntimeError, %r{Command:\sfalse$})
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'includes exist status in error message if execution fails' do
|
45
|
+
lambda {
|
46
|
+
subject.execute("false")
|
47
|
+
}.should raise_error(RuntimeError, %r{Status:\s+1$})
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'includes stderr output in error message if execution fails' do
|
51
|
+
lambda {
|
52
|
+
subject.execute("echo 'error' 1>&2 && false")
|
53
|
+
}.should raise_error(RuntimeError, %r{Message:\s+error$})
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'recap/bootstrap'
|
2
|
+
require 'recap/tasks/bootstrap'
|
3
3
|
|
4
|
-
describe Recap::Bootstrap do
|
4
|
+
describe Recap::Tasks::Bootstrap do
|
5
5
|
let :config do
|
6
6
|
Capistrano::Configuration.new
|
7
7
|
end
|
@@ -10,25 +10,21 @@ describe Recap::Bootstrap do
|
|
10
10
|
config.bootstrap
|
11
11
|
end
|
12
12
|
|
13
|
+
let :commands do
|
14
|
+
sequence('commands')
|
15
|
+
end
|
16
|
+
|
13
17
|
before do
|
14
|
-
Recap::Bootstrap.load_into(config)
|
18
|
+
Recap::Tasks::Bootstrap.load_into(config)
|
15
19
|
end
|
16
20
|
|
17
21
|
describe 'Tasks' do
|
18
22
|
describe 'bootstrap' do
|
19
23
|
it 'runs bootsrap:application and bootstrap:user tasks' do
|
20
|
-
namespace.expects(:application).in_sequence
|
21
|
-
namespace.expects(:user).in_sequence
|
24
|
+
namespace.expects(:application).in_sequence(commands)
|
25
|
+
namespace.expects(:user).in_sequence(commands)
|
22
26
|
config.find_and_execute_task('bootstrap')
|
23
27
|
end
|
24
28
|
end
|
25
|
-
|
26
|
-
describe 'bootstrap:user' do
|
27
|
-
pending 'Tests not written'
|
28
|
-
end
|
29
|
-
|
30
|
-
describe 'bootstrap:application' do
|
31
|
-
pending 'Tests not written'
|
32
|
-
end
|
33
29
|
end
|
34
30
|
end
|
data/spec/tasks/bundler_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'recap/tasks/bundler'
|
2
3
|
|
3
|
-
describe Recap::Bundler do
|
4
|
+
describe Recap::Tasks::Bundler do
|
4
5
|
let :config do
|
5
6
|
Capistrano::Configuration.new
|
6
7
|
end
|
@@ -15,7 +16,7 @@ describe Recap::Bundler do
|
|
15
16
|
|
16
17
|
before do
|
17
18
|
config.set :deploy_to, deploy_to
|
18
|
-
Recap::Bundler.load_into(config)
|
19
|
+
Recap::Tasks::Bundler.load_into(config)
|
19
20
|
end
|
20
21
|
|
21
22
|
describe 'Settings' do
|
@@ -39,8 +40,8 @@ describe Recap::Bundler do
|
|
39
40
|
end
|
40
41
|
|
41
42
|
describe '#bundle_without' do
|
42
|
-
it 'defaults to development
|
43
|
-
config.bundle_without.should eql("development test
|
43
|
+
it 'defaults to development and test groups' do
|
44
|
+
config.bundle_without.should eql("development test")
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
@@ -94,9 +95,9 @@ describe Recap::Bundler do
|
|
94
95
|
it 'aborts with warning if Gemfile exists but Gemfile.lock doesn\'t' do
|
95
96
|
namespace.stubs(:deployed_file_exists?).with(config.bundle_gemfile).returns(true)
|
96
97
|
namespace.stubs(:deployed_file_exists?).with(config.bundle_gemfile_lock).returns(false)
|
97
|
-
|
98
|
-
|
99
|
-
|
98
|
+
expected_message = 'Gemfile found without Gemfile.lock. The Gemfile.lock should be committed to the project repository'
|
99
|
+
namespace.install.expects(:abort).with(expected_message)
|
100
|
+
namespace.find_and_execute_task('bundle:install')
|
100
101
|
end
|
101
102
|
end
|
102
103
|
|
@@ -122,5 +123,36 @@ describe Recap::Bundler do
|
|
122
123
|
config.find_and_execute_task('bundle:install:if_changed')
|
123
124
|
end
|
124
125
|
end
|
126
|
+
|
127
|
+
describe 'bundle:check_installed' do
|
128
|
+
before do
|
129
|
+
config.set(:application_user, 'fred')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'checks to see whether bundler is installed' do
|
133
|
+
namespace.expects(:exit_code_as_app).with('bundle --version', '.').returns("0")
|
134
|
+
config.find_and_execute_task('bundle:check_installed')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'aborts with explanation if bundler command fails' do
|
138
|
+
namespace.stubs(:exit_code_as_app).returns("1")
|
139
|
+
namespace.expects(:abort).with("The application user 'fred' cannot execute `bundle`. Please check you have bundler installed.")
|
140
|
+
config.find_and_execute_task('bundle:check_installed')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'Callbacks' do
|
146
|
+
describe 'bundle:check_installed' do
|
147
|
+
before do
|
148
|
+
Recap::Tasks::Preflight.load_into(config)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'runs after `preflight:check`' do
|
152
|
+
config.stubs(:find_and_execute_task)
|
153
|
+
config.expects(:find_and_execute_task).with('bundle:check_installed')
|
154
|
+
config.trigger :after, config.find_task('preflight:check')
|
155
|
+
end
|
156
|
+
end
|
125
157
|
end
|
126
158
|
end
|
data/spec/tasks/deploy_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'recap/tasks/deploy'
|
2
3
|
|
3
|
-
describe Recap::Deploy do
|
4
|
+
describe Recap::Tasks::Deploy do
|
4
5
|
let :config do
|
5
6
|
Capistrano::Configuration.new
|
6
7
|
end
|
@@ -9,8 +10,12 @@ describe Recap::Deploy do
|
|
9
10
|
config.deploy
|
10
11
|
end
|
11
12
|
|
13
|
+
let :commands do
|
14
|
+
sequence('commands')
|
15
|
+
end
|
16
|
+
|
12
17
|
before do
|
13
|
-
Recap::Deploy.load_into(config)
|
18
|
+
Recap::Tasks::Deploy.load_into(config)
|
14
19
|
end
|
15
20
|
|
16
21
|
it 'configures capistrano to use ssh key forwarding' do
|
@@ -24,17 +29,15 @@ describe Recap::Deploy do
|
|
24
29
|
describe 'Settings' do
|
25
30
|
describe '#application' do
|
26
31
|
it 'exits if accessed before being set' do
|
27
|
-
|
28
|
-
|
29
|
-
end.should raise_error(SystemExit)
|
32
|
+
namespace.expects(:abort).with(regexp_matches(/You must set the name of your application in your Capfile/))
|
33
|
+
config.application
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
37
|
describe '#repository' do
|
34
38
|
it 'exits if accessed before being set' do
|
35
|
-
|
36
|
-
|
37
|
-
end.should raise_error(SystemExit)
|
39
|
+
namespace.expects(:abort).with(regexp_matches(/You must set the git respository location in your Capfile/))
|
40
|
+
config.repository
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
@@ -59,10 +62,10 @@ describe Recap::Deploy do
|
|
59
62
|
end
|
60
63
|
|
61
64
|
describe '#deploy_to' do
|
62
|
-
it 'defaults to
|
65
|
+
it 'defaults to an `app` folder within the application user home directory' do
|
63
66
|
config.set :application, 'hare'
|
64
67
|
config.set :application_user, 'rabbitfoot'
|
65
|
-
config.deploy_to.should eql('/home/rabbitfoot/
|
68
|
+
config.deploy_to.should eql('/home/rabbitfoot/app')
|
66
69
|
end
|
67
70
|
end
|
68
71
|
|
@@ -103,7 +106,10 @@ describe Recap::Deploy do
|
|
103
106
|
end
|
104
107
|
|
105
108
|
describe 'deploy:setup' do
|
106
|
-
it 'runs deploy:clone_code
|
109
|
+
it 'runs env:set and deploy:clone_code tasks' do
|
110
|
+
env = stub('env')
|
111
|
+
config.stubs(:env).returns(env)
|
112
|
+
env.expects('set')
|
107
113
|
namespace.expects(:clone_code)
|
108
114
|
config.find_and_execute_task('deploy:setup')
|
109
115
|
end
|
@@ -113,22 +119,33 @@ describe Recap::Deploy do
|
|
113
119
|
namespace.expects(:clone_code).never
|
114
120
|
config.find_and_execute_task('deploy:setup')
|
115
121
|
end
|
122
|
+
|
123
|
+
it 'removes the deploy_to dir if a rollback is triggered' do
|
124
|
+
config.stubs(:env).returns(stub_everything('env'))
|
125
|
+
namespace.stubs(:as_app)
|
126
|
+
namespace.expects(:as_app).with('rm -fr ' + deploy_to)
|
127
|
+
namespace.stubs(:git).raises(RuntimeError)
|
128
|
+
config.find_and_execute_task('deploy:setup') rescue RuntimeError
|
129
|
+
end
|
116
130
|
end
|
117
131
|
|
118
132
|
describe 'deploy:clone_code' do
|
119
133
|
it 'creates deploy_to dir, ensures it\'s group writable, then clones the repository into it' do
|
120
|
-
namespace.expects(:as_app).with('mkdir -p ' + deploy_to, '~').in_sequence
|
121
|
-
namespace.expects(:as_app).with('chmod g+rw ' + deploy_to).in_sequence
|
122
|
-
namespace.expects(:git).with('clone ' + repository + ' .').in_sequence
|
134
|
+
namespace.expects(:as_app).with('mkdir -p ' + deploy_to, '~').in_sequence(commands)
|
135
|
+
namespace.expects(:as_app).with('chmod g+rw ' + deploy_to).in_sequence(commands)
|
136
|
+
namespace.expects(:git).with('clone ' + repository + ' .').in_sequence(commands)
|
123
137
|
config.find_and_execute_task('deploy:clone_code')
|
124
138
|
end
|
125
139
|
end
|
126
140
|
|
127
141
|
describe 'deploy' do
|
128
|
-
it 'runs deploy:update_code, deploy:tag and then deploy:restart tasks' do
|
129
|
-
|
130
|
-
|
131
|
-
|
142
|
+
it 'runs env:set, deploy:update_code, deploy:tag and then deploy:restart tasks' do
|
143
|
+
env = stub('env')
|
144
|
+
config.stubs(:env).returns(env)
|
145
|
+
env.expects('set')
|
146
|
+
namespace.expects(:update_code).in_sequence(commands)
|
147
|
+
namespace.expects(:tag).in_sequence(commands)
|
148
|
+
namespace.expects(:restart).in_sequence(commands)
|
132
149
|
config.find_and_execute_task('deploy')
|
133
150
|
end
|
134
151
|
|
@@ -154,8 +171,8 @@ describe Recap::Deploy do
|
|
154
171
|
describe 'deploy:update_code' do
|
155
172
|
it 'fetches latest changes, then resets to repository branch' do
|
156
173
|
config.set :branch, 'release-branch'
|
157
|
-
namespace.expects(:git).with('fetch').in_sequence
|
158
|
-
namespace.expects(:git).with('reset --hard origin/release-branch').in_sequence
|
174
|
+
namespace.expects(:git).with('fetch').in_sequence(commands)
|
175
|
+
namespace.expects(:git).with('reset --hard origin/release-branch').in_sequence(commands)
|
159
176
|
namespace.find_and_execute_task('deploy:update_code')
|
160
177
|
end
|
161
178
|
end
|
@@ -176,17 +193,16 @@ describe Recap::Deploy do
|
|
176
193
|
it 'deletes latest tag, resets to previous tag and restarts' do
|
177
194
|
config.stubs(:latest_tag).returns('release-2')
|
178
195
|
config.stubs(:latest_tag_from_repository).returns('release-1')
|
179
|
-
namespace.expects(:git).with('tag -d release-2').in_sequence
|
180
|
-
namespace.expects(:git).with('reset --hard release-1').in_sequence
|
181
|
-
namespace.expects(:restart).in_sequence
|
196
|
+
namespace.expects(:git).with('tag -d release-2').in_sequence(commands)
|
197
|
+
namespace.expects(:git).with('reset --hard release-1').in_sequence(commands)
|
198
|
+
namespace.expects(:restart).in_sequence(commands)
|
182
199
|
namespace.find_and_execute_task('deploy:rollback')
|
183
200
|
end
|
184
201
|
|
185
202
|
it 'aborts if no tag has been deployed' do
|
186
203
|
config.stubs(:latest_tag).returns(nil)
|
187
|
-
|
188
|
-
|
189
|
-
end.should raise_error(SystemExit, 'This app is not currently deployed')
|
204
|
+
namespace.rollback.expects(:abort).with('This app is not currently deployed')
|
205
|
+
namespace.find_and_execute_task('deploy:rollback')
|
190
206
|
end
|
191
207
|
end
|
192
208
|
|