capistrano 3.0.1 → 3.1.0
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 -2
- data/CHANGELOG.md +36 -3
- data/CONTRIBUTING.md +91 -0
- data/Gemfile +9 -0
- data/README.md +9 -11
- data/capistrano.gemspec +5 -8
- data/features/configuration.feature +15 -0
- data/features/deploy.feature +1 -0
- data/features/deploy_failure.feature +17 -0
- data/features/step_definitions/assertions.rb +19 -0
- data/features/step_definitions/cap_commands.rb +5 -1
- data/features/step_definitions/setup.rb +13 -0
- data/features/support/remote_command_helpers.rb +4 -0
- data/lib/capistrano/application.rb +41 -3
- data/lib/capistrano/configuration.rb +8 -0
- data/lib/capistrano/configuration/server.rb +28 -5
- data/lib/capistrano/configuration/servers.rb +3 -6
- data/lib/capistrano/configuration/servers/host_filter.rb +82 -0
- data/lib/capistrano/dsl.rb +16 -1
- data/lib/capistrano/dsl/env.rb +11 -1
- data/lib/capistrano/dsl/paths.rb +8 -0
- data/lib/capistrano/dsl/stages.rb +8 -1
- data/lib/capistrano/dsl/task_enhancements.rb +13 -2
- data/lib/capistrano/git.rb +35 -0
- data/lib/capistrano/hg.rb +32 -0
- data/lib/capistrano/i18n.rb +6 -2
- data/lib/capistrano/scm.rb +116 -0
- data/lib/capistrano/setup.rb +4 -3
- data/lib/capistrano/tasks/console.rake +9 -1
- data/lib/capistrano/tasks/deploy.rake +17 -15
- data/lib/capistrano/tasks/framework.rake +1 -0
- data/lib/capistrano/tasks/git.rake +16 -10
- data/lib/capistrano/tasks/hg.rake +13 -9
- data/lib/capistrano/templates/Capfile +1 -2
- data/lib/capistrano/templates/deploy.rb.erb +20 -2
- data/lib/capistrano/templates/stage.rb.erb +1 -4
- data/lib/capistrano/version.rb +1 -1
- data/spec/integration/dsl_spec.rb +147 -2
- data/spec/lib/capistrano/application_spec.rb +2 -5
- data/spec/lib/capistrano/configuration/server_spec.rb +40 -4
- data/spec/lib/capistrano/configuration/servers/host_filter_spec.rb +84 -0
- data/spec/lib/capistrano/configuration/servers_spec.rb +35 -0
- data/spec/lib/capistrano/configuration_spec.rb +8 -0
- data/spec/lib/capistrano/dsl_spec.rb +0 -11
- data/spec/lib/capistrano/git_spec.rb +70 -0
- data/spec/lib/capistrano/hg_spec.rb +70 -0
- data/spec/lib/capistrano/scm_spec.rb +104 -0
- data/spec/support/tasks/fail.cap +7 -0
- data/spec/support/tasks/failed.cap +5 -0
- data/spec/support/test_app.rb +33 -3
- metadata +29 -52
- data/spec/lib/capistrano/dsl/env_spec.rb +0 -10
@@ -9,6 +9,10 @@ module Capistrano
|
|
9
9
|
def env
|
10
10
|
@env ||= new
|
11
11
|
end
|
12
|
+
|
13
|
+
def reset!
|
14
|
+
@env = new
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
def ask(key, default=nil)
|
@@ -34,6 +38,10 @@ module Capistrano
|
|
34
38
|
end
|
35
39
|
|
36
40
|
def role(name, hosts, options={})
|
41
|
+
if name == :all
|
42
|
+
raise ArgumentError.new("#{name} reserved name for role. Please choose another name")
|
43
|
+
end
|
44
|
+
|
37
45
|
servers.add_role(name, hosts, options)
|
38
46
|
end
|
39
47
|
|
@@ -5,6 +5,10 @@ module Capistrano
|
|
5
5
|
extend Forwardable
|
6
6
|
def_delegators :properties, :roles, :fetch, :set
|
7
7
|
|
8
|
+
def self.[](host)
|
9
|
+
host.is_a?(Server) ? host : new(host)
|
10
|
+
end
|
11
|
+
|
8
12
|
def add_roles(roles)
|
9
13
|
Array(roles).each { |role| add_role(role) }
|
10
14
|
end
|
@@ -18,12 +22,8 @@ module Capistrano
|
|
18
22
|
roles.include? role.to_sym
|
19
23
|
end
|
20
24
|
|
21
|
-
def matches?(host)
|
22
|
-
hostname == Server.new(host).hostname
|
23
|
-
end
|
24
|
-
|
25
25
|
def select?(options)
|
26
|
-
selector = Selector.
|
26
|
+
selector = Selector.for(options)
|
27
27
|
selector.call(self)
|
28
28
|
end
|
29
29
|
|
@@ -50,6 +50,10 @@ module Capistrano
|
|
50
50
|
roles.to_a
|
51
51
|
end
|
52
52
|
|
53
|
+
def matches?(other)
|
54
|
+
hostname == other.hostname && port == other.port
|
55
|
+
end
|
56
|
+
|
53
57
|
private
|
54
58
|
|
55
59
|
def add_property(key, value)
|
@@ -103,6 +107,14 @@ module Capistrano
|
|
103
107
|
@options = options
|
104
108
|
end
|
105
109
|
|
110
|
+
def self.for(options)
|
111
|
+
if options.has_key?(:exclude)
|
112
|
+
Exclusive
|
113
|
+
else
|
114
|
+
self
|
115
|
+
end.new(options)
|
116
|
+
end
|
117
|
+
|
106
118
|
def callable
|
107
119
|
if key.respond_to?(:call)
|
108
120
|
key
|
@@ -126,6 +138,17 @@ module Capistrano
|
|
126
138
|
->(server) { :all }
|
127
139
|
end
|
128
140
|
|
141
|
+
class Exclusive < Selector
|
142
|
+
|
143
|
+
def key
|
144
|
+
options[:exclude]
|
145
|
+
end
|
146
|
+
|
147
|
+
def call(server)
|
148
|
+
!callable.call(server)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
129
152
|
end
|
130
153
|
|
131
154
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'set'
|
2
2
|
require_relative 'servers/role_filter'
|
3
|
+
require_relative 'servers/host_filter'
|
3
4
|
module Capistrano
|
4
5
|
class Configuration
|
5
6
|
class Servers
|
@@ -30,11 +31,7 @@ module Capistrano
|
|
30
31
|
private
|
31
32
|
|
32
33
|
def server(host)
|
33
|
-
|
34
|
-
host
|
35
|
-
else
|
36
|
-
servers.find { |server| server.matches?(host) } || Server.new(host)
|
37
|
-
end
|
34
|
+
servers.find { |server| server.matches? Server[host] } || Server[host]
|
38
35
|
end
|
39
36
|
|
40
37
|
def fetch(role)
|
@@ -43,7 +40,7 @@ module Capistrano
|
|
43
40
|
|
44
41
|
def fetch_roles(required, options)
|
45
42
|
filter_roles = RoleFilter.for(required, available_roles)
|
46
|
-
select(servers_with_roles(filter_roles), options)
|
43
|
+
HostFilter.for(select(servers_with_roles(filter_roles), options))
|
47
44
|
end
|
48
45
|
|
49
46
|
def servers_with_roles(roles)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class Configuration
|
3
|
+
class Servers
|
4
|
+
class HostFilter
|
5
|
+
|
6
|
+
def initialize(available)
|
7
|
+
@available = available
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.for(available)
|
11
|
+
new(available).hosts
|
12
|
+
end
|
13
|
+
|
14
|
+
def hosts
|
15
|
+
if host_filter.any?
|
16
|
+
@available.select { |server| host_filter.include? server.hostname }
|
17
|
+
else
|
18
|
+
@available
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def filter
|
25
|
+
if host_filter.any?
|
26
|
+
host_filter
|
27
|
+
else
|
28
|
+
@available
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def host_filter
|
33
|
+
env_filter | configuration_filter
|
34
|
+
end
|
35
|
+
|
36
|
+
def configuration_filter
|
37
|
+
ConfigurationFilter.new.hosts
|
38
|
+
end
|
39
|
+
|
40
|
+
def env_filter
|
41
|
+
EnvFilter.new.hosts
|
42
|
+
end
|
43
|
+
|
44
|
+
class ConfigurationFilter
|
45
|
+
|
46
|
+
def hosts
|
47
|
+
if filter
|
48
|
+
Array(filter.fetch(:hosts, []))
|
49
|
+
else
|
50
|
+
[]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def config
|
55
|
+
Configuration.env
|
56
|
+
end
|
57
|
+
|
58
|
+
def filter
|
59
|
+
config.fetch(:filter) || config.fetch(:select)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
class EnvFilter
|
65
|
+
|
66
|
+
def hosts
|
67
|
+
if filter
|
68
|
+
filter.split(',')
|
69
|
+
else
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def filter
|
75
|
+
ENV['HOSTS']
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/capistrano/dsl.rb
CHANGED
@@ -26,9 +26,20 @@ module Capistrano
|
|
26
26
|
execute :sudo, *args
|
27
27
|
end
|
28
28
|
|
29
|
+
def capturing_revisions(&block)
|
30
|
+
set :previous_revision, fetch_revision
|
31
|
+
block.call
|
32
|
+
set :current_revision, fetch_revision
|
33
|
+
end
|
34
|
+
|
29
35
|
def revision_log_message
|
30
36
|
fetch(:revision_log_message,
|
31
|
-
|
37
|
+
t(:revision_log_message,
|
38
|
+
branch: fetch(:branch),
|
39
|
+
user: local_user,
|
40
|
+
sha: fetch(:current_revision),
|
41
|
+
release: release_timestamp)
|
42
|
+
)
|
32
43
|
end
|
33
44
|
|
34
45
|
def rollback_log_message
|
@@ -43,6 +54,10 @@ module Capistrano
|
|
43
54
|
VersionValidator.new(locked_version).verify
|
44
55
|
end
|
45
56
|
|
57
|
+
private
|
58
|
+
def fetch_revision
|
59
|
+
capture("cd #{repo_path} && git rev-parse --short HEAD")
|
60
|
+
end
|
46
61
|
end
|
47
62
|
end
|
48
63
|
self.extend Capistrano::DSL
|
data/lib/capistrano/dsl/env.rb
CHANGED
@@ -40,7 +40,17 @@ module Capistrano
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def roles(*names)
|
43
|
-
env.roles_for(names)
|
43
|
+
env.roles_for(names.flatten)
|
44
|
+
end
|
45
|
+
|
46
|
+
def release_roles(*names)
|
47
|
+
options = { exclude: :no_release }
|
48
|
+
if names.last.is_a? Hash
|
49
|
+
names.last.merge(options)
|
50
|
+
else
|
51
|
+
names << options
|
52
|
+
end
|
53
|
+
roles(*names)
|
44
54
|
end
|
45
55
|
|
46
56
|
def primary(role)
|
data/lib/capistrano/dsl/paths.rb
CHANGED
@@ -27,6 +27,14 @@ module Capistrano
|
|
27
27
|
set(:release_path, releases_path.join(timestamp))
|
28
28
|
end
|
29
29
|
|
30
|
+
def stage_config_path
|
31
|
+
Pathname.new fetch(:stage_config_path, 'config/deploy')
|
32
|
+
end
|
33
|
+
|
34
|
+
def deploy_config_path
|
35
|
+
Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
|
36
|
+
end
|
37
|
+
|
30
38
|
def repo_url
|
31
39
|
require 'cgi'
|
32
40
|
require 'uri'
|
@@ -3,7 +3,14 @@ module Capistrano
|
|
3
3
|
module Stages
|
4
4
|
|
5
5
|
def stages
|
6
|
-
Dir[
|
6
|
+
Dir[stage_definitions].map { |f| File.basename(f, '.rb') }
|
7
|
+
end
|
8
|
+
|
9
|
+
def infer_stages_from_stage_files
|
10
|
+
end
|
11
|
+
|
12
|
+
def stage_definitions
|
13
|
+
stage_config_path.join('*.rb')
|
7
14
|
end
|
8
15
|
|
9
16
|
def stage_set?
|
@@ -6,9 +6,10 @@ module Capistrano
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def after(task, post_task, *args, &block)
|
9
|
-
|
9
|
+
Rake::Task.define_task(post_task, *args, &block) if block_given?
|
10
|
+
post_task = Rake::Task[post_task]
|
10
11
|
Rake::Task[task].enhance do
|
11
|
-
invoke
|
12
|
+
post_task.invoke
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -49,5 +50,15 @@ module Capistrano
|
|
49
50
|
%w{install}
|
50
51
|
end
|
51
52
|
|
53
|
+
def exit_deploy_because_of_exception(ex)
|
54
|
+
warn t(:deploy_failed, ex: ex.inspect)
|
55
|
+
invoke 'deploy:failed'
|
56
|
+
exit(false)
|
57
|
+
end
|
58
|
+
|
59
|
+
def deploying?
|
60
|
+
fetch(:deploying, false)
|
61
|
+
end
|
62
|
+
|
52
63
|
end
|
53
64
|
end
|
data/lib/capistrano/git.rb
CHANGED
@@ -1 +1,36 @@
|
|
1
1
|
load File.expand_path("../tasks/git.rake", __FILE__)
|
2
|
+
|
3
|
+
require 'capistrano/scm'
|
4
|
+
|
5
|
+
class Capistrano::Git < Capistrano::SCM
|
6
|
+
|
7
|
+
# execute git with argument in the context
|
8
|
+
#
|
9
|
+
def git(*args)
|
10
|
+
args.unshift :git
|
11
|
+
context.execute *args
|
12
|
+
end
|
13
|
+
|
14
|
+
# The Capistrano default strategy for git. You should want to use this.
|
15
|
+
module DefaultStrategy
|
16
|
+
def test
|
17
|
+
test! " [ -f #{repo_path}/HEAD ] "
|
18
|
+
end
|
19
|
+
|
20
|
+
def check
|
21
|
+
test! :git, :'ls-remote', repo_url
|
22
|
+
end
|
23
|
+
|
24
|
+
def clone
|
25
|
+
git :clone, '--mirror', repo_url, repo_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def update
|
29
|
+
git :remote, :update
|
30
|
+
end
|
31
|
+
|
32
|
+
def release
|
33
|
+
git :archive, fetch(:branch), '| tar -x -C', release_path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/capistrano/hg.rb
CHANGED
@@ -1 +1,33 @@
|
|
1
1
|
load File.expand_path("../tasks/hg.rake", __FILE__)
|
2
|
+
|
3
|
+
require 'capistrano/scm'
|
4
|
+
|
5
|
+
class Capistrano::Hg < Capistrano::SCM
|
6
|
+
# execute hg in context with arguments
|
7
|
+
def hg(*args)
|
8
|
+
args.unshift(:hg)
|
9
|
+
context.execute *args
|
10
|
+
end
|
11
|
+
|
12
|
+
module DefaultStrategy
|
13
|
+
def test
|
14
|
+
test! " [ -d #{repo_path}/.hg ] "
|
15
|
+
end
|
16
|
+
|
17
|
+
def check
|
18
|
+
hg "id", repo_url
|
19
|
+
end
|
20
|
+
|
21
|
+
def clone
|
22
|
+
hg "clone", "--noupdate", repo_url, repo_path
|
23
|
+
end
|
24
|
+
|
25
|
+
def update
|
26
|
+
hg "pull"
|
27
|
+
end
|
28
|
+
|
29
|
+
def release
|
30
|
+
hg "archive", release_path, "--rev", fetch(:branch)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/capistrano/i18n.rb
CHANGED
@@ -7,7 +7,6 @@ en = {
|
|
7
7
|
start: 'Start',
|
8
8
|
update: 'Update',
|
9
9
|
finalize: 'Finalise',
|
10
|
-
restart: 'Restart',
|
11
10
|
finishing: 'Finishing',
|
12
11
|
finished: 'Finished',
|
13
12
|
stage_not_set: 'Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.',
|
@@ -17,8 +16,9 @@ en = {
|
|
17
16
|
no_old_releases: 'No old releases (keeping newest %{keep_releases}) on %{host}',
|
18
17
|
linked_file_does_not_exist: 'linked file %{file} does not exist on %{host}',
|
19
18
|
mirror_exists: "The repository mirror is at %{at}",
|
20
|
-
revision_log_message: 'Branch %{branch} deployed as release %{release} by %{user}',
|
19
|
+
revision_log_message: 'Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}',
|
21
20
|
rollback_log_message: '%{user} rolled back to release %{release}',
|
21
|
+
deploy_failed: 'The deploy has failed with an error: %{ex}',
|
22
22
|
console: {
|
23
23
|
welcome: 'capistrano console - enter command to execute on %{stage}',
|
24
24
|
bye: 'bye'
|
@@ -32,3 +32,7 @@ en = {
|
|
32
32
|
}
|
33
33
|
|
34
34
|
I18n.backend.store_translations(:en, { capistrano: en })
|
35
|
+
|
36
|
+
if I18n.respond_to?(:enforce_available_locales=)
|
37
|
+
I18n.enforce_available_locales = true
|
38
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Capistrano
|
2
|
+
|
3
|
+
# Base class for SCM strategy providers.
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
#
|
7
|
+
# @attr_reader [Rake] context
|
8
|
+
#
|
9
|
+
# @author Hartog de Mik
|
10
|
+
#
|
11
|
+
class SCM
|
12
|
+
attr_reader :context
|
13
|
+
|
14
|
+
# Provide a wrapper for the SCM that loads a strategy for the user.
|
15
|
+
#
|
16
|
+
# @param [Rake] context The context in which the strategy should run
|
17
|
+
# @param [Module] strategy A module to include into the SCM instance. The
|
18
|
+
# module should provide the abstract methods of Capistrano::SCM
|
19
|
+
#
|
20
|
+
def initialize(context, strategy)
|
21
|
+
@context = context
|
22
|
+
singleton = class << self; self; end
|
23
|
+
singleton.send(:include, strategy)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Call test in context
|
27
|
+
def test!(*args)
|
28
|
+
context.test *args
|
29
|
+
end
|
30
|
+
|
31
|
+
# The repository URL accoriding to the context
|
32
|
+
def repo_url
|
33
|
+
context.repo_url
|
34
|
+
end
|
35
|
+
|
36
|
+
# The repository path accoriding to the context
|
37
|
+
def repo_path
|
38
|
+
context.repo_path
|
39
|
+
end
|
40
|
+
|
41
|
+
# The release path accoriding to the context
|
42
|
+
def release_path
|
43
|
+
context.release_path
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fetch a var from the context
|
47
|
+
# @param [Symbol] variable The variable to fetch
|
48
|
+
# @param [Object] default The default value if not found
|
49
|
+
#
|
50
|
+
def fetch(*args)
|
51
|
+
context.fetch(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @abstract
|
55
|
+
#
|
56
|
+
# Your implementation should check the existance of a cache repository on
|
57
|
+
# the deployment target
|
58
|
+
#
|
59
|
+
# @return [Boolean]
|
60
|
+
#
|
61
|
+
def test
|
62
|
+
raise NotImplementedError.new(
|
63
|
+
"Your SCM strategy module should provide a #test method"
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @abstract
|
68
|
+
#
|
69
|
+
# Your implementation should check if the specified remote-repository is
|
70
|
+
# available.
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
#
|
74
|
+
def check
|
75
|
+
raise NotImplementedError.new(
|
76
|
+
"Your SCM strategy module should provide a #check method"
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @abstract
|
81
|
+
#
|
82
|
+
# Create a (new) clone of the remote-repository on the deployment target
|
83
|
+
#
|
84
|
+
# @return void
|
85
|
+
#
|
86
|
+
def clone
|
87
|
+
raise NotImplementedError.new(
|
88
|
+
"Your SCM strategy module should provide a #clone method"
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @abstract
|
93
|
+
#
|
94
|
+
# Update the clone on the deployment target
|
95
|
+
#
|
96
|
+
# @return void
|
97
|
+
#
|
98
|
+
def update
|
99
|
+
raise NotImplementedError.new(
|
100
|
+
"Your SCM strategy module should provide a #update method"
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @abstract
|
105
|
+
#
|
106
|
+
# Copy the contents of the cache-repository onto the release path
|
107
|
+
#
|
108
|
+
# @return void
|
109
|
+
#
|
110
|
+
def release
|
111
|
+
raise NotImplementedError.new(
|
112
|
+
"Your SCM strategy module should provide a #release method"
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|