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.
- checksums.yaml +5 -5
- data/.circleci/config.yml +129 -0
- data/.github/issue_template.md +19 -0
- data/.github/pull_request_template.md +22 -0
- data/.github/release-drafter.yml +17 -0
- data/.github/workflows/push.yml +12 -0
- data/.gitignore +8 -5
- data/.rubocop.yml +62 -0
- data/CHANGELOG.md +1 -307
- data/CONTRIBUTING.md +63 -93
- data/DEVELOPMENT.md +127 -0
- data/Dangerfile +1 -0
- data/Gemfile +40 -3
- data/LICENSE.txt +1 -1
- data/README.md +127 -44
- data/RELEASING.md +17 -0
- data/Rakefile +13 -2
- data/UPGRADING-3.7.md +86 -0
- data/bin/cap +1 -1
- data/capistrano.gemspec +21 -24
- data/features/deploy.feature +35 -1
- data/features/doctor.feature +11 -0
- data/features/installation.feature +8 -3
- data/features/stage_failure.feature +9 -0
- data/features/step_definitions/assertions.rb +51 -18
- data/features/step_definitions/cap_commands.rb +9 -0
- data/features/step_definitions/setup.rb +53 -9
- data/features/subdirectory.feature +9 -0
- data/features/support/env.rb +5 -5
- data/features/support/remote_command_helpers.rb +12 -6
- data/features/support/vagrant_helpers.rb +17 -11
- data/lib/Capfile +1 -1
- data/lib/capistrano/all.rb +10 -10
- data/lib/capistrano/application.rb +47 -34
- data/lib/capistrano/configuration/empty_filter.rb +9 -0
- data/lib/capistrano/configuration/filter.rb +17 -47
- 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 +31 -9
- data/lib/capistrano/configuration/role_filter.rb +29 -0
- data/lib/capistrano/configuration/scm_resolver.rb +149 -0
- data/lib/capistrano/configuration/server.rb +29 -23
- data/lib/capistrano/configuration/servers.rb +21 -14
- data/lib/capistrano/configuration/validated_variables.rb +110 -0
- data/lib/capistrano/configuration/variables.rb +112 -0
- data/lib/capistrano/configuration.rb +91 -44
- data/lib/capistrano/defaults.rb +26 -4
- data/lib/capistrano/deploy.rb +1 -1
- 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 +1 -2
- data/lib/capistrano/dsl/env.rb +9 -47
- data/lib/capistrano/dsl/paths.rb +11 -25
- data/lib/capistrano/dsl/stages.rb +14 -2
- data/lib/capistrano/dsl/task_enhancements.rb +7 -12
- data/lib/capistrano/dsl.rb +47 -16
- data/lib/capistrano/framework.rb +1 -1
- data/lib/capistrano/i18n.rb +32 -24
- data/lib/capistrano/immutable_task.rb +30 -0
- data/lib/capistrano/install.rb +1 -1
- data/lib/capistrano/plugin.rb +95 -0
- data/lib/capistrano/proc_helpers.rb +13 -0
- data/lib/capistrano/scm/git.rb +100 -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 +73 -0
- data/lib/capistrano/scm/tasks/hg.rake +53 -0
- data/lib/capistrano/scm/tasks/svn.rake +53 -0
- data/lib/capistrano/scm.rb +7 -20
- data/lib/capistrano/setup.rb +20 -6
- data/lib/capistrano/tasks/console.rake +4 -8
- data/lib/capistrano/tasks/deploy.rake +105 -73
- data/lib/capistrano/tasks/doctor.rake +24 -0
- data/lib/capistrano/tasks/framework.rake +13 -14
- data/lib/capistrano/tasks/install.rake +14 -15
- data/lib/capistrano/templates/Capfile +21 -10
- data/lib/capistrano/templates/deploy.rb.erb +17 -26
- data/lib/capistrano/templates/stage.rb.erb +9 -9
- data/lib/capistrano/upload_task.rb +1 -1
- data/lib/capistrano/version.rb +1 -1
- data/lib/capistrano/version_validator.rb +5 -10
- data/spec/integration/dsl_spec.rb +289 -240
- data/spec/integration_spec_helper.rb +3 -5
- data/spec/lib/capistrano/application_spec.rb +23 -39
- data/spec/lib/capistrano/configuration/empty_filter_spec.rb +17 -0
- data/spec/lib/capistrano/configuration/filter_spec.rb +83 -85
- 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 +58 -26
- data/spec/lib/capistrano/configuration/role_filter_spec.rb +80 -0
- data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +55 -0
- data/spec/lib/capistrano/configuration/server_spec.rb +106 -113
- data/spec/lib/capistrano/configuration/servers_spec.rb +129 -145
- data/spec/lib/capistrano/configuration_spec.rb +224 -63
- 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 +97 -59
- data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +57 -37
- data/spec/lib/capistrano/dsl_spec.rb +84 -11
- 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 +184 -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 +7 -8
- data/spec/lib/capistrano/upload_task_spec.rb +7 -7
- data/spec/lib/capistrano/version_validator_spec.rb +61 -46
- data/spec/lib/capistrano_spec.rb +2 -3
- data/spec/spec_helper.rb +21 -8
- data/spec/support/Vagrantfile +9 -10
- data/spec/support/tasks/database.rake +3 -3
- data/spec/support/tasks/fail.rake +4 -3
- data/spec/support/tasks/failed.rake +2 -2
- data/spec/support/tasks/plugin.rake +6 -0
- data/spec/support/tasks/root.rake +4 -4
- data/spec/support/test_app.rb +64 -39
- metadata +100 -55
- data/.travis.yml +0 -13
- data/features/remote_file_task.feature +0 -14
- data/lib/capistrano/git.rb +0 -46
- data/lib/capistrano/hg.rb +0 -43
- data/lib/capistrano/svn.rb +0 -38
- data/lib/capistrano/tasks/git.rake +0 -81
- data/lib/capistrano/tasks/hg.rake +0 -52
- data/lib/capistrano/tasks/svn.rake +0 -52
- data/spec/lib/capistrano/git_spec.rb +0 -81
- data/spec/lib/capistrano/hg_spec.rb +0 -81
- data/spec/lib/capistrano/svn_spec.rb +0 -79
|
@@ -1,148 +1,177 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "spec_helper"
|
|
2
2
|
|
|
3
3
|
describe Capistrano::DSL do
|
|
4
|
-
|
|
5
4
|
let(:dsl) { Class.new.extend Capistrano::DSL }
|
|
6
5
|
|
|
7
6
|
before do
|
|
8
7
|
Capistrano::Configuration.reset!
|
|
9
8
|
end
|
|
10
9
|
|
|
11
|
-
describe
|
|
12
|
-
describe
|
|
10
|
+
describe "setting and fetching hosts" do
|
|
11
|
+
describe "when defining a host using the `server` syntax" do
|
|
13
12
|
before do
|
|
14
|
-
dsl.server
|
|
15
|
-
dsl.server
|
|
16
|
-
dsl.server
|
|
17
|
-
dsl.server
|
|
18
|
-
dsl.server
|
|
13
|
+
dsl.server "example1.com", roles: %w{web}, active: true
|
|
14
|
+
dsl.server "example2.com", roles: %w{web}
|
|
15
|
+
dsl.server "example3.com", roles: %w{app web}, active: true
|
|
16
|
+
dsl.server "example4.com", roles: %w{app}, primary: true
|
|
17
|
+
dsl.server "example5.com", roles: %w{db}, no_release: true, active: true
|
|
19
18
|
end
|
|
20
19
|
|
|
21
|
-
describe
|
|
20
|
+
describe "fetching all servers" do
|
|
22
21
|
subject { dsl.roles(:all) }
|
|
23
22
|
|
|
24
|
-
it
|
|
23
|
+
it "returns all servers" do
|
|
25
24
|
expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com}
|
|
26
25
|
end
|
|
27
26
|
end
|
|
28
27
|
|
|
29
|
-
describe
|
|
30
|
-
|
|
31
|
-
context 'with no additional options' do
|
|
28
|
+
describe "fetching all release servers" do
|
|
29
|
+
context "with no additional options" do
|
|
32
30
|
subject { dsl.release_roles(:all) }
|
|
33
31
|
|
|
34
|
-
it
|
|
32
|
+
it "returns all release servers" do
|
|
35
33
|
expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com}
|
|
36
34
|
end
|
|
37
35
|
end
|
|
38
36
|
|
|
39
|
-
context
|
|
37
|
+
context "with property filter options" do
|
|
40
38
|
subject { dsl.release_roles(:all, filter: :active) }
|
|
41
39
|
|
|
42
|
-
it
|
|
40
|
+
it "returns all release servers that match the property filter" do
|
|
43
41
|
expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
|
|
44
42
|
end
|
|
45
43
|
end
|
|
46
44
|
end
|
|
47
45
|
|
|
48
|
-
describe
|
|
46
|
+
describe "fetching servers by multiple roles" do
|
|
49
47
|
it "does not confuse the last role with options" do
|
|
50
48
|
expect(dsl.roles(:app, :web).count).to eq 4
|
|
51
49
|
expect(dsl.roles(:app, :web, filter: :active).count).to eq 2
|
|
52
50
|
end
|
|
53
51
|
end
|
|
54
52
|
|
|
55
|
-
describe
|
|
53
|
+
describe "fetching servers by role" do
|
|
56
54
|
subject { dsl.roles(:app) }
|
|
57
55
|
|
|
58
|
-
it
|
|
56
|
+
it "returns the servers" do
|
|
59
57
|
expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
describe
|
|
61
|
+
describe "fetching servers by an array of roles" do
|
|
64
62
|
subject { dsl.roles([:app]) }
|
|
65
63
|
|
|
66
|
-
it
|
|
64
|
+
it "returns the servers" do
|
|
67
65
|
expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
|
|
68
66
|
end
|
|
69
67
|
end
|
|
70
68
|
|
|
71
|
-
describe
|
|
69
|
+
describe "fetching filtered servers by role" do
|
|
72
70
|
subject { dsl.roles(:app, filter: :active) }
|
|
73
71
|
|
|
74
|
-
it
|
|
72
|
+
it "returns the servers" do
|
|
75
73
|
expect(subject.map(&:hostname)).to eq %w{example3.com}
|
|
76
74
|
end
|
|
77
75
|
end
|
|
78
76
|
|
|
79
|
-
describe
|
|
77
|
+
describe "fetching selected servers by role" do
|
|
80
78
|
subject { dsl.roles(:app, select: :active) }
|
|
81
79
|
|
|
82
|
-
it
|
|
80
|
+
it "returns the servers" do
|
|
83
81
|
expect(subject.map(&:hostname)).to eq %w{example3.com}
|
|
84
82
|
end
|
|
85
83
|
end
|
|
86
84
|
|
|
87
|
-
describe
|
|
88
|
-
context
|
|
85
|
+
describe "fetching the primary server by role" do
|
|
86
|
+
context "when inferring primary status based on order" do
|
|
89
87
|
subject { dsl.primary(:web) }
|
|
90
|
-
it
|
|
91
|
-
expect(subject.hostname).to eq
|
|
88
|
+
it "returns the servers" do
|
|
89
|
+
expect(subject.hostname).to eq "example1.com"
|
|
92
90
|
end
|
|
93
91
|
end
|
|
94
92
|
|
|
95
|
-
context
|
|
93
|
+
context "when the attribute `primary` is explicitly set" do
|
|
96
94
|
subject { dsl.primary(:app) }
|
|
97
|
-
it
|
|
98
|
-
expect(subject.hostname).to eq
|
|
95
|
+
it "returns the servers" do
|
|
96
|
+
expect(subject.hostname).to eq "example4.com"
|
|
99
97
|
end
|
|
100
98
|
end
|
|
101
99
|
end
|
|
102
100
|
|
|
103
|
-
describe
|
|
101
|
+
describe "setting an internal host filter" do
|
|
104
102
|
subject { dsl.roles(:app) }
|
|
105
|
-
it
|
|
106
|
-
dsl.set :filter,
|
|
107
|
-
expect(subject.map(&:hostname)).to eq([
|
|
103
|
+
it "is ignored" do
|
|
104
|
+
dsl.set :filter, host: "example3.com"
|
|
105
|
+
expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
|
|
108
106
|
end
|
|
109
107
|
end
|
|
110
108
|
|
|
111
|
-
describe
|
|
109
|
+
describe "setting an internal role filter" do
|
|
112
110
|
subject { dsl.roles(:app) }
|
|
113
|
-
it
|
|
114
|
-
dsl.set :filter,
|
|
115
|
-
expect(subject.map(&:hostname)).to eq([
|
|
111
|
+
it "ignores it" do
|
|
112
|
+
dsl.set :filter, role: :web
|
|
113
|
+
expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
|
|
116
114
|
end
|
|
117
115
|
end
|
|
118
116
|
|
|
119
|
-
describe
|
|
117
|
+
describe "setting an internal host and role filter" do
|
|
120
118
|
subject { dsl.roles(:app) }
|
|
121
|
-
it
|
|
122
|
-
dsl.set :filter,
|
|
123
|
-
expect(subject.map(&:hostname)).to eq([
|
|
119
|
+
it "ignores it" do
|
|
120
|
+
dsl.set :filter, role: :web, host: "example1.com"
|
|
121
|
+
expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
|
|
124
122
|
end
|
|
125
123
|
end
|
|
126
124
|
|
|
127
|
-
describe
|
|
125
|
+
describe "setting an internal regexp host filter" do
|
|
128
126
|
subject { dsl.roles(:all) }
|
|
129
|
-
it
|
|
130
|
-
dsl.set :filter,
|
|
127
|
+
it "is ignored" do
|
|
128
|
+
dsl.set :filter, host: /1/
|
|
131
129
|
expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
|
|
132
130
|
end
|
|
133
131
|
end
|
|
134
132
|
|
|
133
|
+
describe "setting an internal hosts filter" do
|
|
134
|
+
subject { dsl.roles(:app) }
|
|
135
|
+
it "is ignored" do
|
|
136
|
+
dsl.set :filter, hosts: "example3.com"
|
|
137
|
+
expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe "setting an internal roles filter" do
|
|
142
|
+
subject { dsl.roles(:app) }
|
|
143
|
+
it "ignores it" do
|
|
144
|
+
dsl.set :filter, roles: :web
|
|
145
|
+
expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "setting an internal hosts and roles filter" do
|
|
150
|
+
subject { dsl.roles(:app) }
|
|
151
|
+
it "ignores it" do
|
|
152
|
+
dsl.set :filter, roles: :web, hosts: "example1.com"
|
|
153
|
+
expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe "setting an internal regexp hosts filter" do
|
|
158
|
+
subject { dsl.roles(:all) }
|
|
159
|
+
it "is ignored" do
|
|
160
|
+
dsl.set :filter, hosts: /1/
|
|
161
|
+
expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
|
|
162
|
+
end
|
|
163
|
+
end
|
|
135
164
|
end
|
|
136
165
|
|
|
137
|
-
describe
|
|
138
|
-
it
|
|
139
|
-
expect
|
|
166
|
+
describe "when defining role with reserved name" do
|
|
167
|
+
it "fails with ArgumentError" do
|
|
168
|
+
expect do
|
|
140
169
|
dsl.role :all, %w{example1.com}
|
|
141
|
-
|
|
170
|
+
end.to raise_error(ArgumentError, "all reserved name for role. Please choose another name")
|
|
142
171
|
end
|
|
143
172
|
end
|
|
144
173
|
|
|
145
|
-
describe
|
|
174
|
+
describe "when defining hosts using the `role` syntax" do
|
|
146
175
|
before do
|
|
147
176
|
dsl.role :web, %w{example1.com example2.com example3.com}
|
|
148
177
|
dsl.role :web, %w{example1.com}, active: true
|
|
@@ -152,243 +181,239 @@ describe Capistrano::DSL do
|
|
|
152
181
|
dsl.role :db, %w{example5.com}, no_release: true
|
|
153
182
|
end
|
|
154
183
|
|
|
155
|
-
describe
|
|
184
|
+
describe "fetching all servers" do
|
|
156
185
|
subject { dsl.roles(:all) }
|
|
157
186
|
|
|
158
|
-
it
|
|
187
|
+
it "returns all servers" do
|
|
159
188
|
expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com}
|
|
160
189
|
end
|
|
161
190
|
end
|
|
162
191
|
|
|
163
|
-
describe
|
|
164
|
-
|
|
165
|
-
context 'with no additional options' do
|
|
192
|
+
describe "fetching all release servers" do
|
|
193
|
+
context "with no additional options" do
|
|
166
194
|
subject { dsl.release_roles(:all) }
|
|
167
195
|
|
|
168
|
-
it
|
|
196
|
+
it "returns all release servers" do
|
|
169
197
|
expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com}
|
|
170
198
|
end
|
|
171
199
|
end
|
|
172
200
|
|
|
173
|
-
context
|
|
201
|
+
context "with filter options" do
|
|
174
202
|
subject { dsl.release_roles(:all, filter: :active) }
|
|
175
203
|
|
|
176
|
-
it
|
|
204
|
+
it "returns all release servers that match the filter" do
|
|
177
205
|
expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
|
|
178
206
|
end
|
|
179
207
|
end
|
|
180
208
|
end
|
|
181
209
|
|
|
182
|
-
|
|
183
|
-
describe 'fetching servers by role' do
|
|
210
|
+
describe "fetching servers by role" do
|
|
184
211
|
subject { dsl.roles(:app) }
|
|
185
212
|
|
|
186
|
-
it
|
|
213
|
+
it "returns the servers" do
|
|
187
214
|
expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
|
|
188
215
|
end
|
|
189
216
|
end
|
|
190
217
|
|
|
191
|
-
describe
|
|
218
|
+
describe "fetching servers by an array of roles" do
|
|
192
219
|
subject { dsl.roles([:app]) }
|
|
193
220
|
|
|
194
|
-
it
|
|
221
|
+
it "returns the servers" do
|
|
195
222
|
expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
|
|
196
223
|
end
|
|
197
224
|
end
|
|
198
225
|
|
|
199
|
-
describe
|
|
226
|
+
describe "fetching filtered servers by role" do
|
|
200
227
|
subject { dsl.roles(:app, filter: :active) }
|
|
201
228
|
|
|
202
|
-
it
|
|
229
|
+
it "returns the servers" do
|
|
203
230
|
expect(subject.map(&:hostname)).to eq %w{example3.com}
|
|
204
231
|
end
|
|
205
232
|
end
|
|
206
233
|
|
|
207
|
-
describe
|
|
234
|
+
describe "fetching selected servers by role" do
|
|
208
235
|
subject { dsl.roles(:app, select: :active) }
|
|
209
236
|
|
|
210
|
-
it
|
|
237
|
+
it "returns the servers" do
|
|
211
238
|
expect(subject.map(&:hostname)).to eq %w{example3.com}
|
|
212
239
|
end
|
|
213
240
|
end
|
|
214
241
|
|
|
215
|
-
describe
|
|
216
|
-
context
|
|
242
|
+
describe "fetching the primary server by role" do
|
|
243
|
+
context "when inferring primary status based on order" do
|
|
217
244
|
subject { dsl.primary(:web) }
|
|
218
|
-
it
|
|
219
|
-
expect(subject.hostname).to eq
|
|
245
|
+
it "returns the servers" do
|
|
246
|
+
expect(subject.hostname).to eq "example1.com"
|
|
220
247
|
end
|
|
221
248
|
end
|
|
222
249
|
|
|
223
|
-
context
|
|
250
|
+
context "when the attribute `primary` is explicity set" do
|
|
224
251
|
subject { dsl.primary(:app) }
|
|
225
|
-
it
|
|
226
|
-
expect(subject.hostname).to eq
|
|
252
|
+
it "returns the servers" do
|
|
253
|
+
expect(subject.hostname).to eq "example4.com"
|
|
227
254
|
end
|
|
228
255
|
end
|
|
229
256
|
end
|
|
230
|
-
|
|
231
257
|
end
|
|
232
258
|
|
|
233
|
-
describe
|
|
234
|
-
|
|
259
|
+
describe "when defining a host using a combination of the `server` and `role` syntax" do
|
|
235
260
|
before do
|
|
236
|
-
dsl.server
|
|
237
|
-
dsl.server
|
|
238
|
-
dsl.server
|
|
261
|
+
dsl.server "db@example1.com:1234", roles: %w{db}, active: true
|
|
262
|
+
dsl.server "root@example1.com:1234", roles: %w{web}, active: true
|
|
263
|
+
dsl.server "example1.com:5678", roles: %w{web}, active: true
|
|
239
264
|
dsl.role :app, %w{deployer@example1.com:1234}
|
|
240
265
|
dsl.role :app, %w{example1.com:5678}
|
|
241
266
|
end
|
|
242
267
|
|
|
243
|
-
describe
|
|
244
|
-
it
|
|
245
|
-
expect(dsl.roles(:all).size).to eq(
|
|
268
|
+
describe "fetching all servers" do
|
|
269
|
+
it "creates one server per hostname, ignoring user combinations" do
|
|
270
|
+
expect(dsl.roles(:all).size).to eq(2)
|
|
246
271
|
end
|
|
247
272
|
end
|
|
248
273
|
|
|
249
|
-
describe
|
|
250
|
-
it
|
|
274
|
+
describe "fetching servers for a role" do
|
|
275
|
+
it "roles defined using the `server` syntax are included" do
|
|
251
276
|
as = dsl.roles(:web).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
|
|
252
|
-
expect(as.size).to eq(
|
|
253
|
-
expect(as[0]).to eq("deployer@example1.com:
|
|
277
|
+
expect(as.size).to eq(2)
|
|
278
|
+
expect(as[0]).to eq("deployer@example1.com:1234")
|
|
279
|
+
expect(as[1]).to eq("@example1.com:5678")
|
|
254
280
|
end
|
|
255
281
|
|
|
256
|
-
it
|
|
282
|
+
it "roles defined using the `role` syntax are included" do
|
|
257
283
|
as = dsl.roles(:app).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
|
|
258
|
-
expect(as.size).to eq(
|
|
259
|
-
expect(as[0]).to eq("deployer@example1.com:
|
|
284
|
+
expect(as.size).to eq(2)
|
|
285
|
+
expect(as[0]).to eq("deployer@example1.com:1234")
|
|
286
|
+
expect(as[1]).to eq("@example1.com:5678")
|
|
260
287
|
end
|
|
261
288
|
end
|
|
262
|
-
|
|
263
289
|
end
|
|
264
290
|
|
|
265
|
-
describe
|
|
291
|
+
describe "when setting user and port" do
|
|
266
292
|
subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first }
|
|
267
293
|
|
|
268
294
|
describe "using the :user property" do
|
|
269
295
|
it "takes precedence over in the host string" do
|
|
270
|
-
dsl.server
|
|
296
|
+
dsl.server "db@example1.com:1234", roles: %w{db}, active: true, user: "brian"
|
|
271
297
|
expect(subject).to eq("brian@example1.com:1234")
|
|
272
298
|
end
|
|
273
299
|
end
|
|
274
300
|
|
|
275
301
|
describe "using the :port property" do
|
|
276
302
|
it "takes precedence over in the host string" do
|
|
277
|
-
dsl.server
|
|
303
|
+
dsl.server "db@example1.com:9090", roles: %w{db}, active: true, port: 1234
|
|
278
304
|
expect(subject).to eq("db@example1.com:1234")
|
|
279
305
|
end
|
|
280
306
|
end
|
|
281
307
|
end
|
|
282
|
-
|
|
283
308
|
end
|
|
284
309
|
|
|
285
|
-
describe
|
|
286
|
-
|
|
310
|
+
describe "setting and fetching variables" do
|
|
287
311
|
before do
|
|
288
312
|
dsl.set :scm, :git
|
|
289
313
|
end
|
|
290
314
|
|
|
291
|
-
context
|
|
292
|
-
context
|
|
293
|
-
it
|
|
315
|
+
context "without a default" do
|
|
316
|
+
context "when the variables is defined" do
|
|
317
|
+
it "returns the variable" do
|
|
294
318
|
expect(dsl.fetch(:scm)).to eq :git
|
|
295
319
|
end
|
|
296
320
|
end
|
|
297
321
|
|
|
298
|
-
context
|
|
299
|
-
it
|
|
322
|
+
context "when the variables is undefined" do
|
|
323
|
+
it "returns nil" do
|
|
300
324
|
expect(dsl.fetch(:source_control)).to be_nil
|
|
301
325
|
end
|
|
302
326
|
end
|
|
303
327
|
end
|
|
304
328
|
|
|
305
|
-
context
|
|
306
|
-
context
|
|
307
|
-
it
|
|
329
|
+
context "with a default" do
|
|
330
|
+
context "when the variables is defined" do
|
|
331
|
+
it "returns the variable" do
|
|
308
332
|
expect(dsl.fetch(:scm, :svn)).to eq :git
|
|
309
333
|
end
|
|
310
334
|
end
|
|
311
335
|
|
|
312
|
-
context
|
|
313
|
-
it
|
|
336
|
+
context "when the variables is undefined" do
|
|
337
|
+
it "returns the default" do
|
|
314
338
|
expect(dsl.fetch(:source_control, :svn)).to eq :svn
|
|
315
339
|
end
|
|
316
340
|
end
|
|
317
341
|
end
|
|
318
342
|
|
|
319
|
-
context
|
|
320
|
-
context
|
|
321
|
-
it
|
|
343
|
+
context "with a block" do
|
|
344
|
+
context "when the variables is defined" do
|
|
345
|
+
it "returns the variable" do
|
|
322
346
|
expect(dsl.fetch(:scm) { :svn }).to eq :git
|
|
323
347
|
end
|
|
324
348
|
end
|
|
325
349
|
|
|
326
|
-
context
|
|
327
|
-
it
|
|
350
|
+
context "when the variables is undefined" do
|
|
351
|
+
it "calls the block" do
|
|
328
352
|
expect(dsl.fetch(:source_control) { :svn }).to eq :svn
|
|
329
353
|
end
|
|
330
354
|
end
|
|
331
355
|
end
|
|
332
|
-
|
|
333
356
|
end
|
|
334
357
|
|
|
335
|
-
describe
|
|
358
|
+
describe "asking for a variable" do
|
|
359
|
+
let(:stdin) { stub(tty?: true) }
|
|
360
|
+
|
|
336
361
|
before do
|
|
337
|
-
dsl.ask(:scm, :svn)
|
|
362
|
+
dsl.ask(:scm, :svn, stdin: stdin)
|
|
338
363
|
$stdout.stubs(:print)
|
|
339
364
|
end
|
|
340
365
|
|
|
341
|
-
context
|
|
366
|
+
context "variable is provided" do
|
|
342
367
|
before do
|
|
343
|
-
|
|
368
|
+
stdin.expects(:gets).returns("git")
|
|
344
369
|
end
|
|
345
370
|
|
|
346
|
-
it
|
|
347
|
-
expect(dsl.fetch(:scm)).to eq
|
|
371
|
+
it "sets the input as the variable" do
|
|
372
|
+
expect(dsl.fetch(:scm)).to eq "git"
|
|
348
373
|
end
|
|
349
374
|
end
|
|
350
375
|
|
|
351
|
-
context
|
|
376
|
+
context "variable is not provided" do
|
|
352
377
|
before do
|
|
353
|
-
|
|
378
|
+
stdin.expects(:gets).returns("")
|
|
354
379
|
end
|
|
355
380
|
|
|
356
|
-
it
|
|
381
|
+
it "sets the variable as the default" do
|
|
357
382
|
expect(dsl.fetch(:scm)).to eq :svn
|
|
358
383
|
end
|
|
359
384
|
end
|
|
360
385
|
end
|
|
361
386
|
|
|
362
|
-
describe
|
|
387
|
+
describe "checking for presence" do
|
|
363
388
|
subject { dsl.any? :linked_files }
|
|
364
389
|
|
|
365
390
|
before do
|
|
366
391
|
dsl.set(:linked_files, linked_files)
|
|
367
392
|
end
|
|
368
393
|
|
|
369
|
-
context
|
|
394
|
+
context "variable is an non-empty array" do
|
|
370
395
|
let(:linked_files) { %w{1} }
|
|
371
396
|
|
|
372
397
|
it { expect(subject).to be_truthy }
|
|
373
398
|
end
|
|
374
399
|
|
|
375
|
-
context
|
|
400
|
+
context "variable is an empty array" do
|
|
376
401
|
let(:linked_files) { [] }
|
|
377
402
|
it { expect(subject).to be_falsey }
|
|
378
403
|
end
|
|
379
404
|
|
|
380
|
-
context
|
|
405
|
+
context "variable exists, is not an array" do
|
|
381
406
|
let(:linked_files) { stub }
|
|
382
407
|
it { expect(subject).to be_truthy }
|
|
383
408
|
end
|
|
384
409
|
|
|
385
|
-
context
|
|
410
|
+
context "variable is nil" do
|
|
386
411
|
let(:linked_files) { nil }
|
|
387
412
|
it { expect(subject).to be_falsey }
|
|
388
413
|
end
|
|
389
414
|
end
|
|
390
415
|
|
|
391
|
-
describe
|
|
416
|
+
describe "configuration SSHKit" do
|
|
392
417
|
let(:config) { SSHKit.config }
|
|
393
418
|
let(:backend) { SSHKit.config.backend.config }
|
|
394
419
|
let(:default_env) { { rails_env: :production } }
|
|
@@ -399,185 +424,209 @@ describe Capistrano::DSL do
|
|
|
399
424
|
dsl.set(:default_env, default_env)
|
|
400
425
|
dsl.set(:pty, true)
|
|
401
426
|
dsl.set(:connection_timeout, 10)
|
|
402
|
-
dsl.set(:ssh_options,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
auth_methods: %w(publickey password)
|
|
406
|
-
})
|
|
427
|
+
dsl.set(:ssh_options, keys: %w(/home/user/.ssh/id_rsa),
|
|
428
|
+
forward_agent: false,
|
|
429
|
+
auth_methods: %w(publickey password))
|
|
407
430
|
dsl.configure_backend
|
|
408
431
|
end
|
|
409
432
|
|
|
410
|
-
it
|
|
433
|
+
it "sets the output" do
|
|
411
434
|
expect(config.output).to be_a SSHKit::Formatter::Dot
|
|
412
435
|
end
|
|
413
436
|
|
|
414
|
-
it
|
|
437
|
+
it "sets the output verbosity" do
|
|
415
438
|
expect(config.output_verbosity).to eq 0
|
|
416
439
|
end
|
|
417
440
|
|
|
418
|
-
it
|
|
441
|
+
it "sets the default env" do
|
|
419
442
|
expect(config.default_env).to eq default_env
|
|
420
443
|
end
|
|
421
444
|
|
|
422
|
-
it
|
|
445
|
+
it "sets the backend pty" do
|
|
423
446
|
expect(backend.pty).to be_truthy
|
|
424
447
|
end
|
|
425
448
|
|
|
426
|
-
it
|
|
449
|
+
it "sets the backend connection timeout" do
|
|
427
450
|
expect(backend.connection_timeout).to eq 10
|
|
428
451
|
end
|
|
429
452
|
|
|
430
|
-
it
|
|
453
|
+
it "sets the backend ssh_options" do
|
|
431
454
|
expect(backend.ssh_options[:keys]).to eq %w(/home/user/.ssh/id_rsa)
|
|
432
455
|
expect(backend.ssh_options[:forward_agent]).to eq false
|
|
433
456
|
expect(backend.ssh_options[:auth_methods]).to eq %w(publickey password)
|
|
434
457
|
end
|
|
435
|
-
|
|
436
458
|
end
|
|
437
459
|
|
|
438
|
-
describe
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
460
|
+
describe "on()" do
|
|
461
|
+
describe "when passed server objects" do
|
|
462
|
+
before do
|
|
463
|
+
dsl.server "example1.com", roles: %w{web}, active: true
|
|
464
|
+
dsl.server "example2.com", roles: %w{web}
|
|
465
|
+
dsl.server "example3.com", roles: %w{app web}, active: true
|
|
466
|
+
dsl.server "example4.com", roles: %w{app}, primary: true
|
|
467
|
+
dsl.server "example5.com", roles: %w{db}, no_release: true
|
|
468
|
+
@coordinator = mock("coordinator")
|
|
469
|
+
@coordinator.expects(:each).returns(nil)
|
|
470
|
+
ENV.delete "ROLES"
|
|
471
|
+
ENV.delete "HOSTS"
|
|
472
|
+
end
|
|
445
473
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
474
|
+
it "filters by role from the :filter variable" do
|
|
475
|
+
hosts = dsl.roles(:web)
|
|
476
|
+
all = dsl.roles(:all)
|
|
477
|
+
SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
|
|
478
|
+
dsl.set :filter, role: "web"
|
|
479
|
+
dsl.on(all)
|
|
480
|
+
end
|
|
450
481
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
482
|
+
it "filters by host and role from the :filter variable" do
|
|
483
|
+
all = dsl.roles(:all)
|
|
484
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
485
|
+
dsl.set :filter, role: "db", host: "example3.com"
|
|
486
|
+
dsl.on(all)
|
|
454
487
|
end
|
|
455
488
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
489
|
+
it "filters by roles from the :filter variable" do
|
|
490
|
+
hosts = dsl.roles(:web)
|
|
491
|
+
all = dsl.roles(:all)
|
|
492
|
+
SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
|
|
493
|
+
dsl.set :filter, roles: "web"
|
|
494
|
+
dsl.on(all)
|
|
495
|
+
end
|
|
460
496
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
497
|
+
it "filters by hosts and roles from the :filter variable" do
|
|
498
|
+
all = dsl.roles(:all)
|
|
499
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
500
|
+
dsl.set :filter, roles: "db", hosts: "example3.com"
|
|
501
|
+
dsl.on(all)
|
|
464
502
|
end
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
503
|
|
|
468
|
-
|
|
504
|
+
it "filters from ENV[ROLES]" do
|
|
505
|
+
hosts = dsl.roles(:db)
|
|
506
|
+
all = dsl.roles(:all)
|
|
507
|
+
SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
|
|
508
|
+
ENV["ROLES"] = "db"
|
|
509
|
+
dsl.on(all)
|
|
510
|
+
end
|
|
469
511
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
@coordinator.expects(:each).returns(nil)
|
|
478
|
-
ENV.delete 'ROLES'
|
|
479
|
-
ENV.delete 'HOSTS'
|
|
512
|
+
it "filters from ENV[HOSTS]" do
|
|
513
|
+
hosts = dsl.roles(:db)
|
|
514
|
+
all = dsl.roles(:all)
|
|
515
|
+
SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
|
|
516
|
+
ENV["HOSTS"] = "example5.com"
|
|
517
|
+
dsl.on(all)
|
|
518
|
+
end
|
|
480
519
|
|
|
520
|
+
it "filters by ENV[HOSTS] && ENV[ROLES]" do
|
|
521
|
+
all = dsl.roles(:all)
|
|
522
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
523
|
+
ENV["HOSTS"] = "example5.com"
|
|
524
|
+
ENV["ROLES"] = "web"
|
|
525
|
+
dsl.on(all)
|
|
526
|
+
end
|
|
481
527
|
end
|
|
482
528
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
529
|
+
describe "when passed server literal names" do
|
|
530
|
+
before do
|
|
531
|
+
ENV.delete "ROLES"
|
|
532
|
+
ENV.delete "HOSTS"
|
|
533
|
+
@coordinator = mock("coordinator")
|
|
534
|
+
@coordinator.expects(:each).returns(nil)
|
|
535
|
+
end
|
|
490
536
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
end
|
|
537
|
+
it "selects nothing when a role filter is present" do
|
|
538
|
+
dsl.set :filter, role: "web"
|
|
539
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
540
|
+
dsl.on("my.server")
|
|
541
|
+
end
|
|
497
542
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
dsl.on(all)
|
|
504
|
-
end
|
|
543
|
+
it "selects using the string when a host filter is present" do
|
|
544
|
+
dsl.set :filter, host: "server.local"
|
|
545
|
+
SSHKit::Coordinator.expects(:new).with(["server.local"]).returns(@coordinator)
|
|
546
|
+
dsl.on("server.local")
|
|
547
|
+
end
|
|
505
548
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
dsl.on(all)
|
|
512
|
-
end
|
|
549
|
+
it "doesn't select when a host filter is present that doesn't match" do
|
|
550
|
+
dsl.set :filter, host: "ruby.local"
|
|
551
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
552
|
+
dsl.on("server.local")
|
|
553
|
+
end
|
|
513
554
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
dsl.on(all)
|
|
520
|
-
end
|
|
555
|
+
it "selects nothing when a roles filter is present" do
|
|
556
|
+
dsl.set :filter, roles: "web"
|
|
557
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
558
|
+
dsl.on("my.server")
|
|
559
|
+
end
|
|
521
560
|
|
|
522
|
-
|
|
561
|
+
it "selects using the string when a hosts filter is present" do
|
|
562
|
+
dsl.set :filter, hosts: "server.local"
|
|
563
|
+
SSHKit::Coordinator.expects(:new).with(["server.local"]).returns(@coordinator)
|
|
564
|
+
dsl.on("server.local")
|
|
565
|
+
end
|
|
523
566
|
|
|
524
|
-
|
|
567
|
+
it "doesn't select when a hosts filter is present that doesn't match" do
|
|
568
|
+
dsl.set :filter, hosts: "ruby.local"
|
|
569
|
+
SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
|
|
570
|
+
dsl.on("server.local")
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
525
574
|
|
|
575
|
+
describe "role_properties()" do
|
|
526
576
|
before do
|
|
527
577
|
dsl.role :redis, %w[example1.com example2.com], redis: { port: 6379, type: :slave }
|
|
528
|
-
dsl.server
|
|
529
|
-
dsl.server
|
|
530
|
-
dsl.server
|
|
578
|
+
dsl.server "example1.com", roles: %w{web}, active: true, web: { port: 80 }
|
|
579
|
+
dsl.server "example2.com", roles: %w{web redis}, web: { port: 81 }, redis: { type: :master }
|
|
580
|
+
dsl.server "example3.com", roles: %w{app}, primary: true
|
|
531
581
|
end
|
|
532
582
|
|
|
533
|
-
it
|
|
583
|
+
it "retrieves properties for a single role as a set" do
|
|
534
584
|
rps = dsl.role_properties(:app)
|
|
535
|
-
expect(rps).to eq(Set[{ hostname:
|
|
585
|
+
expect(rps).to eq(Set[{ hostname: "example3.com", role: :app }])
|
|
536
586
|
end
|
|
537
587
|
|
|
538
|
-
it
|
|
588
|
+
it "retrieves properties for multiple roles as a set" do
|
|
539
589
|
rps = dsl.role_properties(:app, :web)
|
|
540
|
-
expect(rps).to eq(Set[{ hostname:
|
|
590
|
+
expect(rps).to eq(Set[{ hostname: "example3.com", role: :app }, { hostname: "example1.com", role: :web, port: 80 }, { hostname: "example2.com", role: :web, port: 81 }])
|
|
541
591
|
end
|
|
542
592
|
|
|
543
|
-
it
|
|
544
|
-
recipient = mock(
|
|
545
|
-
recipient.expects(:doit).with(
|
|
546
|
-
recipient.expects(:doit).with(
|
|
593
|
+
it "yields the properties for a single role" do
|
|
594
|
+
recipient = mock("recipient")
|
|
595
|
+
recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
|
|
596
|
+
recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
|
|
547
597
|
dsl.role_properties(:redis) do |host, role, props|
|
|
548
598
|
recipient.doit(host, role, props)
|
|
549
599
|
end
|
|
550
600
|
end
|
|
551
601
|
|
|
552
|
-
it
|
|
553
|
-
recipient = mock(
|
|
554
|
-
recipient.expects(:doit).with(
|
|
555
|
-
recipient.expects(:doit).with(
|
|
556
|
-
recipient.expects(:doit).with(
|
|
602
|
+
it "yields the properties for multiple roles" do
|
|
603
|
+
recipient = mock("recipient")
|
|
604
|
+
recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
|
|
605
|
+
recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
|
|
606
|
+
recipient.expects(:doit).with("example3.com", :app, nil)
|
|
557
607
|
dsl.role_properties(:redis, :app) do |host, role, props|
|
|
558
608
|
recipient.doit(host, role, props)
|
|
559
609
|
end
|
|
560
610
|
end
|
|
561
611
|
|
|
562
|
-
it
|
|
563
|
-
recipient = mock(
|
|
564
|
-
recipient.expects(:doit).with(
|
|
565
|
-
recipient.expects(:doit).with(
|
|
566
|
-
recipient.expects(:doit).with(
|
|
567
|
-
recipient.expects(:doit).with(
|
|
612
|
+
it "yields the merged properties for multiple roles" do
|
|
613
|
+
recipient = mock("recipient")
|
|
614
|
+
recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
|
|
615
|
+
recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
|
|
616
|
+
recipient.expects(:doit).with("example1.com", :web, port: 80)
|
|
617
|
+
recipient.expects(:doit).with("example2.com", :web, port: 81)
|
|
568
618
|
dsl.role_properties(:redis, :web) do |host, role, props|
|
|
569
619
|
recipient.doit(host, role, props)
|
|
570
620
|
end
|
|
571
621
|
end
|
|
572
622
|
|
|
573
|
-
it
|
|
574
|
-
recipient = mock(
|
|
575
|
-
recipient.expects(:doit).with(
|
|
576
|
-
recipient.expects(:doit).with(
|
|
623
|
+
it "honours a property filter before yielding" do
|
|
624
|
+
recipient = mock("recipient")
|
|
625
|
+
recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
|
|
626
|
+
recipient.expects(:doit).with("example1.com", :web, port: 80)
|
|
577
627
|
dsl.role_properties(:redis, :web, select: :active) do |host, role, props|
|
|
578
628
|
recipient.doit(host, role, props)
|
|
579
629
|
end
|
|
580
630
|
end
|
|
581
631
|
end
|
|
582
|
-
|
|
583
632
|
end
|