capistrano 2.8.0 → 3.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.docker/Dockerfile +7 -0
- data/.docker/ssh_key_rsa +49 -0
- data/.docker/ssh_key_rsa.pub +1 -0
- data/.docker/ubuntu_setup.sh +23 -0
- data/.github/issue_template.md +19 -0
- data/.github/pull_request_template.md +22 -0
- data/.github/release-drafter.yml +25 -0
- data/.github/workflows/ci.yml +80 -0
- data/.github/workflows/release-drafter.yml +18 -0
- data/.gitignore +23 -8
- data/.rubocop.yml +62 -0
- data/CHANGELOG.md +1 -0
- data/CONTRIBUTING.md +63 -0
- data/DEVELOPMENT.md +112 -0
- data/Gemfile +42 -9
- data/LICENSE.txt +21 -0
- data/README.md +221 -0
- data/RELEASING.md +17 -0
- data/Rakefile +17 -8
- data/UPGRADING-3.7.md +86 -0
- data/bin/cap +2 -3
- data/bin/capify +7 -89
- data/capistrano.gemspec +29 -43
- data/docker-compose.yml +8 -0
- data/features/configuration.feature +28 -0
- data/features/deploy.feature +92 -0
- data/features/deploy_failure.feature +17 -0
- data/features/doctor.feature +11 -0
- data/features/installation.feature +21 -0
- data/features/sshconnect.feature +11 -0
- data/features/stage_failure.feature +9 -0
- data/features/step_definitions/assertions.rb +162 -0
- data/features/step_definitions/cap_commands.rb +21 -0
- data/features/step_definitions/setup.rb +91 -0
- data/features/subdirectory.feature +9 -0
- data/features/support/docker_gateway.rb +53 -0
- data/features/support/env.rb +1 -0
- data/features/support/remote_command_helpers.rb +29 -0
- data/features/support/remote_ssh_helpers.rb +33 -0
- data/lib/Capfile +3 -0
- data/lib/capistrano/all.rb +17 -0
- data/lib/capistrano/application.rb +153 -0
- data/lib/capistrano/configuration/empty_filter.rb +9 -0
- data/lib/capistrano/configuration/filter.rb +26 -0
- data/lib/capistrano/configuration/host_filter.rb +29 -0
- data/lib/capistrano/configuration/null_filter.rb +9 -0
- data/lib/capistrano/configuration/plugin_installer.rb +51 -0
- data/lib/capistrano/configuration/question.rb +76 -0
- data/lib/capistrano/configuration/role_filter.rb +29 -0
- data/lib/capistrano/configuration/scm_resolver.rb +149 -0
- data/lib/capistrano/configuration/server.rb +137 -0
- data/lib/capistrano/configuration/servers.rb +56 -96
- data/lib/capistrano/configuration/validated_variables.rb +110 -0
- data/lib/capistrano/configuration/variables.rb +79 -94
- data/lib/capistrano/configuration.rb +178 -33
- data/lib/capistrano/console.rb +1 -0
- data/lib/capistrano/defaults.rb +36 -0
- data/lib/capistrano/deploy.rb +3 -0
- data/lib/capistrano/doctor/environment_doctor.rb +19 -0
- data/lib/capistrano/doctor/gems_doctor.rb +45 -0
- data/lib/capistrano/doctor/output_helpers.rb +79 -0
- data/lib/capistrano/doctor/servers_doctor.rb +105 -0
- data/lib/capistrano/doctor/variables_doctor.rb +74 -0
- data/lib/capistrano/doctor.rb +6 -0
- data/lib/capistrano/dotfile.rb +2 -0
- data/lib/capistrano/dsl/env.rb +43 -0
- data/lib/capistrano/dsl/paths.rb +89 -0
- data/lib/capistrano/dsl/stages.rb +31 -0
- data/lib/capistrano/dsl/task_enhancements.rb +61 -0
- data/lib/capistrano/dsl.rb +95 -0
- data/lib/capistrano/framework.rb +2 -0
- data/lib/capistrano/i18n.rb +46 -0
- data/lib/capistrano/immutable_task.rb +30 -0
- data/lib/capistrano/install.rb +1 -0
- data/lib/capistrano/plugin.rb +95 -0
- data/lib/capistrano/proc_helpers.rb +13 -0
- data/lib/capistrano/scm/git.rb +105 -0
- data/lib/capistrano/scm/hg.rb +55 -0
- data/lib/capistrano/scm/plugin.rb +13 -0
- data/lib/capistrano/scm/svn.rb +56 -0
- data/lib/capistrano/scm/tasks/git.rake +84 -0
- data/lib/capistrano/scm/tasks/hg.rake +53 -0
- data/lib/capistrano/scm/tasks/svn.rake +53 -0
- data/lib/capistrano/scm.rb +115 -0
- data/lib/capistrano/setup.rb +36 -0
- data/lib/capistrano/tasks/console.rake +25 -0
- data/lib/capistrano/tasks/deploy.rake +280 -0
- data/lib/capistrano/tasks/doctor.rake +24 -0
- data/lib/capistrano/tasks/framework.rake +67 -0
- data/lib/capistrano/tasks/install.rake +41 -0
- data/lib/capistrano/templates/Capfile +38 -0
- data/lib/capistrano/templates/deploy.rb.erb +39 -0
- data/lib/capistrano/templates/stage.rb.erb +61 -0
- data/lib/capistrano/upload_task.rb +9 -0
- data/lib/capistrano/version.rb +1 -14
- data/lib/capistrano/version_validator.rb +32 -0
- data/lib/capistrano.rb +0 -3
- data/spec/integration/dsl_spec.rb +632 -0
- data/spec/integration_spec_helper.rb +5 -0
- data/spec/lib/capistrano/application_spec.rb +60 -0
- data/spec/lib/capistrano/configuration/empty_filter_spec.rb +17 -0
- data/spec/lib/capistrano/configuration/filter_spec.rb +109 -0
- data/spec/lib/capistrano/configuration/host_filter_spec.rb +71 -0
- data/spec/lib/capistrano/configuration/null_filter_spec.rb +17 -0
- data/spec/lib/capistrano/configuration/plugin_installer_spec.rb +98 -0
- data/spec/lib/capistrano/configuration/question_spec.rb +92 -0
- data/spec/lib/capistrano/configuration/role_filter_spec.rb +80 -0
- data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +56 -0
- data/spec/lib/capistrano/configuration/server_spec.rb +309 -0
- data/spec/lib/capistrano/configuration/servers_spec.rb +331 -0
- data/spec/lib/capistrano/configuration_spec.rb +357 -0
- data/spec/lib/capistrano/doctor/environment_doctor_spec.rb +44 -0
- data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +67 -0
- data/spec/lib/capistrano/doctor/output_helpers_spec.rb +47 -0
- data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
- data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +89 -0
- data/spec/lib/capistrano/dsl/paths_spec.rb +228 -0
- data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +108 -0
- data/spec/lib/capistrano/dsl_spec.rb +125 -0
- data/spec/lib/capistrano/immutable_task_spec.rb +31 -0
- data/spec/lib/capistrano/plugin_spec.rb +84 -0
- data/spec/lib/capistrano/scm/git_spec.rb +194 -0
- data/spec/lib/capistrano/scm/hg_spec.rb +109 -0
- data/spec/lib/capistrano/scm/svn_spec.rb +137 -0
- data/spec/lib/capistrano/scm_spec.rb +103 -0
- data/spec/lib/capistrano/upload_task_spec.rb +19 -0
- data/spec/lib/capistrano/version_validator_spec.rb +118 -0
- data/spec/lib/capistrano_spec.rb +7 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/matchers.rb +5 -0
- data/spec/support/tasks/database.rake +11 -0
- data/spec/support/tasks/fail.rake +8 -0
- data/spec/support/tasks/failed.rake +5 -0
- data/spec/support/tasks/plugin.rake +6 -0
- data/spec/support/tasks/root.rake +11 -0
- data/spec/support/test_app.rb +205 -0
- metadata +234 -208
- data/.rvmrc +0 -1
- data/CHANGELOG +0 -954
- data/README.mdown +0 -76
- data/lib/capistrano/callback.rb +0 -45
- data/lib/capistrano/cli/execute.rb +0 -85
- data/lib/capistrano/cli/help.rb +0 -125
- data/lib/capistrano/cli/help.txt +0 -81
- data/lib/capistrano/cli/options.rb +0 -243
- data/lib/capistrano/cli/ui.rb +0 -40
- data/lib/capistrano/cli.rb +0 -47
- data/lib/capistrano/command.rb +0 -286
- data/lib/capistrano/configuration/actions/file_transfer.rb +0 -51
- data/lib/capistrano/configuration/actions/inspect.rb +0 -46
- data/lib/capistrano/configuration/actions/invocation.rb +0 -298
- data/lib/capistrano/configuration/callbacks.rb +0 -148
- data/lib/capistrano/configuration/connections.rb +0 -230
- data/lib/capistrano/configuration/execution.rb +0 -143
- data/lib/capistrano/configuration/loading.rb +0 -197
- data/lib/capistrano/configuration/namespaces.rb +0 -197
- data/lib/capistrano/configuration/roles.rb +0 -73
- data/lib/capistrano/errors.rb +0 -19
- data/lib/capistrano/ext/string.rb +0 -5
- data/lib/capistrano/extensions.rb +0 -57
- data/lib/capistrano/logger.rb +0 -59
- data/lib/capistrano/processable.rb +0 -53
- data/lib/capistrano/recipes/compat.rb +0 -32
- data/lib/capistrano/recipes/deploy/assets.rb +0 -57
- data/lib/capistrano/recipes/deploy/dependencies.rb +0 -44
- data/lib/capistrano/recipes/deploy/local_dependency.rb +0 -54
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +0 -111
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +0 -169
- data/lib/capistrano/recipes/deploy/scm/base.rb +0 -196
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +0 -86
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +0 -153
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +0 -96
- data/lib/capistrano/recipes/deploy/scm/git.rb +0 -282
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +0 -137
- data/lib/capistrano/recipes/deploy/scm/none.rb +0 -44
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +0 -138
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +0 -121
- data/lib/capistrano/recipes/deploy/scm.rb +0 -19
- data/lib/capistrano/recipes/deploy/strategy/base.rb +0 -88
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +0 -20
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +0 -224
- data/lib/capistrano/recipes/deploy/strategy/export.rb +0 -20
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +0 -52
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +0 -57
- data/lib/capistrano/recipes/deploy/strategy.rb +0 -19
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/deploy.rb +0 -568
- data/lib/capistrano/recipes/standard.rb +0 -37
- data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/role.rb +0 -102
- data/lib/capistrano/server_definition.rb +0 -56
- data/lib/capistrano/shell.rb +0 -260
- data/lib/capistrano/ssh.rb +0 -101
- data/lib/capistrano/task_definition.rb +0 -75
- data/lib/capistrano/transfer.rb +0 -216
- data/rvmrc.sample +0 -1
- data/test/cli/execute_test.rb +0 -132
- data/test/cli/help_test.rb +0 -165
- data/test/cli/options_test.rb +0 -329
- data/test/cli/ui_test.rb +0 -28
- data/test/cli_test.rb +0 -17
- data/test/command_test.rb +0 -289
- data/test/configuration/actions/file_transfer_test.rb +0 -61
- data/test/configuration/actions/inspect_test.rb +0 -65
- data/test/configuration/actions/invocation_test.rb +0 -247
- data/test/configuration/callbacks_test.rb +0 -220
- data/test/configuration/connections_test.rb +0 -420
- data/test/configuration/execution_test.rb +0 -175
- data/test/configuration/loading_test.rb +0 -132
- data/test/configuration/namespace_dsl_test.rb +0 -311
- data/test/configuration/roles_test.rb +0 -144
- data/test/configuration/servers_test.rb +0 -183
- data/test/configuration/variables_test.rb +0 -190
- data/test/configuration_test.rb +0 -88
- data/test/deploy/local_dependency_test.rb +0 -76
- data/test/deploy/remote_dependency_test.rb +0 -135
- data/test/deploy/scm/accurev_test.rb +0 -23
- data/test/deploy/scm/base_test.rb +0 -55
- data/test/deploy/scm/bzr_test.rb +0 -51
- data/test/deploy/scm/darcs_test.rb +0 -37
- data/test/deploy/scm/git_test.rb +0 -184
- data/test/deploy/scm/mercurial_test.rb +0 -134
- data/test/deploy/scm/none_test.rb +0 -35
- data/test/deploy/scm/subversion_test.rb +0 -32
- data/test/deploy/strategy/copy_test.rb +0 -321
- data/test/extensions_test.rb +0 -69
- data/test/fixtures/cli_integration.rb +0 -5
- data/test/fixtures/config.rb +0 -5
- data/test/fixtures/custom.rb +0 -3
- data/test/logger_test.rb +0 -123
- data/test/recipes_test.rb +0 -25
- data/test/role_test.rb +0 -11
- data/test/server_definition_test.rb +0 -121
- data/test/shell_test.rb +0 -90
- data/test/ssh_test.rb +0 -113
- data/test/task_definition_test.rb +0 -116
- data/test/transfer_test.rb +0 -160
- 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,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,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
|