capistrano 3.4.0 → 3.17.1

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.
Files changed (138) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +129 -0
  3. data/.github/issue_template.md +19 -0
  4. data/.github/pull_request_template.md +22 -0
  5. data/.github/release-drafter.yml +17 -0
  6. data/.github/workflows/push.yml +12 -0
  7. data/.gitignore +8 -5
  8. data/.rubocop.yml +62 -0
  9. data/CHANGELOG.md +1 -307
  10. data/CONTRIBUTING.md +63 -93
  11. data/DEVELOPMENT.md +127 -0
  12. data/Dangerfile +1 -0
  13. data/Gemfile +40 -3
  14. data/LICENSE.txt +1 -1
  15. data/README.md +127 -44
  16. data/RELEASING.md +17 -0
  17. data/Rakefile +13 -2
  18. data/UPGRADING-3.7.md +86 -0
  19. data/bin/cap +1 -1
  20. data/capistrano.gemspec +21 -24
  21. data/features/deploy.feature +35 -1
  22. data/features/doctor.feature +11 -0
  23. data/features/installation.feature +8 -3
  24. data/features/stage_failure.feature +9 -0
  25. data/features/step_definitions/assertions.rb +51 -18
  26. data/features/step_definitions/cap_commands.rb +9 -0
  27. data/features/step_definitions/setup.rb +53 -9
  28. data/features/subdirectory.feature +9 -0
  29. data/features/support/env.rb +5 -5
  30. data/features/support/remote_command_helpers.rb +12 -6
  31. data/features/support/vagrant_helpers.rb +17 -11
  32. data/lib/Capfile +1 -1
  33. data/lib/capistrano/all.rb +10 -10
  34. data/lib/capistrano/application.rb +47 -34
  35. data/lib/capistrano/configuration/empty_filter.rb +9 -0
  36. data/lib/capistrano/configuration/filter.rb +17 -47
  37. data/lib/capistrano/configuration/host_filter.rb +29 -0
  38. data/lib/capistrano/configuration/null_filter.rb +9 -0
  39. data/lib/capistrano/configuration/plugin_installer.rb +51 -0
  40. data/lib/capistrano/configuration/question.rb +31 -9
  41. data/lib/capistrano/configuration/role_filter.rb +29 -0
  42. data/lib/capistrano/configuration/scm_resolver.rb +149 -0
  43. data/lib/capistrano/configuration/server.rb +29 -23
  44. data/lib/capistrano/configuration/servers.rb +21 -14
  45. data/lib/capistrano/configuration/validated_variables.rb +110 -0
  46. data/lib/capistrano/configuration/variables.rb +112 -0
  47. data/lib/capistrano/configuration.rb +91 -44
  48. data/lib/capistrano/defaults.rb +26 -4
  49. data/lib/capistrano/deploy.rb +1 -1
  50. data/lib/capistrano/doctor/environment_doctor.rb +19 -0
  51. data/lib/capistrano/doctor/gems_doctor.rb +45 -0
  52. data/lib/capistrano/doctor/output_helpers.rb +79 -0
  53. data/lib/capistrano/doctor/servers_doctor.rb +105 -0
  54. data/lib/capistrano/doctor/variables_doctor.rb +74 -0
  55. data/lib/capistrano/doctor.rb +6 -0
  56. data/lib/capistrano/dotfile.rb +1 -2
  57. data/lib/capistrano/dsl/env.rb +9 -47
  58. data/lib/capistrano/dsl/paths.rb +11 -25
  59. data/lib/capistrano/dsl/stages.rb +14 -2
  60. data/lib/capistrano/dsl/task_enhancements.rb +7 -12
  61. data/lib/capistrano/dsl.rb +47 -16
  62. data/lib/capistrano/framework.rb +1 -1
  63. data/lib/capistrano/i18n.rb +32 -24
  64. data/lib/capistrano/immutable_task.rb +30 -0
  65. data/lib/capistrano/install.rb +1 -1
  66. data/lib/capistrano/plugin.rb +95 -0
  67. data/lib/capistrano/proc_helpers.rb +13 -0
  68. data/lib/capistrano/scm/git.rb +100 -0
  69. data/lib/capistrano/scm/hg.rb +55 -0
  70. data/lib/capistrano/scm/plugin.rb +13 -0
  71. data/lib/capistrano/scm/svn.rb +56 -0
  72. data/lib/capistrano/scm/tasks/git.rake +73 -0
  73. data/lib/capistrano/scm/tasks/hg.rake +53 -0
  74. data/lib/capistrano/scm/tasks/svn.rake +53 -0
  75. data/lib/capistrano/scm.rb +7 -20
  76. data/lib/capistrano/setup.rb +20 -6
  77. data/lib/capistrano/tasks/console.rake +4 -8
  78. data/lib/capistrano/tasks/deploy.rake +105 -73
  79. data/lib/capistrano/tasks/doctor.rake +24 -0
  80. data/lib/capistrano/tasks/framework.rake +13 -14
  81. data/lib/capistrano/tasks/install.rake +14 -15
  82. data/lib/capistrano/templates/Capfile +21 -10
  83. data/lib/capistrano/templates/deploy.rb.erb +17 -26
  84. data/lib/capistrano/templates/stage.rb.erb +9 -9
  85. data/lib/capistrano/upload_task.rb +1 -1
  86. data/lib/capistrano/version.rb +1 -1
  87. data/lib/capistrano/version_validator.rb +5 -10
  88. data/spec/integration/dsl_spec.rb +289 -240
  89. data/spec/integration_spec_helper.rb +3 -5
  90. data/spec/lib/capistrano/application_spec.rb +23 -39
  91. data/spec/lib/capistrano/configuration/empty_filter_spec.rb +17 -0
  92. data/spec/lib/capistrano/configuration/filter_spec.rb +83 -85
  93. data/spec/lib/capistrano/configuration/host_filter_spec.rb +71 -0
  94. data/spec/lib/capistrano/configuration/null_filter_spec.rb +17 -0
  95. data/spec/lib/capistrano/configuration/plugin_installer_spec.rb +98 -0
  96. data/spec/lib/capistrano/configuration/question_spec.rb +58 -26
  97. data/spec/lib/capistrano/configuration/role_filter_spec.rb +80 -0
  98. data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +55 -0
  99. data/spec/lib/capistrano/configuration/server_spec.rb +106 -113
  100. data/spec/lib/capistrano/configuration/servers_spec.rb +129 -145
  101. data/spec/lib/capistrano/configuration_spec.rb +224 -63
  102. data/spec/lib/capistrano/doctor/environment_doctor_spec.rb +44 -0
  103. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +67 -0
  104. data/spec/lib/capistrano/doctor/output_helpers_spec.rb +47 -0
  105. data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
  106. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +89 -0
  107. data/spec/lib/capistrano/dsl/paths_spec.rb +97 -59
  108. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +57 -37
  109. data/spec/lib/capistrano/dsl_spec.rb +84 -11
  110. data/spec/lib/capistrano/immutable_task_spec.rb +31 -0
  111. data/spec/lib/capistrano/plugin_spec.rb +84 -0
  112. data/spec/lib/capistrano/scm/git_spec.rb +184 -0
  113. data/spec/lib/capistrano/scm/hg_spec.rb +109 -0
  114. data/spec/lib/capistrano/scm/svn_spec.rb +137 -0
  115. data/spec/lib/capistrano/scm_spec.rb +7 -8
  116. data/spec/lib/capistrano/upload_task_spec.rb +7 -7
  117. data/spec/lib/capistrano/version_validator_spec.rb +61 -46
  118. data/spec/lib/capistrano_spec.rb +2 -3
  119. data/spec/spec_helper.rb +21 -8
  120. data/spec/support/Vagrantfile +9 -10
  121. data/spec/support/tasks/database.rake +3 -3
  122. data/spec/support/tasks/fail.rake +4 -3
  123. data/spec/support/tasks/failed.rake +2 -2
  124. data/spec/support/tasks/plugin.rake +6 -0
  125. data/spec/support/tasks/root.rake +4 -4
  126. data/spec/support/test_app.rb +64 -39
  127. metadata +100 -55
  128. data/.travis.yml +0 -13
  129. data/features/remote_file_task.feature +0 -14
  130. data/lib/capistrano/git.rb +0 -46
  131. data/lib/capistrano/hg.rb +0 -43
  132. data/lib/capistrano/svn.rb +0 -38
  133. data/lib/capistrano/tasks/git.rake +0 -81
  134. data/lib/capistrano/tasks/hg.rake +0 -52
  135. data/lib/capistrano/tasks/svn.rake +0 -52
  136. data/spec/lib/capistrano/git_spec.rb +0 -81
  137. data/spec/lib/capistrano/hg_spec.rb +0 -81
  138. data/spec/lib/capistrano/svn_spec.rb +0 -79
@@ -0,0 +1,19 @@
1
+ require "capistrano/doctor/output_helpers"
2
+
3
+ module Capistrano
4
+ module Doctor
5
+ class EnvironmentDoctor
6
+ include Capistrano::Doctor::OutputHelpers
7
+
8
+ def call
9
+ title("Environment")
10
+ puts <<-OUT.gsub(/^\s+/, "")
11
+ Ruby #{RUBY_DESCRIPTION}
12
+ Rubygems #{Gem::VERSION}
13
+ Bundler #{defined?(Bundler::VERSION) ? Bundler::VERSION : 'N/A'}
14
+ Command #{$PROGRAM_NAME} #{ARGV.join(' ')}
15
+ OUT
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ require "capistrano/doctor/output_helpers"
2
+
3
+ module Capistrano
4
+ module Doctor
5
+ # Prints table of all Capistrano-related gems and their version numbers. If
6
+ # there is a newer version of a gem available, call attention to it.
7
+ class GemsDoctor
8
+ include Capistrano::Doctor::OutputHelpers
9
+
10
+ def call
11
+ title("Gems")
12
+ table(all_gem_names) do |gem, row|
13
+ row.yellow if update_available?(gem)
14
+ row << gem
15
+ row << installed_gem_version(gem)
16
+ row << "(update available)" if update_available?(gem)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def installed_gem_version(gem_name)
23
+ Gem.loaded_specs[gem_name].version
24
+ end
25
+
26
+ def update_available?(gem_name)
27
+ latest = Gem.latest_version_for(gem_name)
28
+ return false if latest.nil?
29
+ latest > installed_gem_version(gem_name)
30
+ end
31
+
32
+ def all_gem_names
33
+ core_gem_names + plugin_gem_names
34
+ end
35
+
36
+ def core_gem_names
37
+ %w(capistrano airbrussh rake sshkit net-ssh) & Gem.loaded_specs.keys
38
+ end
39
+
40
+ def plugin_gem_names
41
+ (Gem.loaded_specs.keys - ["capistrano"]).grep(/capistrano/).sort
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,79 @@
1
+ module Capistrano
2
+ module Doctor
3
+ # Helper methods for pretty-printing doctor output to stdout. All output
4
+ # (other than `title`) is indented by four spaces to facilitate copying and
5
+ # pasting this output into e.g. GitHub or Stack Overflow to achieve code
6
+ # formatting.
7
+ module OutputHelpers
8
+ class Row
9
+ attr_reader :color
10
+ attr_reader :values
11
+
12
+ def initialize
13
+ @values = []
14
+ end
15
+
16
+ def <<(value)
17
+ values << value
18
+ end
19
+
20
+ def yellow
21
+ @color = :yellow
22
+ end
23
+ end
24
+
25
+ # Prints a table for a given array of records. For each record, the block
26
+ # is yielded two arguments: the record and a Row object. To print values
27
+ # for that record, add values using `row << "some value"`. A row can
28
+ # optionally be highlighted in yellow using `row.yellow`.
29
+ def table(records, &block)
30
+ return if records.empty?
31
+ rows = collect_rows(records, &block)
32
+ col_widths = calculate_column_widths(rows)
33
+
34
+ rows.each do |row|
35
+ line = row.values.each_with_index.map do |value, col|
36
+ value.to_s.ljust(col_widths[col])
37
+ end.join(" ").rstrip
38
+ line = color.colorize(line, row.color) if row.color
39
+ puts line
40
+ end
41
+ end
42
+
43
+ # Prints a title in blue with surrounding newlines.
44
+ def title(text)
45
+ # Use $stdout directly to bypass the indentation that our `puts` does.
46
+ $stdout.puts(color.colorize("\n#{text}\n", :blue))
47
+ end
48
+
49
+ # Prints text in yellow.
50
+ def warning(text)
51
+ puts color.colorize(text, :yellow)
52
+ end
53
+
54
+ # Override `Kernel#puts` to prepend four spaces to each line.
55
+ def puts(string=nil)
56
+ $stdout.puts(string.to_s.gsub(/^/, " "))
57
+ end
58
+
59
+ private
60
+
61
+ def collect_rows(records)
62
+ records.map do |rec|
63
+ Row.new.tap { |row| yield(rec, row) }
64
+ end
65
+ end
66
+
67
+ def calculate_column_widths(rows)
68
+ num_columns = rows.map { |row| row.values.length }.max
69
+ Array.new(num_columns) do |col|
70
+ rows.map { |row| row.values[col].to_s.length }.max
71
+ end
72
+ end
73
+
74
+ def color
75
+ @color ||= SSHKit::Color.new($stdout)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,105 @@
1
+ require "capistrano/doctor/output_helpers"
2
+
3
+ module Capistrano
4
+ module Doctor
5
+ class ServersDoctor
6
+ include Capistrano::Doctor::OutputHelpers
7
+
8
+ def initialize(env=Capistrano::Configuration.env)
9
+ @servers = env.servers.to_a
10
+ end
11
+
12
+ def call
13
+ title("Servers (#{servers.size})")
14
+ rwc = RoleWhitespaceChecker.new(servers)
15
+
16
+ table(servers) do |server, row|
17
+ sd = ServerDecorator.new(server)
18
+
19
+ row << sd.uri_form
20
+ row << sd.roles
21
+ row << sd.properties
22
+ row.yellow if rwc.any_has_whitespace?(server.roles)
23
+ end
24
+
25
+ if rwc.whitespace_roles.any?
26
+ warning "\nWhitespace detected in role(s) #{rwc.whitespace_roles_decorated}. " \
27
+ "This might be a result of a mistyped \"%w()\" array literal."
28
+ end
29
+ puts
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :servers
35
+
36
+ class RoleWhitespaceChecker
37
+ attr_reader :whitespace_roles, :servers
38
+
39
+ def initialize(servers)
40
+ @servers = servers
41
+ @whitespace_roles = find_whitespace_roles
42
+ end
43
+
44
+ def any_has_whitespace?(roles)
45
+ roles.any? { |role| include_whitespace?(role) }
46
+ end
47
+
48
+ def include_whitespace?(role)
49
+ role =~ /\s/
50
+ end
51
+
52
+ def whitespace_roles_decorated
53
+ whitespace_roles.map(&:inspect).join(", ")
54
+ end
55
+
56
+ private
57
+
58
+ def find_whitespace_roles
59
+ servers.map(&:roles).flat_map(&:to_a).uniq
60
+ .select { |role| include_whitespace?(role) }
61
+ end
62
+ end
63
+
64
+ class ServerDecorator
65
+ def initialize(server)
66
+ @server = server
67
+ end
68
+
69
+ def uri_form
70
+ [
71
+ server.user,
72
+ server.user && "@",
73
+ server.hostname,
74
+ server.port && ":",
75
+ server.port
76
+ ].compact.join
77
+ end
78
+
79
+ def roles
80
+ server.roles.to_a.inspect
81
+ end
82
+
83
+ def properties
84
+ return "" unless server.properties.keys.any?
85
+ pretty_inspect(server.properties.to_h)
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :server
91
+
92
+ # Hashes with proper padding
93
+ def pretty_inspect(element)
94
+ return element.inspect unless element.is_a?(Hash)
95
+
96
+ pairs_string = element.keys.map do |key|
97
+ [pretty_inspect(key), pretty_inspect(element.fetch(key))].join(" => ")
98
+ end.join(", ")
99
+
100
+ "{ #{pairs_string} }"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,74 @@
1
+ require "capistrano/doctor/output_helpers"
2
+
3
+ module Capistrano
4
+ module Doctor
5
+ # Prints a table of all Capistrano variables and their current values. If
6
+ # there are unrecognized variables, print warnings for them.
7
+ class VariablesDoctor
8
+ # These are keys that are recognized by Capistrano, but do not have values
9
+ # set by default.
10
+ WHITELIST = %i(
11
+ application
12
+ current_directory
13
+ linked_dirs
14
+ linked_files
15
+ releases_directory
16
+ repo_url
17
+ repo_tree
18
+ shared_directory
19
+ ).freeze
20
+ private_constant :WHITELIST
21
+
22
+ include Capistrano::Doctor::OutputHelpers
23
+
24
+ def initialize(env=Capistrano::Configuration.env)
25
+ @env = env
26
+ end
27
+
28
+ def call
29
+ title("Variables")
30
+ values = inspect_all_values
31
+
32
+ table(variables.keys.sort_by(&:to_s)) do |key, row|
33
+ row.yellow if suspicious_keys.include?(key)
34
+ row << key.inspect
35
+ row << values[key]
36
+ end
37
+
38
+ puts if suspicious_keys.any?
39
+
40
+ suspicious_keys.sort_by(&:to_s).each do |key|
41
+ warning("#{key.inspect} is not a recognized Capistrano setting "\
42
+ "(#{location(key)})")
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :env
49
+
50
+ def variables
51
+ env.variables
52
+ end
53
+
54
+ def inspect_all_values
55
+ variables.keys.each_with_object({}) do |key, inspected|
56
+ inspected[key] = if env.is_question?(key)
57
+ "<ask>"
58
+ else
59
+ variables.peek(key).inspect
60
+ end
61
+ end
62
+ end
63
+
64
+ def suspicious_keys
65
+ (variables.untrusted_keys & variables.unused_keys) - WHITELIST
66
+ end
67
+
68
+ def location(key)
69
+ loc = variables.source_locations(key).first
70
+ loc && loc.sub(/^#{Regexp.quote(Dir.pwd)}/, "").sub(/:in.*/, "")
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ require "capistrano/doctor/environment_doctor"
2
+ require "capistrano/doctor/gems_doctor"
3
+ require "capistrano/doctor/variables_doctor"
4
+ require "capistrano/doctor/servers_doctor"
5
+
6
+ load File.expand_path("../tasks/doctor.rake", __FILE__)
@@ -1,3 +1,2 @@
1
- dotfile = Pathname.new(File.join(Dir.home, '.capfile'))
1
+ dotfile = Pathname.new(File.join(Dir.home, ".capfile"))
2
2
  load dotfile if dotfile.file?
3
-
@@ -1,47 +1,14 @@
1
+ require "forwardable"
2
+
1
3
  module Capistrano
2
4
  module DSL
3
5
  module Env
4
-
5
- def configure_backend
6
- env.configure_backend
7
- end
8
-
9
- def fetch(key, default=nil, &block)
10
- env.fetch(key, default, &block)
11
- end
12
-
13
- def any?(key)
14
- value = fetch(key)
15
- if value && value.respond_to?(:any?)
16
- value.any?
17
- else
18
- !fetch(key).nil?
19
- end
20
- end
21
-
22
- def set(key, value)
23
- env.set(key, value)
24
- end
25
-
26
- def set_if_empty(key, value)
27
- env.set_if_empty(key, value)
28
- end
29
-
30
- def delete(key)
31
- env.delete(key)
32
- end
33
-
34
- def ask(key, value, options={})
35
- env.ask(key, value, options)
36
- end
37
-
38
- def role(name, servers, options={})
39
- env.role(name, servers, options)
40
- end
41
-
42
- def server(name, properties={})
43
- env.server(name, properties)
44
- end
6
+ extend Forwardable
7
+ def_delegators :env,
8
+ :configure_backend, :fetch, :set, :set_if_empty, :delete,
9
+ :ask, :role, :server, :primary, :validate, :append,
10
+ :remove, :dry_run?, :install_plugin, :any?, :is_question?,
11
+ :configure_scm, :scm_plugin_installed?
45
12
 
46
13
  def roles(*names)
47
14
  env.roles_for(names.flatten)
@@ -53,17 +20,13 @@ module Capistrano
53
20
 
54
21
  def release_roles(*names)
55
22
  if names.last.is_a? Hash
56
- names.last.merge!({ :exclude => :no_release })
23
+ names.last[:exclude] = :no_release
57
24
  else
58
25
  names << { exclude: :no_release }
59
26
  end
60
27
  roles(*names)
61
28
  end
62
29
 
63
- def primary(role)
64
- env.primary(role)
65
- end
66
-
67
30
  def env
68
31
  Configuration.env
69
32
  end
@@ -75,7 +38,6 @@ module Capistrano
75
38
  def asset_timestamp
76
39
  env.timestamp.strftime("%Y%m%d%H%M.%S")
77
40
  end
78
-
79
41
  end
80
42
  end
81
43
  end
@@ -1,8 +1,7 @@
1
- require 'pathname'
1
+ require "pathname"
2
2
  module Capistrano
3
3
  module DSL
4
4
  module Paths
5
-
6
5
  def deploy_to
7
6
  fetch(:deploy_to)
8
7
  end
@@ -12,15 +11,15 @@ module Capistrano
12
11
  end
13
12
 
14
13
  def current_path
15
- deploy_path.join('current')
14
+ deploy_path.join(fetch(:current_directory, "current"))
16
15
  end
17
16
 
18
17
  def releases_path
19
- deploy_path.join('releases')
18
+ deploy_path.join(fetch(:releases_directory, "releases"))
20
19
  end
21
20
 
22
21
  def release_path
23
- fetch(:release_path, current_path)
22
+ fetch(:release_path) { current_path }
24
23
  end
25
24
 
26
25
  def set_release_path(timestamp=now)
@@ -29,40 +28,27 @@ module Capistrano
29
28
  end
30
29
 
31
30
  def stage_config_path
32
- Pathname.new fetch(:stage_config_path, 'config/deploy')
31
+ Pathname.new fetch(:stage_config_path, "config/deploy")
33
32
  end
34
33
 
35
34
  def deploy_config_path
36
- Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
35
+ Pathname.new fetch(:deploy_config_path, "config/deploy.rb")
37
36
  end
38
37
 
39
38
  def repo_url
40
- require 'cgi'
41
- require 'uri'
42
- if fetch(:git_http_username) and fetch(:git_http_password)
43
- URI.parse(fetch(:repo_url)).tap do |repo_uri|
44
- repo_uri.user = fetch(:git_http_username)
45
- repo_uri.password = CGI.escape(fetch(:git_http_password))
46
- end.to_s
47
- elsif fetch(:git_http_username)
48
- URI.parse(fetch(:repo_url)).tap do |repo_uri|
49
- repo_uri.user = fetch(:git_http_username)
50
- end.to_s
51
- else
52
- fetch(:repo_url)
53
- end
39
+ fetch(:repo_url)
54
40
  end
55
41
 
56
42
  def repo_path
57
- Pathname.new(fetch(:repo_path, ->(){deploy_path.join('repo')}))
43
+ Pathname.new(fetch(:repo_path, ->() { deploy_path.join("repo") }))
58
44
  end
59
45
 
60
46
  def shared_path
61
- deploy_path.join('shared')
47
+ deploy_path.join(fetch(:shared_directory, "shared"))
62
48
  end
63
49
 
64
50
  def revision_log
65
- deploy_path.join('revisions.log')
51
+ deploy_path.join("revisions.log")
66
52
  end
67
53
 
68
54
  def now
@@ -96,7 +82,7 @@ module Capistrano
96
82
  end
97
83
 
98
84
  def map_dirnames(paths)
99
- paths.map { |path| path.dirname }
85
+ paths.map(&:dirname).uniq
100
86
  end
101
87
  end
102
88
  end
@@ -1,19 +1,31 @@
1
1
  module Capistrano
2
2
  module DSL
3
3
  module Stages
4
+ RESERVED_NAMES = %w(deploy doctor install).freeze
5
+ private_constant :RESERVED_NAMES
4
6
 
5
7
  def stages
6
- Dir[stage_definitions].map { |f| File.basename(f, '.rb') }
8
+ names = Dir[stage_definitions].map { |f| File.basename(f, ".rb") }
9
+ assert_valid_stage_names(names)
10
+ names
7
11
  end
8
12
 
9
13
  def stage_definitions
10
- stage_config_path.join('*.rb')
14
+ stage_config_path.join("*.rb")
11
15
  end
12
16
 
13
17
  def stage_set?
14
18
  !!fetch(:stage, false)
15
19
  end
16
20
 
21
+ private
22
+
23
+ def assert_valid_stage_names(names)
24
+ invalid = names.find { |n| RESERVED_NAMES.include?(n) }
25
+ return if invalid.nil?
26
+
27
+ raise t("error.invalid_stage_name", name: invalid, path: stage_config_path.join("#{invalid}.rb"))
28
+ end
17
29
  end
18
30
  end
19
31
  end
@@ -1,4 +1,4 @@
1
- require 'capistrano/upload_task'
1
+ require "capistrano/upload_task"
2
2
 
3
3
  module Capistrano
4
4
  module TaskEnhancements
@@ -9,17 +9,14 @@ module Capistrano
9
9
 
10
10
  def after(task, post_task, *args, &block)
11
11
  Rake::Task.define_task(post_task, *args, &block) if block_given?
12
- post_task = Rake::Task[post_task]
13
- Rake::Task[task].enhance do
14
- post_task.invoke
12
+ task = Rake::Task[task]
13
+ task.enhance do
14
+ post = Rake.application.lookup(post_task, task.scope)
15
+ raise ArgumentError, "Task #{post_task.inspect} not found" unless post
16
+ post.invoke
15
17
  end
16
18
  end
17
19
 
18
- def remote_file(task)
19
- target_roles = task.delete(:roles) { :all }
20
- define_remote_file_task(task, target_roles)
21
- end
22
-
23
20
  def define_remote_file_task(task, target_roles)
24
21
  Capistrano::UploadTask.define_task(task) do |t|
25
22
  prerequisite_file = t.prerequisites.first
@@ -31,7 +28,6 @@ module Capistrano
31
28
  upload! File.open(prerequisite_file), file
32
29
  end
33
30
  end
34
-
35
31
  end
36
32
  end
37
33
 
@@ -54,13 +50,12 @@ module Capistrano
54
50
 
55
51
  def exit_deploy_because_of_exception(ex)
56
52
  warn t(:deploy_failed, ex: ex.message)
57
- invoke 'deploy:failed'
53
+ invoke "deploy:failed"
58
54
  exit(false)
59
55
  end
60
56
 
61
57
  def deploying?
62
58
  fetch(:deploying, false)
63
59
  end
64
-
65
60
  end
66
61
  end
@@ -1,9 +1,8 @@
1
- require 'etc'
2
- require 'capistrano/dsl/task_enhancements'
3
- require 'capistrano/dsl/paths'
4
- require 'capistrano/dsl/stages'
5
- require 'capistrano/dsl/env'
6
- require 'capistrano/configuration/filter'
1
+ require "capistrano/dsl/task_enhancements"
2
+ require "capistrano/dsl/paths"
3
+ require "capistrano/dsl/stages"
4
+ require "capistrano/dsl/env"
5
+ require "capistrano/configuration/filter"
7
6
 
8
7
  module Capistrano
9
8
  module DSL
@@ -12,12 +11,29 @@ module Capistrano
12
11
  include Paths
13
12
  include Stages
14
13
 
15
- def invoke(task, *args)
16
- Rake::Task[task].invoke(*args)
14
+ def invoke(task_name, *args)
15
+ task = Rake::Task[task_name]
16
+ # NOTE: We access instance variable since the accessor was only added recently. Once Capistrano depends on rake 11+, we can revert the following line
17
+ if task && task.instance_variable_get(:@already_invoked)
18
+ file, line, = caller.first.split(":")
19
+ colors = SSHKit::Color.new($stderr)
20
+ $stderr.puts colors.colorize("Skipping task `#{task_name}'.", :yellow)
21
+ $stderr.puts "Capistrano tasks may only be invoked once. Since task `#{task}' was previously invoked, invoke(\"#{task_name}\") at #{file}:#{line} will be skipped."
22
+ $stderr.puts "If you really meant to run this task again, use invoke!(\"#{task_name}\")"
23
+ $stderr.puts colors.colorize("THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.", :red)
24
+ $stderr.puts colors.colorize("https://github.com/capistrano/capistrano/issues/1686", :red)
25
+ end
26
+ task.invoke(*args)
27
+ end
28
+
29
+ def invoke!(task_name, *args)
30
+ task = Rake::Task[task_name]
31
+ task.reenable
32
+ task.invoke(*args)
17
33
  end
18
34
 
19
35
  def t(key, options={})
20
- I18n.t(key, options.merge(scope: :capistrano))
36
+ I18n.t(key, **options.merge(scope: :capistrano))
21
37
  end
22
38
 
23
39
  def scm
@@ -30,12 +46,11 @@ module Capistrano
30
46
 
31
47
  def revision_log_message
32
48
  fetch(:revision_log_message,
33
- t(:revision_log_message,
34
- branch: fetch(:branch),
35
- user: local_user,
36
- sha: fetch(:current_revision),
37
- release: fetch(:release_timestamp))
38
- )
49
+ t(:revision_log_message,
50
+ branch: fetch(:branch),
51
+ user: local_user,
52
+ sha: fetch(:current_revision),
53
+ release: fetch(:release_timestamp)))
39
54
  end
40
55
 
41
56
  def rollback_log_message
@@ -50,15 +65,31 @@ module Capistrano
50
65
  VersionValidator.new(locked_version).verify
51
66
  end
52
67
 
68
+ # rubocop:disable Security/MarshalLoad
53
69
  def on(hosts, options={}, &block)
54
70
  subset_copy = Marshal.dump(Configuration.env.filter(hosts))
55
71
  SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
56
72
  end
73
+ # rubocop:enable Security/MarshalLoad
57
74
 
58
75
  def run_locally(&block)
59
76
  SSHKit::Backend::Local.new(&block).run
60
77
  end
61
78
 
79
+ # Catch common beginner mistake and give a helpful error message on stderr
80
+ def execute(*)
81
+ file, line, = caller.first.split(":")
82
+ colors = SSHKit::Color.new($stderr)
83
+ $stderr.puts colors.colorize("Warning: `execute' should be wrapped in an `on' scope in #{file}:#{line}.", :red)
84
+ $stderr.puts
85
+ $stderr.puts " task :example do"
86
+ $stderr.puts colors.colorize(" on roles(:app) do", :yellow)
87
+ $stderr.puts " execute 'whoami'"
88
+ $stderr.puts colors.colorize(" end", :yellow)
89
+ $stderr.puts " end"
90
+ $stderr.puts
91
+ raise NoMethodError, "undefined method `execute' for main:Object"
92
+ end
62
93
  end
63
94
  end
64
- self.extend Capistrano::DSL
95
+ extend Capistrano::DSL
@@ -1,2 +1,2 @@
1
1
  load File.expand_path("../tasks/framework.rake", __FILE__)
2
- require 'capistrano/install'
2
+ require "capistrano/install"