capistrano 2.8.0 → 3.19.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.
Files changed (239) hide show
  1. checksums.yaml +7 -0
  2. data/.docker/Dockerfile +7 -0
  3. data/.docker/ssh_key_rsa +49 -0
  4. data/.docker/ssh_key_rsa.pub +1 -0
  5. data/.docker/ubuntu_setup.sh +23 -0
  6. data/.github/issue_template.md +19 -0
  7. data/.github/pull_request_template.md +22 -0
  8. data/.github/release-drafter.yml +25 -0
  9. data/.github/workflows/ci.yml +80 -0
  10. data/.github/workflows/release-drafter.yml +18 -0
  11. data/.gitignore +23 -8
  12. data/.rubocop.yml +62 -0
  13. data/CHANGELOG.md +1 -0
  14. data/CONTRIBUTING.md +63 -0
  15. data/DEVELOPMENT.md +112 -0
  16. data/Gemfile +42 -9
  17. data/LICENSE.txt +21 -0
  18. data/README.md +221 -0
  19. data/RELEASING.md +17 -0
  20. data/Rakefile +17 -8
  21. data/UPGRADING-3.7.md +86 -0
  22. data/bin/cap +2 -3
  23. data/bin/capify +7 -89
  24. data/capistrano.gemspec +29 -43
  25. data/docker-compose.yml +8 -0
  26. data/features/configuration.feature +28 -0
  27. data/features/deploy.feature +92 -0
  28. data/features/deploy_failure.feature +17 -0
  29. data/features/doctor.feature +11 -0
  30. data/features/installation.feature +21 -0
  31. data/features/sshconnect.feature +11 -0
  32. data/features/stage_failure.feature +9 -0
  33. data/features/step_definitions/assertions.rb +162 -0
  34. data/features/step_definitions/cap_commands.rb +21 -0
  35. data/features/step_definitions/setup.rb +91 -0
  36. data/features/subdirectory.feature +9 -0
  37. data/features/support/docker_gateway.rb +53 -0
  38. data/features/support/env.rb +1 -0
  39. data/features/support/remote_command_helpers.rb +29 -0
  40. data/features/support/remote_ssh_helpers.rb +33 -0
  41. data/lib/Capfile +3 -0
  42. data/lib/capistrano/all.rb +17 -0
  43. data/lib/capistrano/application.rb +153 -0
  44. data/lib/capistrano/configuration/empty_filter.rb +9 -0
  45. data/lib/capistrano/configuration/filter.rb +26 -0
  46. data/lib/capistrano/configuration/host_filter.rb +29 -0
  47. data/lib/capistrano/configuration/null_filter.rb +9 -0
  48. data/lib/capistrano/configuration/plugin_installer.rb +51 -0
  49. data/lib/capistrano/configuration/question.rb +76 -0
  50. data/lib/capistrano/configuration/role_filter.rb +29 -0
  51. data/lib/capistrano/configuration/scm_resolver.rb +149 -0
  52. data/lib/capistrano/configuration/server.rb +137 -0
  53. data/lib/capistrano/configuration/servers.rb +56 -96
  54. data/lib/capistrano/configuration/validated_variables.rb +110 -0
  55. data/lib/capistrano/configuration/variables.rb +79 -94
  56. data/lib/capistrano/configuration.rb +178 -33
  57. data/lib/capistrano/console.rb +1 -0
  58. data/lib/capistrano/defaults.rb +36 -0
  59. data/lib/capistrano/deploy.rb +3 -0
  60. data/lib/capistrano/doctor/environment_doctor.rb +19 -0
  61. data/lib/capistrano/doctor/gems_doctor.rb +45 -0
  62. data/lib/capistrano/doctor/output_helpers.rb +79 -0
  63. data/lib/capistrano/doctor/servers_doctor.rb +105 -0
  64. data/lib/capistrano/doctor/variables_doctor.rb +74 -0
  65. data/lib/capistrano/doctor.rb +6 -0
  66. data/lib/capistrano/dotfile.rb +2 -0
  67. data/lib/capistrano/dsl/env.rb +43 -0
  68. data/lib/capistrano/dsl/paths.rb +89 -0
  69. data/lib/capistrano/dsl/stages.rb +31 -0
  70. data/lib/capistrano/dsl/task_enhancements.rb +61 -0
  71. data/lib/capistrano/dsl.rb +95 -0
  72. data/lib/capistrano/framework.rb +2 -0
  73. data/lib/capistrano/i18n.rb +46 -0
  74. data/lib/capistrano/immutable_task.rb +30 -0
  75. data/lib/capistrano/install.rb +1 -0
  76. data/lib/capistrano/plugin.rb +95 -0
  77. data/lib/capistrano/proc_helpers.rb +13 -0
  78. data/lib/capistrano/scm/git.rb +105 -0
  79. data/lib/capistrano/scm/hg.rb +55 -0
  80. data/lib/capistrano/scm/plugin.rb +13 -0
  81. data/lib/capistrano/scm/svn.rb +56 -0
  82. data/lib/capistrano/scm/tasks/git.rake +84 -0
  83. data/lib/capistrano/scm/tasks/hg.rake +53 -0
  84. data/lib/capistrano/scm/tasks/svn.rake +53 -0
  85. data/lib/capistrano/scm.rb +115 -0
  86. data/lib/capistrano/setup.rb +36 -0
  87. data/lib/capistrano/tasks/console.rake +25 -0
  88. data/lib/capistrano/tasks/deploy.rake +280 -0
  89. data/lib/capistrano/tasks/doctor.rake +24 -0
  90. data/lib/capistrano/tasks/framework.rake +67 -0
  91. data/lib/capistrano/tasks/install.rake +41 -0
  92. data/lib/capistrano/templates/Capfile +38 -0
  93. data/lib/capistrano/templates/deploy.rb.erb +39 -0
  94. data/lib/capistrano/templates/stage.rb.erb +61 -0
  95. data/lib/capistrano/upload_task.rb +9 -0
  96. data/lib/capistrano/version.rb +1 -14
  97. data/lib/capistrano/version_validator.rb +32 -0
  98. data/lib/capistrano.rb +0 -3
  99. data/spec/integration/dsl_spec.rb +632 -0
  100. data/spec/integration_spec_helper.rb +5 -0
  101. data/spec/lib/capistrano/application_spec.rb +60 -0
  102. data/spec/lib/capistrano/configuration/empty_filter_spec.rb +17 -0
  103. data/spec/lib/capistrano/configuration/filter_spec.rb +109 -0
  104. data/spec/lib/capistrano/configuration/host_filter_spec.rb +71 -0
  105. data/spec/lib/capistrano/configuration/null_filter_spec.rb +17 -0
  106. data/spec/lib/capistrano/configuration/plugin_installer_spec.rb +98 -0
  107. data/spec/lib/capistrano/configuration/question_spec.rb +92 -0
  108. data/spec/lib/capistrano/configuration/role_filter_spec.rb +80 -0
  109. data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +56 -0
  110. data/spec/lib/capistrano/configuration/server_spec.rb +309 -0
  111. data/spec/lib/capistrano/configuration/servers_spec.rb +331 -0
  112. data/spec/lib/capistrano/configuration_spec.rb +357 -0
  113. data/spec/lib/capistrano/doctor/environment_doctor_spec.rb +44 -0
  114. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +67 -0
  115. data/spec/lib/capistrano/doctor/output_helpers_spec.rb +47 -0
  116. data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
  117. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +89 -0
  118. data/spec/lib/capistrano/dsl/paths_spec.rb +228 -0
  119. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +108 -0
  120. data/spec/lib/capistrano/dsl_spec.rb +125 -0
  121. data/spec/lib/capistrano/immutable_task_spec.rb +31 -0
  122. data/spec/lib/capistrano/plugin_spec.rb +84 -0
  123. data/spec/lib/capistrano/scm/git_spec.rb +194 -0
  124. data/spec/lib/capistrano/scm/hg_spec.rb +109 -0
  125. data/spec/lib/capistrano/scm/svn_spec.rb +137 -0
  126. data/spec/lib/capistrano/scm_spec.rb +103 -0
  127. data/spec/lib/capistrano/upload_task_spec.rb +19 -0
  128. data/spec/lib/capistrano/version_validator_spec.rb +118 -0
  129. data/spec/lib/capistrano_spec.rb +7 -0
  130. data/spec/spec_helper.rb +29 -0
  131. data/spec/support/matchers.rb +5 -0
  132. data/spec/support/tasks/database.rake +11 -0
  133. data/spec/support/tasks/fail.rake +8 -0
  134. data/spec/support/tasks/failed.rake +5 -0
  135. data/spec/support/tasks/plugin.rake +6 -0
  136. data/spec/support/tasks/root.rake +11 -0
  137. data/spec/support/test_app.rb +205 -0
  138. metadata +234 -208
  139. data/.rvmrc +0 -1
  140. data/CHANGELOG +0 -954
  141. data/README.mdown +0 -76
  142. data/lib/capistrano/callback.rb +0 -45
  143. data/lib/capistrano/cli/execute.rb +0 -85
  144. data/lib/capistrano/cli/help.rb +0 -125
  145. data/lib/capistrano/cli/help.txt +0 -81
  146. data/lib/capistrano/cli/options.rb +0 -243
  147. data/lib/capistrano/cli/ui.rb +0 -40
  148. data/lib/capistrano/cli.rb +0 -47
  149. data/lib/capistrano/command.rb +0 -286
  150. data/lib/capistrano/configuration/actions/file_transfer.rb +0 -51
  151. data/lib/capistrano/configuration/actions/inspect.rb +0 -46
  152. data/lib/capistrano/configuration/actions/invocation.rb +0 -298
  153. data/lib/capistrano/configuration/callbacks.rb +0 -148
  154. data/lib/capistrano/configuration/connections.rb +0 -230
  155. data/lib/capistrano/configuration/execution.rb +0 -143
  156. data/lib/capistrano/configuration/loading.rb +0 -197
  157. data/lib/capistrano/configuration/namespaces.rb +0 -197
  158. data/lib/capistrano/configuration/roles.rb +0 -73
  159. data/lib/capistrano/errors.rb +0 -19
  160. data/lib/capistrano/ext/string.rb +0 -5
  161. data/lib/capistrano/extensions.rb +0 -57
  162. data/lib/capistrano/logger.rb +0 -59
  163. data/lib/capistrano/processable.rb +0 -53
  164. data/lib/capistrano/recipes/compat.rb +0 -32
  165. data/lib/capistrano/recipes/deploy/assets.rb +0 -57
  166. data/lib/capistrano/recipes/deploy/dependencies.rb +0 -44
  167. data/lib/capistrano/recipes/deploy/local_dependency.rb +0 -54
  168. data/lib/capistrano/recipes/deploy/remote_dependency.rb +0 -111
  169. data/lib/capistrano/recipes/deploy/scm/accurev.rb +0 -169
  170. data/lib/capistrano/recipes/deploy/scm/base.rb +0 -196
  171. data/lib/capistrano/recipes/deploy/scm/bzr.rb +0 -86
  172. data/lib/capistrano/recipes/deploy/scm/cvs.rb +0 -153
  173. data/lib/capistrano/recipes/deploy/scm/darcs.rb +0 -96
  174. data/lib/capistrano/recipes/deploy/scm/git.rb +0 -282
  175. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +0 -137
  176. data/lib/capistrano/recipes/deploy/scm/none.rb +0 -44
  177. data/lib/capistrano/recipes/deploy/scm/perforce.rb +0 -138
  178. data/lib/capistrano/recipes/deploy/scm/subversion.rb +0 -121
  179. data/lib/capistrano/recipes/deploy/scm.rb +0 -19
  180. data/lib/capistrano/recipes/deploy/strategy/base.rb +0 -88
  181. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +0 -20
  182. data/lib/capistrano/recipes/deploy/strategy/copy.rb +0 -224
  183. data/lib/capistrano/recipes/deploy/strategy/export.rb +0 -20
  184. data/lib/capistrano/recipes/deploy/strategy/remote.rb +0 -52
  185. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +0 -57
  186. data/lib/capistrano/recipes/deploy/strategy.rb +0 -19
  187. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  188. data/lib/capistrano/recipes/deploy.rb +0 -568
  189. data/lib/capistrano/recipes/standard.rb +0 -37
  190. data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
  191. data/lib/capistrano/role.rb +0 -102
  192. data/lib/capistrano/server_definition.rb +0 -56
  193. data/lib/capistrano/shell.rb +0 -260
  194. data/lib/capistrano/ssh.rb +0 -101
  195. data/lib/capistrano/task_definition.rb +0 -75
  196. data/lib/capistrano/transfer.rb +0 -216
  197. data/rvmrc.sample +0 -1
  198. data/test/cli/execute_test.rb +0 -132
  199. data/test/cli/help_test.rb +0 -165
  200. data/test/cli/options_test.rb +0 -329
  201. data/test/cli/ui_test.rb +0 -28
  202. data/test/cli_test.rb +0 -17
  203. data/test/command_test.rb +0 -289
  204. data/test/configuration/actions/file_transfer_test.rb +0 -61
  205. data/test/configuration/actions/inspect_test.rb +0 -65
  206. data/test/configuration/actions/invocation_test.rb +0 -247
  207. data/test/configuration/callbacks_test.rb +0 -220
  208. data/test/configuration/connections_test.rb +0 -420
  209. data/test/configuration/execution_test.rb +0 -175
  210. data/test/configuration/loading_test.rb +0 -132
  211. data/test/configuration/namespace_dsl_test.rb +0 -311
  212. data/test/configuration/roles_test.rb +0 -144
  213. data/test/configuration/servers_test.rb +0 -183
  214. data/test/configuration/variables_test.rb +0 -190
  215. data/test/configuration_test.rb +0 -88
  216. data/test/deploy/local_dependency_test.rb +0 -76
  217. data/test/deploy/remote_dependency_test.rb +0 -135
  218. data/test/deploy/scm/accurev_test.rb +0 -23
  219. data/test/deploy/scm/base_test.rb +0 -55
  220. data/test/deploy/scm/bzr_test.rb +0 -51
  221. data/test/deploy/scm/darcs_test.rb +0 -37
  222. data/test/deploy/scm/git_test.rb +0 -184
  223. data/test/deploy/scm/mercurial_test.rb +0 -134
  224. data/test/deploy/scm/none_test.rb +0 -35
  225. data/test/deploy/scm/subversion_test.rb +0 -32
  226. data/test/deploy/strategy/copy_test.rb +0 -321
  227. data/test/extensions_test.rb +0 -69
  228. data/test/fixtures/cli_integration.rb +0 -5
  229. data/test/fixtures/config.rb +0 -5
  230. data/test/fixtures/custom.rb +0 -3
  231. data/test/logger_test.rb +0 -123
  232. data/test/recipes_test.rb +0 -25
  233. data/test/role_test.rb +0 -11
  234. data/test/server_definition_test.rb +0 -121
  235. data/test/shell_test.rb +0 -90
  236. data/test/ssh_test.rb +0 -113
  237. data/test/task_definition_test.rb +0 -116
  238. data/test/transfer_test.rb +0 -160
  239. data/test/utils.rb +0 -37
@@ -0,0 +1,17 @@
1
+ Feature: Deploy failure
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+ And a custom task that will simulate a failure
6
+ And a custom task to run in the event of a failure
7
+ And servers with the roles app and web
8
+
9
+ Scenario: Triggering the custom task
10
+ When I run cap "deploy:starting"
11
+ But an error is raised
12
+ Then the failure task will not run
13
+
14
+ Scenario: Triggering the custom task
15
+ When I run cap "deploy"
16
+ But an error is raised
17
+ Then the failure task will run
@@ -0,0 +1,11 @@
1
+ Feature: Doctor
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+
6
+ Scenario: Running the doctor task
7
+ When I run cap "doctor"
8
+ Then the task is successful
9
+ And contains "Environment" in the output
10
+ And contains "Gems" in the output
11
+ And contains "Variables" in the output
@@ -0,0 +1,21 @@
1
+ Feature: Installation
2
+
3
+ Background:
4
+ Given a test app without any configuration
5
+
6
+ Scenario: The "install" task documentation can be viewed
7
+ When I run "cap -T"
8
+ Then the task is successful
9
+ And contains "cap install" in the output
10
+
11
+ Scenario: With default stages
12
+ When I run "cap install"
13
+ Then the deploy.rb file is created
14
+ And the default stage files are created
15
+ And the tasks folder is created
16
+
17
+ Scenario: With specified stages
18
+ When I run "cap install STAGES=qa,production"
19
+ Then the deploy.rb file is created
20
+ And the specified stage files are created
21
+ And the tasks folder is created
@@ -0,0 +1,11 @@
1
+ Feature: SSH Connection
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+ And servers with the roles app and web
6
+ And a task which executes as root
7
+
8
+ Scenario: Switching from default user to root and back again
9
+ When I run cap "am_i_root"
10
+ Then the task is successful
11
+ And the output matches "I am uid=0\(root\)" followed by "I am uid=\d+\(deployer\)"
@@ -0,0 +1,9 @@
1
+ Feature: Stage failure
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+ And a stage file named deploy.rb
6
+
7
+ Scenario: Running a task
8
+ When I run cap "doctor"
9
+ Then the task fails
@@ -0,0 +1,162 @@
1
+ require "shellwords"
2
+
3
+ Then(/^references in the remote repo are listed$/) do
4
+ expect(@output).to include("refs/heads/master")
5
+ end
6
+
7
+ Then(/^git wrapper permissions are 0700$/) do
8
+ permissions_test = %Q([ $(stat -c "%a" #{TestApp.git_wrapper_path_glob}) == "700" ])
9
+ expect { run_remote_ssh_command(permissions_test) }.not_to raise_error
10
+ end
11
+
12
+ Then(/^the shared path is created$/) do
13
+ run_remote_ssh_command(test_dir_exists(TestApp.shared_path))
14
+ end
15
+
16
+ Then(/^the releases path is created$/) do
17
+ run_remote_ssh_command(test_dir_exists(TestApp.releases_path))
18
+ end
19
+
20
+ Then(/^(\d+) valid releases are kept/) do |num|
21
+ test = %Q([ $(ls -g #{TestApp.releases_path} | grep -E '[0-9]{14}' | wc -l) == "#{num}" ])
22
+ expect { run_remote_ssh_command(test) }.not_to raise_error
23
+ end
24
+
25
+ Then(/^the invalid (.+) release is ignored$/) do |filename|
26
+ test = "ls -g #{TestApp.releases_path} | grep #{filename}"
27
+ expect { run_remote_ssh_command(test) }.not_to raise_error
28
+ end
29
+
30
+ Then(/^directories in :linked_dirs are created in shared$/) do
31
+ TestApp.linked_dirs.each do |dir|
32
+ run_remote_ssh_command(test_dir_exists(TestApp.shared_path.join(dir)))
33
+ end
34
+ end
35
+
36
+ Then(/^directories referenced in :linked_files are created in shared$/) do
37
+ dirs = TestApp.linked_files.map { |path| TestApp.shared_path.join(path).dirname }
38
+ dirs.each do |dir|
39
+ run_remote_ssh_command(test_dir_exists(dir))
40
+ end
41
+ end
42
+
43
+ Then(/^the repo is cloned$/) do
44
+ run_remote_ssh_command(test_dir_exists(TestApp.repo_path))
45
+ end
46
+
47
+ Then(/^the release is created$/) do
48
+ stdout, _stderr = run_remote_ssh_command("ls #{TestApp.releases_path}")
49
+
50
+ expect(stdout.strip).to match(/\A#{Time.now.utc.strftime("%Y%m%d")}\d{6}\Z/)
51
+ end
52
+
53
+ Then(/^the REVISION file is created in the release$/) do
54
+ stdout, _stderr = run_remote_ssh_command("cat #{@release_paths[0]}/REVISION")
55
+
56
+ expect(stdout.strip).to match(/\h{40}/)
57
+ end
58
+
59
+ Then(/^the REVISION_TIME file is created in the release$/) do
60
+ stdout, _stderr = run_remote_ssh_command("cat #{@release_paths[0]}/REVISION_TIME")
61
+
62
+ expect(stdout.strip).to match(/\d{10}/)
63
+ end
64
+
65
+ Then(/^file symlinks are created in the new release$/) do
66
+ TestApp.linked_files.each do |file|
67
+ run_remote_ssh_command(test_symlink_exists(TestApp.current_path.join(file)))
68
+ end
69
+ end
70
+
71
+ Then(/^directory symlinks are created in the new release$/) do
72
+ pending
73
+ TestApp.linked_dirs.each do |dir|
74
+ run_remote_ssh_command(test_symlink_exists(TestApp.release_path.join(dir)))
75
+ end
76
+ end
77
+
78
+ Then(/^the current directory will be a symlink to the release$/) do
79
+ run_remote_ssh_command(exists?("e", TestApp.current_path))
80
+ end
81
+
82
+ Then(/^the deploy\.rb file is created$/) do
83
+ file = TestApp.test_app_path.join("config/deploy.rb")
84
+ expect(File.exist?(file)).to be true
85
+ end
86
+
87
+ Then(/^the default stage files are created$/) do
88
+ staging = TestApp.test_app_path.join("config/deploy/staging.rb")
89
+ production = TestApp.test_app_path.join("config/deploy/production.rb")
90
+ expect(File.exist?(staging)).to be true
91
+ expect(File.exist?(production)).to be true
92
+ end
93
+
94
+ Then(/^the tasks folder is created$/) do
95
+ path = TestApp.test_app_path.join("lib/capistrano/tasks")
96
+ expect(Dir.exist?(path)).to be true
97
+ end
98
+
99
+ Then(/^the specified stage files are created$/) do
100
+ qa = TestApp.test_app_path.join("config/deploy/qa.rb")
101
+ production = TestApp.test_app_path.join("config/deploy/production.rb")
102
+ expect(File.exist?(qa)).to be true
103
+ expect(File.exist?(production)).to be true
104
+ end
105
+
106
+ Then(/^it creates the file with the remote_task prerequisite$/) do
107
+ TestApp.linked_files.each do |file|
108
+ run_remote_ssh_command(test_file_exists(TestApp.shared_path.join(file)))
109
+ end
110
+ end
111
+
112
+ Then(/^it will not recreate the file$/) do
113
+ #
114
+ end
115
+
116
+ Then(/^the task is successful$/) do
117
+ expect(@success).to be true
118
+ end
119
+
120
+ Then(/^the task fails$/) do
121
+ expect(@success).to be_falsey
122
+ end
123
+
124
+ Then(/^the failure task will run$/) do
125
+ failed = TestApp.shared_path.join("failed")
126
+ run_remote_ssh_command(test_file_exists(failed))
127
+ end
128
+
129
+ Then(/^the failure task will not run$/) do
130
+ failed = TestApp.shared_path.join("failed")
131
+ expect { run_remote_ssh_command(test_file_exists(failed)) }
132
+ .to raise_error(RemoteSSHHelpers::RemoteSSHCommandError)
133
+ end
134
+
135
+ When(/^an error is raised$/) do
136
+ error = TestApp.shared_path.join("fail")
137
+ run_remote_ssh_command(test_file_exists(error))
138
+ end
139
+
140
+ Then(/contains "([^"]*)" in the output/) do |expected|
141
+ expect(@output).to include(expected)
142
+ end
143
+
144
+ Then(/the output matches "([^"]*)" followed by "([^"]*)"/) do |expected, followedby|
145
+ expect(@output).to match(/#{expected}.*#{followedby}/m)
146
+ end
147
+
148
+ Then(/doesn't contain "([^"]*)" in the output/) do |expected|
149
+ expect(@output).not_to include(expected)
150
+ end
151
+
152
+ Then(/the current symlink points to the previous release/) do
153
+ previous_release_path = @release_paths[-2]
154
+
155
+ run_remote_ssh_command(symlinked?(TestApp.current_path, previous_release_path))
156
+ end
157
+
158
+ Then(/^the current symlink points to that specific release$/) do
159
+ specific_release_path = TestApp.releases_path.join(@rollback_release)
160
+
161
+ run_remote_ssh_command(symlinked?(TestApp.current_path, specific_release_path))
162
+ end
@@ -0,0 +1,21 @@
1
+ When(/^I run cap "(.*?)"$/) do |task|
2
+ @success, @output = TestApp.cap(task)
3
+ end
4
+
5
+ When(/^I run cap "(.*?)" within the "(.*?)" directory$/) do |task, directory|
6
+ @success, @output = TestApp.cap(task, directory)
7
+ end
8
+
9
+ When(/^I run cap "(.*?)" as part of a release$/) do |task|
10
+ TestApp.cap("deploy:new_release_path #{task}")
11
+ end
12
+
13
+ When(/^I run "(.*?)"$/) do |command|
14
+ @success, @output = TestApp.run(command)
15
+ end
16
+
17
+ When(/^I rollback to a specific release$/) do
18
+ @rollback_release = @release_paths.first.split("/").last
19
+
20
+ step %Q{I run cap "deploy:rollback ROLLBACK_RELEASE=#{@rollback_release}"}
21
+ end
@@ -0,0 +1,91 @@
1
+ Given(/^a test app with the default configuration$/) do
2
+ TestApp.install
3
+ end
4
+
5
+ Given(/^a test app without any configuration$/) do
6
+ TestApp.create_test_app
7
+ end
8
+
9
+ Given(/^servers with the roles app and web$/) do
10
+ start_ssh_server
11
+ wait_for_ssh_server
12
+ end
13
+
14
+ Given(/^a linked file "(.*?)"$/) do |file|
15
+ # ignoring other linked files
16
+ TestApp.append_to_deploy_file("set :linked_files, ['#{file}']")
17
+ end
18
+
19
+ Given(/^file "(.*?)" exists in shared path$/) do |file|
20
+ file_shared_path = TestApp.shared_path.join(file)
21
+ run_remote_ssh_command("mkdir -p #{file_shared_path.dirname}")
22
+ run_remote_ssh_command("touch #{file_shared_path}")
23
+ end
24
+
25
+ Given(/^all linked files exists in shared path$/) do
26
+ TestApp.linked_files.each do |linked_file|
27
+ step %Q{file "#{linked_file}" exists in shared path}
28
+ end
29
+ end
30
+
31
+ Given(/^file "(.*?)" does not exist in shared path$/) do |file|
32
+ file_shared_path = TestApp.shared_path.join(file)
33
+ run_remote_ssh_command("mkdir -p #{TestApp.shared_path}")
34
+ run_remote_ssh_command("touch #{file_shared_path} && rm #{file_shared_path}")
35
+ end
36
+
37
+ Given(/^a custom task to generate a file$/) do
38
+ TestApp.copy_task_to_test_app("spec/support/tasks/database.rake")
39
+ end
40
+
41
+ Given(/^a task which executes as root$/) do
42
+ TestApp.copy_task_to_test_app("spec/support/tasks/root.rake")
43
+ end
44
+
45
+ Given(/config stage file has line "(.*?)"/) do |line|
46
+ TestApp.append_to_deploy_file(line)
47
+ end
48
+
49
+ Given(/^the configuration is in a custom location$/) do
50
+ TestApp.move_configuration_to_custom_location("app")
51
+ end
52
+
53
+ Given(/^a custom task that will simulate a failure$/) do
54
+ safely_remove_file(TestApp.shared_path.join("failed"))
55
+ TestApp.copy_task_to_test_app("spec/support/tasks/fail.rake")
56
+ end
57
+
58
+ Given(/^a custom task to run in the event of a failure$/) do
59
+ safely_remove_file(TestApp.shared_path.join("failed"))
60
+ TestApp.copy_task_to_test_app("spec/support/tasks/failed.rake")
61
+ end
62
+
63
+ Given(/^a stage file named (.+)$/) do |filename|
64
+ TestApp.write_local_stage_file(filename)
65
+ end
66
+
67
+ Given(/^I make (\d+) deployments?$/) do |count|
68
+ step "all linked files exists in shared path"
69
+
70
+ @release_paths = (1..count.to_i).map do
71
+ TestApp.cap("deploy")
72
+ stdout, _stderr = run_remote_ssh_command("readlink #{TestApp.current_path}")
73
+
74
+ stdout.strip
75
+ end
76
+ end
77
+
78
+ Given(/^(\d+) valid existing releases$/) do |num|
79
+ a_day = 86_400 # in seconds
80
+ (1...num).each_slice(100) do |num_batch|
81
+ dirs = num_batch.map do |i|
82
+ offset = -(a_day * i)
83
+ TestApp.release_path(TestApp.timestamp(offset))
84
+ end
85
+ run_remote_ssh_command("mkdir -p #{dirs.join(' ')}")
86
+ end
87
+ end
88
+
89
+ Given(/^an invalid release named "(.+)"$/) do |filename|
90
+ run_remote_ssh_command("mkdir -p #{TestApp.release_path(filename)}")
91
+ end
@@ -0,0 +1,9 @@
1
+ Feature: cap can be run from a subdirectory, and will still find the Capfile
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+ And servers with the roles app and web
6
+
7
+ Scenario: Running cap from a subdirectory
8
+ When I run cap "git:check" within the "config" directory
9
+ Then the task is successful
@@ -0,0 +1,53 @@
1
+ # Ensure Docker container is completely stopped when Ruby exits.
2
+ at_exit do
3
+ DockerGateway.new.stop
4
+ end
5
+
6
+ # Manages the Docker-based SSH server that is declared in docker-compose.yml.
7
+ class DockerGateway
8
+ def initialize(log_proc=$stderr.method(:puts))
9
+ @log_proc = log_proc
10
+ end
11
+
12
+ def start
13
+ run_compose_command("up -d")
14
+ end
15
+
16
+ def stop
17
+ run_compose_command("down")
18
+ end
19
+
20
+ def run_shell_command(command)
21
+ run_compose_command("exec ssh_server /bin/bash -c #{command.shellescape}")
22
+ end
23
+
24
+ private
25
+
26
+ def run_compose_command(command)
27
+ log "[docker compose] #{command}"
28
+ stdout, stderr, status = Open3.popen3("docker compose #{command}") do |stdin, stdout, stderr, wait_threads|
29
+ stdin << ""
30
+ stdin.close
31
+ out = Thread.new { read_lines(stdout, &$stdout.method(:puts)) }
32
+ err = Thread.new { stderr.read }
33
+ [out.value, err.value.to_s, wait_threads.value]
34
+ end
35
+
36
+ (stdout + stderr).each_line { |line| log "[docker compose] #{line}" }
37
+
38
+ [stdout, stderr, status]
39
+ end
40
+
41
+ def read_lines(io)
42
+ buffer = + ""
43
+ while (line = io.gets)
44
+ buffer << line
45
+ yield line
46
+ end
47
+ buffer
48
+ end
49
+
50
+ def log(message)
51
+ @log_proc.call(message)
52
+ end
53
+ end
@@ -0,0 +1 @@
1
+ require_relative "../../spec/support/test_app"
@@ -0,0 +1,29 @@
1
+ module RemoteCommandHelpers
2
+ def test_dir_exists(path)
3
+ exists?("d", path)
4
+ end
5
+
6
+ def test_symlink_exists(path)
7
+ exists?("L", path)
8
+ end
9
+
10
+ def test_file_exists(path)
11
+ exists?("f", path)
12
+ end
13
+
14
+ def exists?(type, path)
15
+ %Q{[[ -#{type} "#{path}" ]]}
16
+ end
17
+
18
+ def symlinked?(symlink_path, target_path)
19
+ "[ #{symlink_path} -ef #{target_path} ]"
20
+ end
21
+
22
+ def safely_remove_file(_path)
23
+ run_remote_ssh_command("rm #{test_file}")
24
+ rescue
25
+ RemoteSSHHelpers::RemoteSSHCommandError
26
+ end
27
+ end
28
+
29
+ World(RemoteCommandHelpers)
@@ -0,0 +1,33 @@
1
+ require "open3"
2
+ require "socket"
3
+ require_relative "docker_gateway"
4
+
5
+ module RemoteSSHHelpers
6
+ extend self
7
+
8
+ class RemoteSSHCommandError < RuntimeError; end
9
+
10
+ def start_ssh_server
11
+ docker_gateway.start
12
+ end
13
+
14
+ def wait_for_ssh_server(retries=3)
15
+ Socket.tcp("localhost", 2022, connect_timeout: 1).close
16
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
17
+ retries -= 1
18
+ sleep(2) && retry if retries.positive?
19
+ raise
20
+ end
21
+
22
+ def run_remote_ssh_command(command)
23
+ stdout, stderr, status = docker_gateway.run_shell_command(command)
24
+ return [stdout, stderr] if status.success?
25
+ raise RemoteSSHCommandError, status
26
+ end
27
+
28
+ def docker_gateway
29
+ @docker_gateway ||= DockerGateway.new(method(:log))
30
+ end
31
+ end
32
+
33
+ World(RemoteSSHHelpers)
data/lib/Capfile ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env cap
2
+ include Capistrano::DSL
3
+ require "capistrano/install"
@@ -0,0 +1,17 @@
1
+ require "rake"
2
+ require "sshkit"
3
+
4
+ require "io/console"
5
+
6
+ Rake.application.options.trace = true
7
+
8
+ require "capistrano/version"
9
+ require "capistrano/version_validator"
10
+ require "capistrano/i18n"
11
+ require "capistrano/dsl"
12
+ require "capistrano/application"
13
+ require "capistrano/configuration"
14
+ require "capistrano/configuration/scm_resolver"
15
+
16
+ module Capistrano
17
+ end
@@ -0,0 +1,153 @@
1
+ module Capistrano
2
+ class Application < Rake::Application
3
+ def initialize
4
+ super
5
+ @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb}
6
+ end
7
+
8
+ def name
9
+ "cap"
10
+ end
11
+
12
+ def run
13
+ Rake.application = self
14
+ super
15
+ end
16
+
17
+ def sort_options(options)
18
+ not_applicable_to_capistrano = %w(quiet silent verbose)
19
+ options.reject! do |(switch, *)|
20
+ switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/
21
+ end
22
+
23
+ super.push(version, dry_run, roles, hostfilter, print_config_variables)
24
+ end
25
+
26
+ def handle_options
27
+ options.rakelib = ["rakelib"]
28
+ options.trace_output = $stderr
29
+
30
+ OptionParser.new do |opts|
31
+ opts.banner = "See full documentation at http://capistranorb.com/."
32
+ opts.separator ""
33
+ opts.separator "Install capistrano in a project:"
34
+ opts.separator " bundle exec cap install [STAGES=qa,staging,production,...]"
35
+ opts.separator ""
36
+ opts.separator "Show available tasks:"
37
+ opts.separator " bundle exec cap -T"
38
+ opts.separator ""
39
+ opts.separator "Invoke (or simulate invoking) a task:"
40
+ opts.separator " bundle exec cap [--dry-run] STAGE TASK"
41
+ opts.separator ""
42
+ opts.separator "Advanced options:"
43
+
44
+ opts.on_tail("-h", "--help", "-H", "Display this help message.") do
45
+ puts opts
46
+ exit
47
+ end
48
+
49
+ standard_rake_options.each { |args| opts.on(*args) }
50
+ opts.environment("RAKEOPT")
51
+ end.parse!
52
+ end
53
+
54
+ def top_level_tasks
55
+ if tasks_without_stage_dependency.include?(@top_level_tasks.first)
56
+ @top_level_tasks
57
+ else
58
+ @top_level_tasks.unshift(ensure_stage.to_s)
59
+ end
60
+ end
61
+
62
+ def display_error_message(ex)
63
+ unless options.backtrace
64
+ Rake.application.options.suppress_backtrace_pattern = backtrace_pattern if backtrace_pattern
65
+ trace "(Backtrace restricted to imported tasks)"
66
+ end
67
+
68
+ super
69
+ end
70
+
71
+ def exit_because_of_exception(ex)
72
+ if respond_to?(:deploying?) && deploying?
73
+ exit_deploy_because_of_exception(ex)
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # allows the `cap install` task to load without a capfile
80
+ def find_rakefile_location
81
+ if (location = super).nil?
82
+ [capfile, Dir.pwd]
83
+ else
84
+ location
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def backtrace_pattern
91
+ loc = Rake.application.find_rakefile_location
92
+ return unless loc
93
+
94
+ whitelist = (@imported.dup << loc[0]).map { |f| File.absolute_path(f, loc[1]) }
95
+ /^(?!#{whitelist.map { |p| Regexp.quote(p) }.join('|')})/
96
+ end
97
+
98
+ def load_imports
99
+ if options.show_tasks && Rake::Task.task_defined?("load:defaults")
100
+ invoke "load:defaults"
101
+ set(:stage, "")
102
+ Dir[deploy_config_path].each { |f| add_import f }
103
+ end
104
+
105
+ super
106
+ end
107
+
108
+ def capfile
109
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "Capfile"))
110
+ end
111
+
112
+ def version
113
+ ["--version", "-V",
114
+ "Display the program version.",
115
+ lambda do |_value|
116
+ puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{Rake::VERSION})"
117
+ exit
118
+ end]
119
+ end
120
+
121
+ def dry_run
122
+ ["--dry-run", "-n",
123
+ "Do a dry run without executing actions",
124
+ lambda do |_value|
125
+ Configuration.env.set(:sshkit_backend, SSHKit::Backend::Printer)
126
+ end]
127
+ end
128
+
129
+ def roles
130
+ ["--roles ROLES", "-r",
131
+ "Run SSH commands only on hosts matching these roles",
132
+ lambda do |value|
133
+ Configuration.env.add_cmdline_filter(:role, value)
134
+ end]
135
+ end
136
+
137
+ def hostfilter
138
+ ["--hosts HOSTS", "-z",
139
+ "Run SSH commands only on matching hosts",
140
+ lambda do |value|
141
+ Configuration.env.add_cmdline_filter(:host, value)
142
+ end]
143
+ end
144
+
145
+ def print_config_variables
146
+ ["--print-config-variables", "-p",
147
+ "Display the defined config variables before starting the deployment tasks.",
148
+ lambda do |_value|
149
+ Configuration.env.set(:print_config_variables, true)
150
+ end]
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,9 @@
1
+ module Capistrano
2
+ class Configuration
3
+ class EmptyFilter
4
+ def filter(_servers)
5
+ []
6
+ end
7
+ end
8
+ end
9
+ end