capistrano 3.2.1 → 3.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -3
- data/CHANGELOG.md +92 -2
- data/Gemfile +1 -5
- data/README.md +84 -3
- data/Rakefile +5 -1
- data/capistrano.gemspec +5 -1
- data/features/configuration.feature +9 -7
- data/features/deploy.feature +9 -8
- data/features/step_definitions/assertions.rb +15 -17
- data/features/step_definitions/cap_commands.rb +1 -1
- data/features/step_definitions/setup.rb +11 -7
- data/features/support/env.rb +8 -9
- data/features/support/remote_command_helpers.rb +2 -3
- data/features/support/vagrant_helpers.rb +35 -0
- data/lib/capistrano/all.rb +2 -1
- data/lib/capistrano/application.rb +52 -7
- data/lib/capistrano/configuration.rb +39 -10
- data/lib/capistrano/configuration/filter.rb +56 -0
- data/lib/capistrano/configuration/question.rb +23 -11
- data/lib/capistrano/configuration/server.rb +14 -5
- data/lib/capistrano/configuration/servers.rb +12 -29
- data/lib/capistrano/defaults.rb +11 -9
- data/lib/capistrano/deploy.rb +1 -0
- data/lib/capistrano/dsl.rb +13 -2
- data/lib/capistrano/dsl/env.rb +6 -2
- data/lib/capistrano/dsl/task_enhancements.rb +5 -3
- data/lib/capistrano/git.rb +8 -2
- data/lib/capistrano/hg.rb +7 -1
- data/lib/capistrano/svn.rb +2 -2
- data/lib/capistrano/tasks/deploy.rake +12 -10
- data/lib/capistrano/tasks/git.rake +1 -1
- data/lib/capistrano/tasks/install.rake +17 -14
- data/lib/capistrano/templates/Capfile +6 -4
- data/lib/capistrano/templates/deploy.rb.erb +5 -15
- data/lib/capistrano/upload_task.rb +9 -0
- data/lib/capistrano/version.rb +1 -1
- data/spec/integration/dsl_spec.rb +129 -10
- data/spec/lib/capistrano/application_spec.rb +24 -6
- data/spec/lib/capistrano/configuration/filter_spec.rb +105 -0
- data/spec/lib/capistrano/configuration/question_spec.rb +18 -12
- data/spec/lib/capistrano/configuration/server_spec.rb +19 -19
- data/spec/lib/capistrano/configuration/servers_spec.rb +101 -20
- data/spec/lib/capistrano/configuration_spec.rb +24 -3
- data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +88 -0
- data/spec/lib/capistrano/dsl_spec.rb +2 -13
- data/spec/lib/capistrano/git_spec.rb +15 -4
- data/spec/lib/capistrano/hg_spec.rb +13 -2
- data/spec/lib/capistrano/scm_spec.rb +3 -3
- data/spec/lib/capistrano/svn_spec.rb +11 -1
- data/spec/lib/capistrano/upload_task_spec.rb +19 -0
- data/spec/lib/capistrano/version_validator_spec.rb +4 -4
- data/spec/spec_helper.rb +2 -1
- data/spec/support/Vagrantfile +1 -1
- data/spec/support/test_app.rb +2 -0
- metadata +45 -26
- data/lib/capistrano/configuration/servers/host_filter.rb +0 -82
- data/lib/capistrano/configuration/servers/role_filter.rb +0 -86
- data/spec/lib/capistrano/configuration/servers/host_filter_spec.rb +0 -84
- data/spec/lib/capistrano/configuration/servers/role_filter_spec.rb +0 -140
@@ -6,17 +6,21 @@ Given(/^servers with the roles app and web$/) do
|
|
6
6
|
vagrant_cli_command('up') rescue nil
|
7
7
|
end
|
8
8
|
|
9
|
-
Given(/^a
|
9
|
+
Given(/^a linked file "(.*?)"$/) do |file|
|
10
|
+
# ignoring other linked files
|
11
|
+
TestApp.append_to_deploy_file("set :linked_files, ['#{file}']")
|
10
12
|
end
|
11
13
|
|
12
|
-
Given(/^
|
13
|
-
|
14
|
+
Given(/^file "(.*?)" exists in shared path$/) do |file|
|
15
|
+
file_shared_path = TestApp.shared_path.join(file)
|
16
|
+
run_vagrant_command("mkdir -p #{TestApp.shared_path}")
|
17
|
+
run_vagrant_command("touch #{file_shared_path}")
|
14
18
|
end
|
15
19
|
|
16
|
-
Given(/^
|
17
|
-
|
18
|
-
|
19
|
-
run_vagrant_command("
|
20
|
+
Given(/^file "(.*?)" does not exist in shared path$/) do |file|
|
21
|
+
file_shared_path = TestApp.shared_path.join(file)
|
22
|
+
run_vagrant_command("mkdir -p #{TestApp.shared_path}")
|
23
|
+
run_vagrant_command("touch #{file_shared_path} && rm #{file_shared_path}")
|
20
24
|
end
|
21
25
|
|
22
26
|
Given(/^a custom task to generate a file$/) do
|
data/features/support/env.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
PROJECT_ROOT = File.expand_path('../../../', __FILE__)
|
2
|
+
VAGRANT_ROOT = File.join(PROJECT_ROOT, 'spec/support')
|
3
|
+
VAGRANT_BIN = ENV['VAGRANT_BIN'] || "vagrant"
|
4
|
+
|
5
|
+
at_exit do
|
6
|
+
if ENV['KEEP_RUNNING']
|
7
|
+
VagrantHelpers.run_vagrant_command("rm -rf /home/vagrant/var")
|
8
|
+
end
|
8
9
|
end
|
9
10
|
|
10
|
-
puts vagrant_root.inspect
|
11
|
-
|
12
11
|
require_relative '../../spec/support/test_app'
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module RemoteCommandHelpers
|
2
|
-
|
3
2
|
def test_dir_exists(path)
|
4
3
|
exists?('d', path)
|
5
4
|
end
|
@@ -13,11 +12,11 @@ module RemoteCommandHelpers
|
|
13
12
|
end
|
14
13
|
|
15
14
|
def exists?(type, path)
|
16
|
-
%{[ -#{type} "#{path}" ]
|
15
|
+
%{[ -#{type} "#{path}" ]}
|
17
16
|
end
|
18
17
|
|
19
18
|
def safely_remove_file(path)
|
20
|
-
run_vagrant_command("rm #{test_file}") rescue
|
19
|
+
run_vagrant_command("rm #{test_file}") rescue VagrantHelpers::VagrantSSHCommandError
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module VagrantHelpers
|
2
|
+
extend self
|
3
|
+
|
4
|
+
class VagrantSSHCommandError < RuntimeError; end
|
5
|
+
|
6
|
+
at_exit do
|
7
|
+
if ENV['KEEP_RUNNING']
|
8
|
+
puts "Vagrant vm will be left up because KEEP_RUNNING is set."
|
9
|
+
puts "Rerun without KEEP_RUNNING set to cleanup the vm."
|
10
|
+
else
|
11
|
+
vagrant_cli_command("destroy -f")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def vagrant_cli_command(command)
|
16
|
+
puts "[vagrant] #{command}"
|
17
|
+
Dir.chdir(VAGRANT_ROOT) do
|
18
|
+
`#{VAGRANT_BIN} #{command} 2>&1`.split("\n").each do |line|
|
19
|
+
puts "[vagrant] #{line}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
$?
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_vagrant_command(command)
|
26
|
+
if (status = vagrant_cli_command("ssh -c #{command.inspect}")).success?
|
27
|
+
true
|
28
|
+
else
|
29
|
+
fail VagrantSSHCommandError, status
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
World(VagrantHelpers)
|
data/lib/capistrano/all.rb
CHANGED
@@ -16,10 +16,43 @@ module Capistrano
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def sort_options(options)
|
19
|
-
|
20
|
-
|
19
|
+
not_applicable_to_capistrano = %w(quiet silent verbose)
|
20
|
+
options.reject! do |(switch, *)|
|
21
|
+
switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/
|
22
|
+
end
|
23
|
+
|
24
|
+
super.push(version, dry_run, roles, hostfilter)
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_options
|
28
|
+
options.rakelib = ['rakelib']
|
29
|
+
options.trace_output = $stderr
|
30
|
+
|
31
|
+
OptionParser.new do |opts|
|
32
|
+
opts.banner = "See full documentation at http://capistranorb.com/."
|
33
|
+
opts.separator ""
|
34
|
+
opts.separator "Install capistrano in a project:"
|
35
|
+
opts.separator " bundle exec cap install [STAGES=qa,staging,production,...]"
|
36
|
+
opts.separator ""
|
37
|
+
opts.separator "Show available tasks:"
|
38
|
+
opts.separator " bundle exec cap -T"
|
39
|
+
opts.separator ""
|
40
|
+
opts.separator "Invoke (or simulate invoking) a task:"
|
41
|
+
opts.separator " bundle exec cap [--dry-run] STAGE TASK"
|
42
|
+
opts.separator ""
|
43
|
+
opts.separator "Advanced options:"
|
44
|
+
|
45
|
+
opts.on_tail("-h", "--help", "-H", "Display this help message.") do
|
46
|
+
puts opts
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
|
50
|
+
standard_rake_options.each { |args| opts.on(*args) }
|
51
|
+
opts.environment('RAKEOPT')
|
52
|
+
end.parse!
|
21
53
|
end
|
22
54
|
|
55
|
+
|
23
56
|
def top_level_tasks
|
24
57
|
if tasks_without_stage_dependency.include?(@top_level_tasks.first)
|
25
58
|
@top_level_tasks
|
@@ -28,6 +61,18 @@ module Capistrano
|
|
28
61
|
end
|
29
62
|
end
|
30
63
|
|
64
|
+
def display_error_message(ex)
|
65
|
+
unless options.backtrace
|
66
|
+
if loc = Rake.application.find_rakefile_location
|
67
|
+
whitelist = (@imported.dup << loc[0]).map{|f| File.absolute_path(f, loc[1])}
|
68
|
+
pattern = %r@^(?!#{whitelist.map{|p| Regexp.quote(p)}.join('|')})@
|
69
|
+
Rake.application.options.suppress_backtrace_pattern = pattern
|
70
|
+
end
|
71
|
+
trace "(Backtrace restricted to imported tasks)"
|
72
|
+
end
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
31
76
|
def exit_because_of_exception(ex)
|
32
77
|
if respond_to?(:deploying?) && deploying?
|
33
78
|
exit_deploy_because_of_exception(ex)
|
@@ -42,7 +87,7 @@ module Capistrano
|
|
42
87
|
if options.show_tasks
|
43
88
|
invoke 'load:defaults'
|
44
89
|
set(:stage, '')
|
45
|
-
Dir[deploy_config_path
|
90
|
+
Dir[deploy_config_path].each { |f| add_import f }
|
46
91
|
end
|
47
92
|
|
48
93
|
super
|
@@ -74,18 +119,18 @@ module Capistrano
|
|
74
119
|
|
75
120
|
def roles
|
76
121
|
['--roles ROLES', '-r',
|
77
|
-
"
|
122
|
+
"Run SSH commands only on hosts matching these roles",
|
78
123
|
lambda { |value|
|
79
|
-
Configuration.env.
|
124
|
+
Configuration.env.add_cmdline_filter(:role, value)
|
80
125
|
}
|
81
126
|
]
|
82
127
|
end
|
83
128
|
|
84
129
|
def hostfilter
|
85
130
|
['--hosts HOSTS', '-z',
|
86
|
-
"
|
131
|
+
"Run SSH commands only on matching hosts",
|
87
132
|
lambda { |value|
|
88
|
-
Configuration.env.
|
133
|
+
Configuration.env.add_cmdline_filter(:host, value)
|
89
134
|
}
|
90
135
|
]
|
91
136
|
end
|
@@ -1,22 +1,25 @@
|
|
1
|
+
require_relative 'configuration/filter'
|
1
2
|
require_relative 'configuration/question'
|
2
|
-
require_relative 'configuration/servers'
|
3
3
|
require_relative 'configuration/server'
|
4
|
+
require_relative 'configuration/servers'
|
4
5
|
|
5
6
|
module Capistrano
|
6
7
|
class Configuration
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
9
|
+
def initialize(config = nil)
|
10
|
+
@config ||= config
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
13
|
+
def self.env
|
14
|
+
@env ||= new
|
16
15
|
end
|
17
16
|
|
18
|
-
def
|
19
|
-
|
17
|
+
def self.reset!
|
18
|
+
@env = new
|
19
|
+
end
|
20
|
+
|
21
|
+
def ask(key, default=nil, options={})
|
22
|
+
question = Question.new(key, default, options)
|
20
23
|
set(key, question)
|
21
24
|
end
|
22
25
|
|
@@ -24,6 +27,10 @@ module Capistrano
|
|
24
27
|
config[key] = value
|
25
28
|
end
|
26
29
|
|
30
|
+
def set_if_empty(key, value)
|
31
|
+
config[key] = value unless config.has_key? key
|
32
|
+
end
|
33
|
+
|
27
34
|
def delete(key)
|
28
35
|
config.delete(key)
|
29
36
|
end
|
@@ -84,8 +91,30 @@ module Capistrano
|
|
84
91
|
@timestamp ||= Time.now.utc
|
85
92
|
end
|
86
93
|
|
94
|
+
def setup_filters
|
95
|
+
@filters = cmdline_filters.clone
|
96
|
+
@filters << Filter.new(:role, ENV['ROLES']) if ENV['ROLES']
|
97
|
+
@filters << Filter.new(:host, ENV['HOSTS']) if ENV['HOSTS']
|
98
|
+
fh = fetch_for(:filter,{})
|
99
|
+
@filters << Filter.new(:host, fh[:host]) if fh[:host]
|
100
|
+
@filters << Filter.new(:role, fh[:role]) if fh[:role]
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_cmdline_filter(type, values)
|
104
|
+
cmdline_filters << Filter.new(type, values)
|
105
|
+
end
|
106
|
+
|
107
|
+
def filter list
|
108
|
+
setup_filters if @filters.nil?
|
109
|
+
@filters.reduce(list) { |l,f| f.filter l }
|
110
|
+
end
|
111
|
+
|
87
112
|
private
|
88
113
|
|
114
|
+
def cmdline_filters
|
115
|
+
@cmdline_filters ||= []
|
116
|
+
end
|
117
|
+
|
89
118
|
def servers
|
90
119
|
@servers ||= Servers.new
|
91
120
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'capistrano/configuration'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
class Configuration
|
5
|
+
class Filter
|
6
|
+
def initialize type, values = nil
|
7
|
+
raise "Invalid filter type #{type}" unless [:host,:role].include? type
|
8
|
+
av = Array(values)
|
9
|
+
@mode = case
|
10
|
+
when av.size == 0 then :none
|
11
|
+
when av.include?(:all) then :all
|
12
|
+
else type
|
13
|
+
end
|
14
|
+
@rex = case @mode
|
15
|
+
when :host
|
16
|
+
av.map!{|v| (v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/) ? v.split(',') : v }
|
17
|
+
av.flatten!
|
18
|
+
av.map! do |v|
|
19
|
+
case v
|
20
|
+
when Regexp then v
|
21
|
+
else
|
22
|
+
vs = v.to_s
|
23
|
+
vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
Regexp.union av
|
27
|
+
when :role
|
28
|
+
av.map!{|v| v.is_a?(String) ? v.split(',') : v }
|
29
|
+
av.flatten!
|
30
|
+
av.map! do |v|
|
31
|
+
case v
|
32
|
+
when Regexp then v
|
33
|
+
else
|
34
|
+
vs = v.to_s
|
35
|
+
vs =~ %r{^/(.+)/$} ? Regexp.new($1) : vs
|
36
|
+
end
|
37
|
+
end
|
38
|
+
Regexp.union av
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def filter servers
|
44
|
+
as = Array(servers)
|
45
|
+
case @mode
|
46
|
+
when :none then return []
|
47
|
+
when :all then return servers
|
48
|
+
when :host
|
49
|
+
as.select {|s| @rex.match s.hostname}
|
50
|
+
when :role
|
51
|
+
as.select {|s| s.roles.any? {|r| @rex.match r} }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -2,27 +2,23 @@ module Capistrano
|
|
2
2
|
class Configuration
|
3
3
|
class Question
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(key, default, options = {})
|
6
|
+
@key, @default, @options = key, default, options
|
7
7
|
end
|
8
8
|
|
9
9
|
def call
|
10
10
|
ask_question
|
11
|
-
|
11
|
+
value_or_default
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :key, :default, :options
|
16
16
|
|
17
17
|
def ask_question
|
18
18
|
$stdout.print question
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
env.set(key, value)
|
23
|
-
end
|
24
|
-
|
25
|
-
def value
|
21
|
+
def value_or_default
|
26
22
|
if response.empty?
|
27
23
|
default
|
28
24
|
else
|
@@ -31,12 +27,28 @@ module Capistrano
|
|
31
27
|
end
|
32
28
|
|
33
29
|
def response
|
34
|
-
@response
|
30
|
+
return @response if defined? @response
|
31
|
+
|
32
|
+
@response = (gets || "").chomp
|
35
33
|
end
|
36
|
-
|
34
|
+
|
35
|
+
def gets
|
36
|
+
if echo?
|
37
|
+
$stdin.gets
|
38
|
+
else
|
39
|
+
$stdin.noecho(&:gets).tap{ $stdout.print "\n" }
|
40
|
+
end
|
41
|
+
rescue Errno::EIO
|
42
|
+
# when stdio gets closed
|
43
|
+
end
|
44
|
+
|
37
45
|
def question
|
38
46
|
I18n.t(:question, key: key, default_value: default, scope: :capistrano)
|
39
47
|
end
|
48
|
+
|
49
|
+
def echo?
|
50
|
+
(options || {}).fetch(:echo, true)
|
51
|
+
end
|
40
52
|
end
|
41
53
|
end
|
42
54
|
end
|
@@ -11,11 +11,13 @@ module Capistrano
|
|
11
11
|
|
12
12
|
def add_roles(roles)
|
13
13
|
Array(roles).each { |role| add_role(role) }
|
14
|
+
self
|
14
15
|
end
|
15
16
|
alias roles= add_roles
|
16
17
|
|
17
18
|
def add_role(role)
|
18
19
|
roles.add role.to_sym
|
20
|
+
self
|
19
21
|
end
|
20
22
|
|
21
23
|
def has_role?(role)
|
@@ -40,11 +42,9 @@ module Capistrano
|
|
40
42
|
@properties ||= Properties.new
|
41
43
|
end
|
42
44
|
|
43
|
-
def
|
44
|
-
@netssh_options ||=
|
45
|
+
def netssh_options
|
46
|
+
@netssh_options ||= super.merge( fetch(:ssh_options) || {} )
|
45
47
|
end
|
46
|
-
alias_method :netssh_options_without_options, :netssh_options
|
47
|
-
alias_method :netssh_options, :netssh_options_with_options
|
48
48
|
|
49
49
|
def roles_array
|
50
50
|
roles.to_a
|
@@ -71,7 +71,16 @@ module Capistrano
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def set(key, value)
|
74
|
-
@properties[key]
|
74
|
+
pval = @properties[key]
|
75
|
+
if pval.is_a? Hash and value.is_a? Hash
|
76
|
+
pval.merge!(value)
|
77
|
+
elsif pval.is_a? Set and value.is_a? Set
|
78
|
+
pval.merge(value)
|
79
|
+
elsif pval.is_a? Array and value.is_a? Array
|
80
|
+
pval.concat value
|
81
|
+
else
|
82
|
+
@properties[key] = value
|
83
|
+
end
|
75
84
|
end
|
76
85
|
|
77
86
|
def fetch(key)
|