capistrano 3.0.1 → 3.1.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.
- 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
|