recap 0.2.0 → 1.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/.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
|
|