capistrano 3.0.0.pre2 → 3.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/README.md +108 -4
- data/lib/capistrano/application.rb +16 -1
- data/lib/capistrano/configuration.rb +12 -17
- data/lib/capistrano/configuration/server.rb +68 -3
- data/lib/capistrano/configuration/servers.rb +71 -28
- data/lib/capistrano/dsl.rb +2 -0
- data/lib/capistrano/dsl/env.rb +7 -4
- data/lib/capistrano/i18n.rb +2 -1
- data/lib/capistrano/tasks/deploy.rake +3 -6
- data/lib/capistrano/templates/Capfile +4 -0
- data/lib/capistrano/version.rb +1 -1
- data/spec/integration/deploy_finalize_spec.rb +34 -0
- data/spec/integration/deploy_finished_spec.rb +36 -0
- data/spec/integration/deploy_started_spec.rb +74 -0
- data/spec/integration/deploy_update_spec.rb +45 -0
- data/spec/integration/dsl_spec.rb +254 -0
- data/spec/integration/installation_spec.rb +76 -0
- data/spec/integration_spec_helper.rb +7 -0
- data/spec/lib/capistrano/application_spec.rb +61 -0
- data/spec/lib/capistrano/configuration/server_spec.rb +91 -0
- data/spec/lib/capistrano/configuration/servers_spec.rb +79 -11
- data/spec/lib/capistrano/configuration_spec.rb +12 -2
- data/spec/lib/capistrano/dsl/env_spec.rb +0 -73
- data/spec/spec_helper.rb +2 -0
- data/spec/support/matchers.rb +5 -0
- data/spec/support/test_app.rb +89 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec0cef7495327f81efcf4e79622e2e27af857334
|
4
|
+
data.tar.gz: 4657cf63f889a01ea11a16f4fb28208297d2ece3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d136e348c49b419e709851111825f264a6d8d94ba8038b8ca13d1fd21ffbe8308acdc49a83db1700700bc1f6f0beb64fad9a3c248bb2c17980a803e5a3dfe619
|
7
|
+
data.tar.gz: 81a2f3f73e250668a807a8f2abfd248dba05247e4c5cab8b73fd971a5691e842193800f7a58f63c43e661d76d74e0a039c19524fcf22a4f6992293d895667cf2
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Capistrano [![Build Status](https://travis-ci.org/capistrano/capistrano.png?branch=v3)](https://travis-ci.org/capistrano/capistrano)
|
2
2
|
|
3
|
+
## Requirements
|
4
|
+
|
5
|
+
* Ruby >= 1.9 (JRuby and C-Ruby/MRI are supported)
|
6
|
+
|
3
7
|
## Installation
|
4
8
|
|
5
9
|
Add this line to your application's Gemfile:
|
@@ -55,7 +59,16 @@ $ cap production deploy --trace
|
|
55
59
|
|
56
60
|
## Tasks
|
57
61
|
|
58
|
-
|
62
|
+
``` ruby
|
63
|
+
server 'example.com', roles: [:web, :app]
|
64
|
+
server 'example.org', roles: [:db, :workers]
|
65
|
+
desc "Report Uptimes"
|
66
|
+
task :uptime do
|
67
|
+
on roles(:all) do |host|
|
68
|
+
info "Host #{host} (#{host.roles.join(', ')}):\t#{capture(:uptime)}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
59
72
|
|
60
73
|
## Before / After
|
61
74
|
|
@@ -78,17 +91,108 @@ after :finishing, :notify do
|
|
78
91
|
end
|
79
92
|
```
|
80
93
|
|
94
|
+
If it makes sense for your use-case (often, that means *generating a file*)
|
95
|
+
the Rake prerequisite mechanism can be used:
|
96
|
+
|
97
|
+
``` ruby
|
98
|
+
desc "Create Important File"
|
99
|
+
file 'important.txt' do |t|
|
100
|
+
sh "touch #{t.name}"
|
101
|
+
end
|
102
|
+
desc "Upload Important File"
|
103
|
+
task :upload => 'important.txt' do |t|
|
104
|
+
on roles(:all) do
|
105
|
+
upload!(t.prerequisites.first, '/tmp')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
The final way to call out to other tasks is to simply `invoke()` them:
|
111
|
+
|
112
|
+
``` ruby
|
113
|
+
task :one do
|
114
|
+
on roles(:all) { info "One" }
|
115
|
+
end
|
116
|
+
task :two do
|
117
|
+
invoke :one
|
118
|
+
on roles(:all) { info "Two" }
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
This method is widely used.
|
123
|
+
|
124
|
+
## Getting User Input
|
125
|
+
|
126
|
+
``` ruby
|
127
|
+
desc "Ask about breakfast"
|
128
|
+
task :breakfast do
|
129
|
+
breakfast = ask(:breakfast, "What would you like your colleagues to you for breakfast?")
|
130
|
+
on roles(:all) do |h|
|
131
|
+
execute "echo \"$(whoami) wants #{breakfast} for breakfast!\" | wall"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
Perfect, who needs telephones.
|
137
|
+
|
81
138
|
## Console
|
82
139
|
|
83
|
-
|
84
|
-
|
140
|
+
**Note:** Here be dragons. The console is very immature, but it's much more
|
141
|
+
cleanly architected than previous incarnations and it'll only get better from
|
142
|
+
here on in.
|
143
|
+
|
144
|
+
Execute arbitrary remote commands, to use this simply add
|
145
|
+
`require 'capistrano/console'` which will add the necessary tasks to your
|
85
146
|
environment:
|
86
147
|
|
87
148
|
``` shell
|
88
149
|
$ cap staging console
|
89
150
|
```
|
90
151
|
|
152
|
+
Then, after setting up the server connections, this is how that might look:
|
153
|
+
|
154
|
+
```
|
155
|
+
$ cap production console
|
156
|
+
capistrano console - enter command to execute on production
|
157
|
+
production> uptime
|
158
|
+
INFO [94db8027] Running /usr/bin/env uptime on leehambley@example.com:22
|
159
|
+
DEBUG [94db8027] Command: /usr/bin/env uptime
|
160
|
+
DEBUG [94db8027] 17:11:17 up 50 days, 22:31, 1 user, load average: 0.02, 0.02, 0.05
|
161
|
+
INFO [94db8027] Finished in 0.435 seconds command successful.
|
162
|
+
production> who
|
163
|
+
INFO [9ce34809] Running /usr/bin/env who on leehambley@example.com:22
|
164
|
+
DEBUG [9ce34809] Command: /usr/bin/env who
|
165
|
+
DEBUG [9ce34809] leehambley pts/0 2013-06-13 17:11 (port-11262.pppoe.wtnet.de)
|
166
|
+
INFO [9ce34809] Finished in 0.420 seconds command successful.
|
167
|
+
```
|
168
|
+
|
169
|
+
## A word about PTYs
|
170
|
+
|
171
|
+
There is a configuration option which asks the backend driver to as the remote host
|
172
|
+
to assign the connection a *pty*. A *pty* is a pseudo-terminal, which in effect means
|
173
|
+
*tell the backend that this is an **interactive** session*. This is normally a bad idea.
|
174
|
+
|
175
|
+
Most of the differences are best explained by [this page](https://github.com/sstephenson/rbenv/wiki/Unix-shell-initialization) from the author of *rbenv*.
|
176
|
+
|
177
|
+
**When Capistrano makes a connection it is a *non-login*, *non-interactive* shell.
|
178
|
+
This was not an accident!**
|
179
|
+
|
180
|
+
It's often used as a band aid to cure issues related to RVM and rbenv not loading login
|
181
|
+
and shell initialisation scripts. In these scenarios RVM and rbenv are the tools at fault,
|
182
|
+
or at least they are being used incorrectly.
|
183
|
+
|
184
|
+
Whilst, especially in the case of language runtimes (Ruby, Node, Python and friends in
|
185
|
+
particular) there is a temptation to run multiple versions in parallel on a single server
|
186
|
+
and to switch between them using environmental variables, this is an anti-pattern, and
|
187
|
+
sympotamtic of bad design (i.e. you're testing a second version of Ruby in production because
|
188
|
+
your company lacks the infrastructure to test this in a staging environment)
|
189
|
+
|
91
190
|
## Configuration
|
92
191
|
|
93
192
|
|
94
|
-
## SSHKit
|
193
|
+
## SSHKit
|
194
|
+
|
195
|
+
[SSHKit][https://github.com/capistrano/sshkit] is the driver for SSH
|
196
|
+
connections behind the scenes in Capistrano, depending how deep you dig, you
|
197
|
+
might run into interfaces that come directly from SSHKit (the configuration is
|
198
|
+
a good example).
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Capistrano
|
2
2
|
class Application < Rake::Application
|
3
|
-
include Rake::DSL
|
4
3
|
|
5
4
|
def initialize
|
6
5
|
super
|
6
|
+
@name = "cap"
|
7
7
|
@rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} << capfile
|
8
8
|
end
|
9
9
|
|
@@ -12,6 +12,11 @@ module Capistrano
|
|
12
12
|
super
|
13
13
|
end
|
14
14
|
|
15
|
+
def sort_options(options)
|
16
|
+
options.push(version)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
15
20
|
def load_rakefile
|
16
21
|
@name = 'cap'
|
17
22
|
super
|
@@ -23,6 +28,16 @@ module Capistrano
|
|
23
28
|
def capfile
|
24
29
|
File.expand_path(File.join(File.dirname(__FILE__),'..','Capfile'))
|
25
30
|
end
|
31
|
+
|
32
|
+
def version
|
33
|
+
['--version', '-V',
|
34
|
+
"Display the program version.",
|
35
|
+
lambda { |value|
|
36
|
+
puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{RAKEVERSION})"
|
37
|
+
exit
|
38
|
+
}
|
39
|
+
]
|
40
|
+
end
|
26
41
|
end
|
27
42
|
|
28
43
|
end
|
@@ -29,35 +29,30 @@ module Capistrano
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def role(name, hosts)
|
33
|
-
servers.add_role(name, hosts)
|
32
|
+
def role(name, hosts, options={})
|
33
|
+
servers.add_role(name, hosts, options)
|
34
34
|
end
|
35
35
|
|
36
|
-
def server(name, properties
|
36
|
+
def server(name, properties={})
|
37
37
|
servers.add_host(name, properties)
|
38
38
|
end
|
39
39
|
|
40
|
-
def roles_for(names
|
41
|
-
servers.
|
42
|
-
if filter = options.delete(:filter) || options.delete(:select)
|
43
|
-
if filter.respond_to?(:call)
|
44
|
-
list.select!(&filter)
|
45
|
-
else
|
46
|
-
list.select! { |s| s.properties.send(filter) }
|
47
|
-
end
|
48
|
-
if list.empty?
|
49
|
-
raise "Your filter #{filter} would remove all matching servers!"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
40
|
+
def roles_for(names)
|
41
|
+
servers.roles_for(names)
|
53
42
|
end
|
54
43
|
|
55
44
|
def primary(role)
|
56
45
|
servers.fetch_primary(role)
|
57
46
|
end
|
58
47
|
|
48
|
+
def backend
|
49
|
+
@backend ||= SSHKit
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_writer :backend
|
53
|
+
|
59
54
|
def configure_backend
|
60
|
-
|
55
|
+
backend.configure do |sshkit|
|
61
56
|
sshkit.format = fetch(:format)
|
62
57
|
sshkit.output_verbosity = fetch(:log_level)
|
63
58
|
sshkit.default_env = fetch(:default_env)
|
@@ -2,9 +2,16 @@ require 'set'
|
|
2
2
|
module Capistrano
|
3
3
|
class Configuration
|
4
4
|
class Server < SSHKit::Host
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :properties, :roles, :fetch, :set
|
7
|
+
|
8
|
+
def add_roles(roles)
|
9
|
+
Array(roles).each { |role| add_role(role) }
|
10
|
+
end
|
11
|
+
alias roles= add_roles
|
5
12
|
|
6
13
|
def add_role(role)
|
7
|
-
roles
|
14
|
+
roles.add role.to_sym
|
8
15
|
end
|
9
16
|
|
10
17
|
def has_role?(role)
|
@@ -15,8 +22,66 @@ module Capistrano
|
|
15
22
|
hostname == Server.new(host).hostname
|
16
23
|
end
|
17
24
|
|
18
|
-
def
|
19
|
-
|
25
|
+
def primary
|
26
|
+
self if fetch(:primary)
|
27
|
+
end
|
28
|
+
|
29
|
+
def with(properties)
|
30
|
+
properties.each { |key, value| add_property(key, value) }
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def properties
|
35
|
+
@properties ||= Properties.new
|
36
|
+
end
|
37
|
+
|
38
|
+
class Properties
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@properties = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(key, value)
|
45
|
+
@properties[key] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch(key)
|
49
|
+
@properties[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
def respond_to?(method)
|
53
|
+
@properties.has_key?(method)
|
54
|
+
end
|
55
|
+
|
56
|
+
def roles
|
57
|
+
@roles ||= Set.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(key, value=nil)
|
61
|
+
if value
|
62
|
+
set(lvalue(key), value)
|
63
|
+
else
|
64
|
+
fetch(key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def lvalue(key)
|
71
|
+
key.to_s.chomp('=').to_sym
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def add_property(key, value)
|
80
|
+
if respond_to?("#{key}=")
|
81
|
+
send("#{key}=", value)
|
82
|
+
else
|
83
|
+
set(key, value)
|
84
|
+
end
|
20
85
|
end
|
21
86
|
|
22
87
|
end
|
@@ -4,34 +4,22 @@ module Capistrano
|
|
4
4
|
class Servers
|
5
5
|
include Enumerable
|
6
6
|
|
7
|
-
def add_host(host, properties
|
8
|
-
|
9
|
-
Array(properties.delete(:roles) || properties.delete("roles")).each do |role|
|
10
|
-
host.add_role(role)
|
11
|
-
end
|
12
|
-
properties.each do |key, value|
|
13
|
-
unless host.properties.respond_to?(key)
|
14
|
-
host.properties.send(:"#{key}=", value)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
servers.add host
|
18
|
-
end
|
7
|
+
def add_host(host, properties={})
|
8
|
+
servers.add server(host).with(properties)
|
19
9
|
end
|
20
10
|
|
21
|
-
def add_role(role, hosts)
|
22
|
-
Array(hosts).each
|
23
|
-
server = find_or_create_server(host)
|
24
|
-
server.add_role(role)
|
25
|
-
servers.add server
|
26
|
-
end
|
11
|
+
def add_role(role, hosts, options={})
|
12
|
+
Array(hosts).each { |host| add_host(host, options.merge(roles: role)) }
|
27
13
|
end
|
28
14
|
|
29
|
-
def
|
30
|
-
|
15
|
+
def roles_for(names)
|
16
|
+
options = extract_options(names)
|
17
|
+
fetch_roles(names, options)
|
31
18
|
end
|
32
19
|
|
33
20
|
def fetch_primary(role)
|
34
|
-
|
21
|
+
hosts = fetch(role)
|
22
|
+
hosts.find(&:primary) || hosts.first
|
35
23
|
end
|
36
24
|
|
37
25
|
def each
|
@@ -40,25 +28,80 @@ module Capistrano
|
|
40
28
|
|
41
29
|
private
|
42
30
|
|
43
|
-
def
|
44
|
-
|
31
|
+
def server(host)
|
32
|
+
if host.is_a? Server
|
33
|
+
host
|
34
|
+
else
|
35
|
+
servers.find { |server| server.matches?(host) } || Server.new(host)
|
36
|
+
end
|
45
37
|
end
|
46
38
|
|
47
|
-
def fetch(
|
48
|
-
servers.find_all { |server| server.has_role?
|
39
|
+
def fetch(role)
|
40
|
+
servers.find_all { |server| server.has_role? role}
|
49
41
|
end
|
50
42
|
|
51
|
-
def
|
43
|
+
def fetch_roles(names, options)
|
52
44
|
if Array(names).map(&:to_sym).include?(:all)
|
53
|
-
servers
|
45
|
+
filter(servers, options)
|
54
46
|
else
|
55
|
-
Array(names).flat_map { |name| fetch name }.uniq
|
47
|
+
role_servers = Array(names).flat_map { |name| fetch name }.uniq
|
48
|
+
filter(role_servers, options)
|
56
49
|
end
|
57
50
|
end
|
58
51
|
|
52
|
+
def filter(servers, options)
|
53
|
+
Filter.new(servers, options).filtered_servers
|
54
|
+
end
|
55
|
+
|
59
56
|
def servers
|
60
57
|
@servers ||= Set.new
|
61
58
|
end
|
59
|
+
|
60
|
+
def extract_options(array)
|
61
|
+
array.last.is_a?(::Hash) ? array.pop : {}
|
62
|
+
end
|
63
|
+
|
64
|
+
class Filter
|
65
|
+
def initialize(servers, options)
|
66
|
+
@servers, @options = servers, options
|
67
|
+
end
|
68
|
+
|
69
|
+
def filtered_servers
|
70
|
+
if servers_with_filter.any?
|
71
|
+
servers_with_filter
|
72
|
+
else
|
73
|
+
fail I18n.t(:filter_removes_all_servers, scope: :capistrano, filter: key )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
attr_reader :options, :servers
|
79
|
+
|
80
|
+
def servers_with_filter
|
81
|
+
@servers_with_filter ||= servers.select(&filter)
|
82
|
+
end
|
83
|
+
|
84
|
+
def key
|
85
|
+
options[:filter] || options[:select]
|
86
|
+
end
|
87
|
+
|
88
|
+
def filter_option
|
89
|
+
key || all
|
90
|
+
end
|
91
|
+
|
92
|
+
def filter
|
93
|
+
if filter_option.respond_to?(:call)
|
94
|
+
filter_option
|
95
|
+
else
|
96
|
+
lambda { |server| server.fetch(filter_option) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def all
|
101
|
+
lambda { |server| :all }
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
62
105
|
end
|
63
106
|
end
|
64
107
|
end
|