Codebase list capistrano / 45a9264
New upstream version 3.10.0 Samuel Henrique 6 years ago
134 changed file(s) with 5920 addition(s) and 2847 deletion(s). Raw diff Collapse all Expand all
0 **Important:** GitHub issues are for feature requests or bug reports. The Capistrano team recommends you use [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) for general questions. For more details, please see our [contribution policy](https://github.com/capistrano/capistrano/blob/master/CONTRIBUTING.md).
1
2 ---
3
4 ### Steps to reproduce
5 If reasonable, you can help by creating a Capistrano skeleton example project which reproduces the issue you are seeing. You can then upload the individual files to a GitHub Gist or a GitHub project. Others can simply modify the configuration to point at a test server/repository of their own. Often times, an issue is resolved simply by making this test case.
6
7 An example test case is here: https://gist.github.com/will-in-wi/527327e31af30b3eae2068e2965be05b
8
9 ### Expected behavior
10 Tell us what should happen
11
12 ### Actual behavior
13 Tell us what happens instead
14
15 ### System configuration
16 Please link to the output of `cap <stage> doctor` in a GitHub Gist.
17
18 Thanks for helping improve Capistrano!
0 ### Summary
1
2 (Guidelines for creating a bug report are available
3 here: https://github.com/capistrano/capistrano/blob/master/DEVELOPMENT.md)
4
5 Provide a general description of the code changes in your pull
6 request... were there any bugs you had fixed? If so, mention them. If
7 these bugs have open GitHub issues, be sure to tag them here as well,
8 to keep the conversation linked together.
9
10 ### Short checklist
11
12 - [ ] Did you run `bundle exec rubocop -a` to fix linter issues?
13 - [ ] If relevant, did you create a test?
14 - [ ] Did you confirm that the RSpec tests pass?
15 - [ ] If you are fixing a bug or introducing a new feature, did you add a CHANGELOG entry?
16
17 ### Other Information
18
19 If there's anything else that's important and relevant to your pull
20 request, mention that information here.
21
22 If you are updating any of the CHANGELOG files or are asked to update the
23 CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file where indicated.
24
25 Thanks for helping improve Capistrano!
00 *.gem
11 *.rbc
2 *.swp
23 .bundle
34 .config
5 .rspec
6 .rspec-local
7 .ruby-version
48 .yardoc
59 Gemfile.lock
610 InstalledFiles
11 _site
712 _yardoc
813 coverage
914 doc/
1116 pkg
1217 rdoc
1318 spec/reports
19 tags
1420 test/tmp
1521 test/version_tmp
1622 tmp
17 .rspec-local
18 .ruby-version
19 _site
20 .rspec
21 *.swp
23 /docs/_site/
24 vendor/
0 AllCops:
1 DisplayCopNames: true
2 DisplayStyleGuide: true
3 TargetRubyVersion: 2.0
4
5 Lint/AmbiguousBlockAssociation:
6 Enabled:
7 false
8 Metrics/BlockLength:
9 Exclude:
10 - "spec/**/*"
11 - "lib/**/*.rake"
12 Style/BarePercentLiterals:
13 EnforcedStyle: percent_q
14 Style/ClassAndModuleChildren:
15 Enabled: false
16 Style/DoubleNegation:
17 Enabled: false
18 Style/FileName:
19 Exclude:
20 - "Dangerfile"
21 Style/IndentHeredoc:
22 Enabled: false
23 Style/SpaceAroundEqualsInParameterDefault:
24 EnforcedStyle: no_space
25 Style/StringLiterals:
26 EnforcedStyle: double_quotes
27 Style/TrivialAccessors:
28 AllowPredicates: true
29 Style/PercentLiteralDelimiters:
30 Enabled: false
31 Style/SingleLineBlockParams:
32 Enabled: false
33 Style/ModuleFunction:
34 Enabled: false
35
36 # Enable someday
37 Style/Documentation:
38 Enabled: false
39
40 # Needs refactors
41 Metrics/PerceivedComplexity:
42 Enabled: false
43 Metrics/CyclomaticComplexity:
44 Enabled: false
45 Metrics/MethodLength:
46 Enabled: false
47 Style/PredicateName:
48 Enabled: false
49 Metrics/LineLength:
50 Enabled: false
51 Metrics/AbcSize:
52 Enabled: false
53 Style/PerlBackrefs:
54 Enabled: false
55 Metrics/ClassLength:
56 Enabled: false
57 Metrics/ModuleLength:
58 Enabled: false
59 Style/AccessorMethodName:
60 Enabled: false
00 language: ruby
11 rvm:
2 - 2.1.0
3 - 2.0.0
4 - 1.9.3
5 - rbx-2
6 script: bundle exec rake spec
2 - 2.4.1
3 - 2.3.1
4 - 2.2
5 - 2.1
6 - 2.0
7 matrix:
8 # Rubinius periodically fails in general, and it always segfaults for RuboCop
9 # in particular. Until we figure out how to make it work consistently, allow
10 # it to fail without breaking the build.
11 allow_failures:
12 - rvm: rbx-2
13 include:
14 - rvm: rbx-2
15 script: bundle exec rake spec
16 # Run Danger only once, on 2.3.1
17 - rvm: 2.3.1
18 before_script: bundle exec danger
19
20 script: bundle exec rake spec rubocop
721 install: bundle install --jobs=1
822 cache: bundler
923 branches:
00 # Capistrano 3.x Changelog
11
2 Reverse Chronological Order:
3
4 ## master
5
6 https://github.com/capistrano/capistrano/compare/v3.4.0...HEAD
7
8 ## `3.4.0`
9
10 https://github.com/capistrano/capistrano/compare/v3.3.5...v3.4.0
2 All notable changes to this project will be documented in this file, in reverse chronological order.
3
4 **Capistrano follows a modified version of [SemVer](http://semver.org)**, similar to the Ruby on Rails project. For a `X.Y.Z` release:
5
6 * `Z` indicates bug fixes only; no breaking changes and no new features, except as necessary for security fixes.
7 * `Y` is bumped when we add new features. Occasionally a `Y` release may include small breaking changes. We will notify via CHANGELOG entries and/or deprecation notices if there are breaking changes.
8 * `X` is incremented for significant breaking changes. This is reserved for special occasions, like a complete rewrite.
9
10 **Capistrano uses a six-week release cadence.** Every six weeks, give or take, any changes in master will be published as a new rubygems version. If you'd like to use a feature or fix that is in master and you can't wait for the next planned release, put this in your project's Gemfile to use the master branch directly:
11
12 ```ruby
13 gem "capistrano", :github => "capistrano/capistrano"
14 ```
15
16 ## [master]
17
18 [master]: https://github.com/capistrano/capistrano/compare/v3.10.0...HEAD
19
20 * Your contribution here!
21
22 ## [`3.10.0`] (2017-10-23)
23
24 [`3.10.0`]: https://github.com/capistrano/capistrano/compare/v3.9.1...v3.10.0
25
26 As of this release, version 2.x of Capistrano is officially End of Life. No further releases of 2.x series are planned, and pull requests against 2.x are no longer accepted. The maintainers encourage you to upgrade to 3.x if possible.
27
28 ### Breaking changes:
29
30 * None
31
32 ### New features:
33
34 * [#1943](https://github.com/capistrano/capistrano/issues/1943): Make 'releases' and 'shared' directory names configurable from deployment target
35 * [#1922](https://github.com/capistrano/capistrano/pull/1922): Prevents last good release from being deleted during cleanup if there are too many subsequent failed deploys
36 * [#1930](https://github.com/capistrano/capistrano/issues/1930): Default to locking the version using the pessimistic version operator at the patch level.
37
38 ### Fixes:
39
40 * [#1937](https://github.com/capistrano/capistrano/pull/1937): Clarify error message when plugin is required in the wrong config file.
41
42 ## [`3.9.1`] (2017-09-08)
43
44 [`3.9.1`]: https://github.com/capistrano/capistrano/compare/v3.9.0...v3.9.1
45
46 ### Breaking changes:
47
48 * None
49
50 ### Fixes:
51
52 * [#1912](https://github.com/capistrano/capistrano/pull/1912): Fixed an issue where questions posed by `ask` were not printed on certain platforms - [@kminiatures](https://github.com/kminiatures)
53
54 ## [`3.9.0`] (2017-07-28)
55
56 [`3.9.0`]: https://github.com/capistrano/capistrano/compare/v3.8.2...v3.9.0
57
58 ### Breaking changes:
59
60 * None
61
62 ### New features:
63
64 * [#1911](https://github.com/capistrano/capistrano/pull/1911): Add Capistrano::DSL#invoke! for repetitive tasks
65
66 ### Fixes:
67
68 * [#1899](https://github.com/capistrano/capistrano/pull/1899): Updated `deploy:cleanup` to continue rotating the releases and skip the invalid directory names instead of skipping the whole rotation of releases. The warning message has changed slightly due to the change of behavior.
69
70 ## [`3.8.2`] (2017-06-16)
71
72 [`3.8.2`]: https://github.com/capistrano/capistrano/compare/v3.8.1...v3.8.2
73
74 ### Breaking changes:
75
76 * None
77
78 ### Other changes:
79
80 * [#1882](https://github.com/capistrano/capistrano/pull/1882): Explain where to add new Capfile lines in scm deprecation warning - [@robd](https://github.com/robd)
81
82 ## [`3.8.1`] (2017-04-21)
83
84 [`3.8.1`]: https://github.com/capistrano/capistrano/compare/v3.8.0...v3.8.1
85
86 ### Breaking changes:
87
88 * None
89
90 ### Fixes:
91
92 * [#1867](https://github.com/capistrano/capistrano/pull/1867): Allow `cap -T` to run without Capfile present - [@mattbrictson](https://github.com/mattbrictson)
93
94 ## [`3.8.0`] (2017-03-10)
95
96 [`3.8.0`]: https://github.com/capistrano/capistrano/compare/v3.7.2...v3.8.0
97
98 ### Minor breaking changes:
99
100 * [#1846](https://github.com/capistrano/capistrano/pull/1846): add_host - When this method has already been called once for a given host and it is called a second time with a port, a new host will be added. Previously, the first host would have been updated. [(@dbenamy)](https://github.com/dbenamy)
101
102 ### New features:
103
104 * [#1860](https://github.com/capistrano/capistrano/pull/1860): Allow cap to be run within subdir and still work - [@mattbrictson](https://github.com/mattbrictson)
105
106 ### Fixes:
107
108 * [#1835](https://github.com/capistrano/capistrano/pull/1835): Stopped printing parenthesis in ask prompt if no default or nil was passed as argument [(@chamini2)](https://github.com/chamini2)
109 * [#1840](https://github.com/capistrano/capistrano/pull/1840): Git plugin: shellescape git_wrapper_path [(@olleolleolle)](https://github.com/olleolleolle)
110 * [#1843](https://github.com/capistrano/capistrano/pull/1843): Properly shell escape git:wrapper steps - [@mattbrictson](https://github.com/mattbrictson)
111 * [#1846](https://github.com/capistrano/capistrano/pull/1846): Defining a role is now O(hosts) instead of O(hosts^2) [(@dbenamy)](https://github.com/dbenamy)
112 * Run `svn switch` to work with svn branches if repo_url is changed
113 * [#1856](https://github.com/capistrano/capistrano/pull/1856): Fix hg repo_tree implementation - [@mattbrictson](https://github.com/mattbrictson)
114 * [#1857](https://github.com/capistrano/capistrano/pull/1857): Don't emit doctor warning when repo_tree is set - [@mattbrictson](https://github.com/mattbrictson)
115
116 ### Other changes:
117
118 * [capistrano-harrow#4](https://github.com/harrowio/capistrano-harrow/issues/4): Drop dependency on `capistrano-harrow` gem. Gem can still be installed separately [(@leehambley)](https://github.com/leehambley)
119 * [#1859](https://github.com/capistrano/capistrano/pull/1859): Move git-specific repo_url logic into git plugin - [@mattbrictson](https://github.com/mattbrictson)
120 * [#1858](https://github.com/capistrano/capistrano/pull/1858): Unset the :scm variable when an SCM plugin is used - [@mattbrictson](https://github.com/mattbrictson)
121
122 ## [`3.7.2`] (2017-01-27)
123
124 [`3.7.2`]: https://github.com/capistrano/capistrano/compare/v3.7.1...v3.7.2
125
126 ### Potentially breaking changes:
127
128 * None
129
130 ### Other changes:
131
132 * Suppress log messages of `git ls-remote` by filtering remote refs (@aeroastro)
133 * The Git SCM now allows the repo_url to be changed without manually wiping out the mirror on each target host first (@javanthropus)
134
135 ## [`3.7.1`] (2016-12-16)
136
137 [`3.7.1`]: https://github.com/capistrano/capistrano/compare/v3.7.0...v3.7.1
138
139 ### Potentially breaking changes:
140
141 * None
142
143 ### Fixes:
144
145 * Fixed a bug with mercurial deploys failing due to an undefined variable
146
147 ## [`3.7.0`] (2016-12-10)
148
149 [`3.7.0`]: https://github.com/capistrano/capistrano/compare/v3.6.1...v3.7.0
150
151 *Note: These release notes include all changes since 3.6.1, including the changes that were first published in 3.7.0.beta1.*
152
153 ### Deprecations:
154
155 * The `set :scm, ...` mechanism is now deprecated in favor of a new SCM plugin system. See the [UPGRADING-3.7](UPGRADING-3.7.md) document for details
156
157 ### Potentially breaking changes:
158
159 * The `:git_strategy`, `:hg_strategy`, and `:svn_strategy` settings have been removed with no replacement. If you have been using these to customize Capistrano's SCM behavior, you will need to rewrite your customization using the [new plugin system](http://capistranorb.com/documentation/advanced-features/custom-scm/)
160 * `remote_file` feature has been removed and is no longer available to use @SaiVardhan
161
162 ### New features:
163
164 * The `tar` used by the Git SCM now honors the SSHKit command map, allowing an alternative tar binary to be used (e.g. gtar) #1787 (@caius)
165 * Add support for custom on-filters [#1776](https://github.com/capistrano/capistrano/issues/1776)
166
167 ### Fixes:
168
169 * Fix test suite to work with Mocha 1.2.0 (@caius)
170 * Fix bug where host_filter and role_filter were overly greedy [#1766](https://github.com/capistrano/capistrano/issues/1766) (@cseeger-epages)
171 * Fix the removal of old releases `deploy:cleanup`. Logic is changed because of unreliable modification times on folders. Removal of directories is now decided by sorting on folder names (name is generated from current datetime format YmdHis). Cleanup is skipped, and a warning is given when a folder name is in a different format
172
173 ## [`3.7.0.beta1`] (2016-11-02)
174
175 [`3.7.0.beta1`]: https://github.com/capistrano/capistrano/compare/v3.6.1...v3.7.0.beta1
176
177 ### Deprecations:
178
179 * The `set :scm, ...` mechanism is now deprecated in favor of a new SCM plugin
180 system. See the [UPGRADING-3.7](UPGRADING-3.7.md) document for details.
181
182 ### Potentially breaking changes:
183
184 * The `:git_strategy`, `:hg_strategy`, and `:svn_strategy` settings have been
185 removed with no replacement. If you have been using these to customize
186 Capistrano's SCM behavior, you will need to rewrite your customization using
187 the [new plugin system](http://capistranorb.com/documentation/advanced-features/custom-scm/).
188 * `remote_file` feature has been removed and is no longer available to use @SaiVardhan
189
190 ### New features:
191
192 * The `tar` used by the Git SCM now honors the SSHKit command map, allowing an alternative tar binary to be used (e.g. gtar) #1787 (@caius)
193
194 ### Fixes:
195
196 * Fix test suite to work with Mocha 1.2.0 (@caius)
197 * Fix bug where host_filter and role_filter were overly greedy [#1766](https://github.com/capistrano/capistrano/issues/1766) (@cseeger-epages)
198
199 ## [`3.6.1`] (2016-08-23)
200
201 [`3.6.1`]: https://github.com/capistrano/capistrano/compare/v3.6.0...v3.6.1
202
203 ### Fixes:
204
205 * Restore compatibility with older versions of Rake (< 11.0.0) (@troelskn)
206 * Fix `NoMethodError: undefined method gsub` when setting `:application` to a Proc. The original fix released in 3.6.0 worked for values specified with blocks, but not for those specified with procs or lambdas (the latter syntax is much more common). [#1681](https://github.com/capistrano/capistrano/issues/1681)
207 * Fix a bug where deploy would fail if `:local_user` contained a space; spaces are now replaced with dashes when computing the git-ssh suffix. (@will_in_wi)
208
209 ## [`3.6.0`] (2016-07-26)
210
211 [`3.6.0`]: https://github.com/capistrano/capistrano/compare/v3.5.0...v3.6.0
212
213 Thank you to the many first-time contributors from the Capistrano community who
214 helped with this release!
215
216 ### Deprecations:
217
218 * Deprecate `remote_file` feature (will be removed in Capistrano 3.7.0) (@lebedev-yury)
219 * Deprecate `:git_strategy`, `:hg_strategy`, and `:svn_strategy` variables.
220 These will be completely removed in 3.7.0.
221 * Added warning about future deprecation of reinvocation behaviour (@troelskn)
222
223 Refer to the [Capistrano 3.7.0 upgrade document](UPGRADING-3.7.md) if you are
224 affected by these deprecations.
225
226 ### New features:
227
228 * Added a `doctor:servers` subtask that outputs a summary of servers, roles & properties (@irvingwashington)
229 * Make path to git wrapper script configurable (@thickpaddy)
230 * Make name of current directory configurable via configuration variable `:current_directory` (@websi)
231 * It is now possible to rollback to a specific release using the
232 `ROLLBACK_RELEASE` environment variable.
233 [#1155](https://github.com/capistrano/capistrano/issues/1155) (@lanrion)
234
235 ### Fixes:
236
237 * `doctor` no longer erroneously warns that `:git_strategy` and other SCM options are "unrecognized" (@shanesaww)
238 * Fix `NoMethodError: undefined method gsub` when setting `:application` to a
239 Proc. [#1681](https://github.com/capistrano/capistrano/issues/1681)
240 (@mattbrictson)
241
242 ### Other changes:
243
244 * Raise a better error when an ‘after’ hook isn’t found (@jdelStrother)
245 * Change git wrapper path to work better with multiple users (@thickpaddy)
246 * Restrict the uploaded git wrapper script permissions to 700 (@irvingwashington)
247 * Add `net-ssh` gem version to `doctor:gems` output (@lebedev-yury)
248
249 ## [`3.5.0`]
250
251 [`3.5.0`]: https://github.com/capistrano/capistrano/compare/v3.4.1...v3.5.0
252
253 **You'll notice a big cosmetic change in this release: the default logging
254 format has been changed to
255 [Airbrussh](https://github.com/mattbrictson/airbrussh).** For more details on
256 what Airbrussh does
257 and how to configure it, visit the
258 [Airbrussh README](https://github.com/mattbrictson/airbrussh#readme).
259
260 * To opt out of the new format, simply add `set :format, :pretty` to switch to
261 the old default of Capistrano 3.4.0 and earlier.
262 * If you are already an Airbrussh user, note that the default configuration has
263 changed, and the syntax for configuring Airbrussh has changed as well.
264 [This simple upgrade guide](https://github.com/mattbrictson/airbrussh/blob/master/UPGRADING-CAP-3.5.md)
265 will walk you through it.
266
267 ### Potentially breaking changes:
268
269 * Drop support for Ruby 1.9.3 (Capistrano does no longer work with 1.9.3)
270 * Git version 1.6.3 or greater is now required
271 * Remove 'vendor/bundle' from default :linked_dirs (@ojab)
272 * Old versions of SSHKit (before 1.9.0) are no longer supported
273 * SHA1 hash of current git revision written to REVISION file is no longer abbreviated
274 * Ensure task invocation within after hooks is namespace aware, which may require
275 you to change how your `after` hooks are declared in some cases; see
276 [#1652](https://github.com/capistrano/capistrano/issues/1652) for an example
277 and how to correct it (@thickpaddy)
278 * Validation of the `:application` variable forbids special characters such as slash,
279 this may be a breaking change in case that you rely on using a `/` in your application
280 name to deploy from a sub directory.
281
282 ### New features:
283
284 * Added a `doctor` task that outputs helpful troubleshooting information. Try it like this: `cap production doctor`. (@mattbrictson)
285 * Added a `dry_run?` helper method
286 * `remove` DSL method for removing values like from arrays like `linked_dirs`
287 * `append` DSL method for pushing values like `linked_dirs`
288 [#1447](https://github.com/capistrano/capistrano/pull/1447),
289 [#1586](https://github.com/capistrano/capistrano/pull/1586)
290 * Added support for git shallow clone
291 * Added new runtime option `--print-config-variables` that inspect all defined config variables in order to assist development of new capistrano tasks (@gerardo-navarro)
292 * Prune dead tracking branches from git repositories while updating
293 * Added options to set username and password when using Subversion as SCM (@dsthode)
294 * Allow after() to refer to tasks that have not been loaded yet (@jcoglan)
295 * Allow use "all" as string for server filtering (@theist)
296 * Print a warning and abort if "load:defaults" is erroneously invoked after
297 capistrano is already loaded, e.g. when a plugin is loaded in `deploy.rb`
298 instead of `Capfile`. (@mattbrictson)
299 * Added option to set specific revision when using Subversion as SCM (@marcovtwout)
300 * Deduplicate list of linked directories
301 * Integration with Harrow.io (See http://capistranorb.com/documentation/harrow/) when running `cap install`
302 * Added validate method to DSL to allow validation of certain values (@Kriechi)
303 * validate values before assignment inside of `set(:key, value)`
304 * should raise a `Capistrano::ValidationError` if invalid
305 * Added default validation for Capistrano-specific variables (@Kriechi)
306
307 ### Fixes:
308
309 * Capistrano is now fully-compatible with Rake 11.0. (@mattbrictson)
310 * Fix filtering behaviour when using literal hostnames in on() block (@townsen)
311 * Allow dot in :application name (@marcovtwout)
312 * Fixed git-ssh permission error (@spight)
313
314 ### Other changes:
315
316 * Internal Rubocop cleanups.
317 * Removed the post-install message (@Kriechi)
318 * Refactor `Configuration::Filter` to use filtering strategies instead
319 of case statements (@cshaffer)
320 * Clean up rubocop lint warnings (@cshaffer)
321
322 ## [`3.4.0`]
323
324 [`3.4.0`]: https://github.com/capistrano/capistrano/compare/v3.3.5...v3.4.0
11325
12326 * Fixed fetch revision for annotated git tags. (@igorsokolov)
13327 * Fixed updating roles when custom user or port is specified. (@ayastreb)
24338 * Breaking Changes
25339 * Hosts with the same name are now consolidated into one irrespective of the
26340 user and port. This allows multiple declarations of a server to be made safely.
27 The last declared properties will win. See capistrnorb.com Properties documentation
341 The last declared properties will win. See capistranorb.com Properties documentation
28342 for details.
29343 * Inside the on() block the host variable is now a copy of the host, so changes can be
30344 made within the block (such as dynamically overriding the user) that will not persist.
39353 * Allow specification of repo_path using stage variable
40354 default is as before (@townsen)
41355
42 ## `3.3.5`
43
44 https://github.com/capistrano/capistrano/compare/v3.3.4...v3.3.5
356 ## [`3.3.5`]
357
358 [`3.3.5`]: https://github.com/capistrano/capistrano/compare/v3.3.4...v3.3.5
45359
46360 * Fixed setting properties twice when creating new server. See [issue
47361 #1214](https://github.com/capistrano/capistrano/issues/1214) (@ayastreb)
48362
49 ## `3.3.4`
50
51 https://github.com/capistrano/capistrano/compare/v3.3.3...v3.3.4
363 ## [`3.3.4`]
364
365 [`3.3.4`]: https://github.com/capistrano/capistrano/compare/v3.3.3...v3.3.4
52366
53367 * Minor changes:
54368 * Rely on a newer version of capistrano-stats with better privacy (@leehambley)
57371 * Spec improvements (@dimitrid, @sponomarev)
58372 * Fix to CLI flags for git-ls-remote (@dimitrid)
59373
60 ## `3.3.3`
61
62 https://github.com/capistrano/capistrano/compare/v3.2.1...v3.3.3
374 ## [`3.3.3`]
375
376 [`3.3.3`]: https://github.com/capistrano/capistrano/compare/v3.2.1...v3.3.3
63377
64378 * Enhancement (@townsen)
65379 * Added the variable `:repo_tree` which allows the specification of a sub-tree that
143457 * Updated svn fetch_revision method to use `svnversion`
144458 * `cap install` no longer overwrites existing files. (@dmarkow)
145459
146 ## `3.2.1`
147
148 https://github.com/capistrano/capistrano/compare/v3.2.0...v3.2.1
460 ## [`3.2.1`]
461
462 [`3.2.1`]: https://github.com/capistrano/capistrano/compare/v3.2.0...v3.2.1
149463
150464 * Bug Fixes:
151465 * 3.2.0 introduced some behaviour to modify the way before/after hooks were called, to allow the optional
160474 * Added `keys` method to Configuration to allow introspection of configuration options. (@juanibiapina)
161475 * Improve error message when git:check fails (raise instead of silently `exit 1`) (@mbrictson)
162476
163 ## `3.2.0`
477 ## [`3.2.0`]
164478
165479 The changelog entries here are incomplete, because many authors choose not to
166480 be credited for their work, check the tag comparison link for Github.
167481
168 https://github.com/capistrano/capistrano/compare/v3.1.0...v3.2.0
482 [`3.2.0`]: https://github.com/capistrano/capistrano/compare/v3.1.0...v3.2.0
169483
170484 * Minor changes:
171485 * Added `keys` method to Server properties to allow introspection of automatically added
173487 * Compatibility with Rake 10.2.0 - `ensure_task` is now added to `@top_level_tasks` as a string. (@dmarkow)
174488 * Amended the git check command, "ls-remote", to use "-h", limiting the list to refs/heads
175489
176 ## `3.1.0`
177
178 https://github.com/capistrano/capistrano/compare/v3.0.1...v3.1.0
490 ## [`3.1.0`]
491
492 [`3.1.0`]: https://github.com/capistrano/capistrano/compare/v3.0.1...v3.1.0
179493
180494 Breaking changes:
181495
209523
210524 Big thanks to @Kriechi for his help.
211525
212 ## `3.0.1`
213
214 https://github.com/capistrano/capistrano/compare/v3.0.0...v3.0.1
526 ## [`3.0.1`]
527
528 [`3.0.1`]: https://github.com/capistrano/capistrano/compare/v3.0.0...v3.0.1
215529
216530 * `capify` not listed as executable (@leehambley)
217531 * Confirm license as MIT (@leehambley)
218532 * Move the git ssh helper to application path (@mpapis)
219533
220 ## `3.0.0`
221
222 https://github.com/capistrano/capistrano/compare/2.15.5...v3.0.0
534 ## [`3.0.0`]
535
536 [`3.0.0`]: https://github.com/capistrano/capistrano/compare/2.15.5...v3.0.0
223537
224538 If you are coming here to wonder why your Capfile doesn't work anymore, please
225539 vendor lock your Capistrano at 2.x, whichever version was working for you
0 ## CONTRIBUTING
0 **Hello and welcome!** Please look over this document before opening an issue or submitting a pull request to Capistrano.
11
2 **The issue tracker is intended exclusively for things that are genuine bugs,
3 or improvements to the code.**
2 * [If you’re looking for help or have a question](#if-youre-looking-for-help-or-have-a-question)
3 * [Reporting bugs](#reporting-bugs)
4 * [Requesting new features or improvements](#requesting-new-features-or-improvements)
5 * [Contributing code or documentation](#contributing-code-or-documentation)
46
5 If you have a user support query, or you suspect that you might just be holding
6 it wrong, drop us a line at [the mailing list](https://groups.google.com/forum/#!forum/capistrano), [StackOverflow](http://stackoverflow.com/questions/tagged/capistrano) or at [CodersClan](http://codersclan.net/?repo_id=325&source=contributing). The
7 mailing list is moderated to cut down on spam, so please be patient, if you use
8 StackOverflow, make sure to tag your post with "Capistrano". (Not forgetting
9 any other tags which might relate, rvm, rbenv, Ubuntu, etc.)
7 ## If you’re looking for help or have a question
108
11 If you have an urgent problem you can use [CodersClan](http://codersclan.net/?repo_id=325&source=contributing) to solve your problem quickly. CodersClan has a community of Capistrano experts dedicated to solve code problems for bounties.
9 **Check [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) first if you need help using Capistrano.** You are more likely to get a quick response at Stack Overflow for common Capistrano topics. Make sure to tag your post with `capistrano` and/or `capistrano3` (not forgetting any other tags which might relate: rvm, rbenv, Ubuntu, etc.)
1210
13 Wherever you post please be sure to include the version of Capistrano you are
14 using, which versions of any plugins (*capistrano-rvm*, *capistrano-bundler*,
15 etc.). Proper logs are vital, if you need to redact them, go ahead, but be
16 careful not to remove anything important. Please take care to format logs and
17 code correctly, ideally wrapped to a sane line length, and in a mono spaced
18 font. This all helps us to gather a clear understanding of what is going wrong.
11 If you have an urgent problem you can also try [CodersClan](http://codersclan.net/?repo_id=325&source=contributing), which has a community of Capistrano experts dedicated to solve code problems for bounties.
1912
20 **If you really think that you found a bug, or want to enquire about a feature,
21 or send us a patch to add a feature, or fix a bug, please keep a few things in
22 mind:**
13 When posting to Stack Overflow or CodersClan, be sure to include relevant information:
2314
24 ## When Submitting An Issue:
15 * Capistrano version
16 * Plugins and versions (capistrano-rvm, capistrano-bundler, etc.)
17 * Logs and backtraces
2518
26 If you think there's a bug, please make sure it's really a bug in Capistrano.
27 As Capistrano sits on the (sometimes rough) edges between SSH, Git, the
28 network, Ruby, RVM, rbenv, chruby, Bundler, your Linux distribution, countless
29 shell configuration files on your end, and the server… there's a good chance
30 the problem lies somewhere else.
19 If you think you’ve found a bug in Capistrano itself, then…
3120
32 Please make sure you have reviewed the FAQs at http://www.capistranorb.com/.
21 ## Reporting bugs
3322
34 It's really important to include as much information as possible, versions of
35 everything involved, anything weird you might be doing that might be having
36 side effects, include as much as you can in a [GitHub
37 Gist](https://gist.github.com/) and link that from the issue, with tools such
38 as Gist, we can link to individual lines and help work out what is going wrong.
23 As much the Capistrano community tries to write good, well-tested code, bugs still happen. Sorry about that!
3924
40 If you are an experienced Ruby programmer, take a few minutes to get our test
41 suite running, and do what you can to get a test case written that fails, from
42 there we can understand exactly what it takes to reproduce the issue (as it's
43 documented with code)
25 **In case you’ve run across an already-known issue, check the FAQs first on the [official Capistrano site](http://capistranorb.com).**
4426
45 ## When Requesting a Feature:
27 When opening a bug report, please include the output of the `cap <stage> doctor` task, e.g.:
4628
47 We can't make everyone happy all of the time, and we've been around the block
48 well enough to know when something doesn't work well, or when your proposed fix
49 might impact other things.
29 ```
30 cap production doctor
31 ```
5032
51 We prefer to [start with
52 "no"](https://gettingreal.37signals.com/ch05_Start_With_No.php), and help you
53 find a better way to solve your problem, sometimes the solution is to [build
54 faster
55 horses](http://blog.cauvin.org/2010/07/henry-fords-faster-horse-quote.html),
56 sometimes the solution is to work around it in a neat way that you didn't know
57 existed.
33 Also include in your report:
5834
59 Please don't be offended if we say no, and don't be afraid to fight your
60 corner, try and avoid being one of the [poisonous
61 people](https://www.youtube.com/watch?v=Q52kFL8zVoM)
35 * Versions of Ruby, Capistrano, and any plugins you’re using (if `doctor` didn't already do this for you)
36 * A description of the troubleshooting steps you’ve taken
37 * Logs and backtraces
38 * Sections of your `deploy.rb` that may be relevant
39 * Any other unique aspects of your environment
6240
63 ## Submitting A Pull Request:
41 If you are an experienced Ruby programmer, take a few minutes to get the Capistrano test suite running (see [DEVELOPMENT.md][]), and do what you can to get a test case written that fails. *This will be a huge help!*
6442
65 Pull requests are awesome, and if they arrive with decent tests, and conform to
66 the guidelines below, we'll merge them in as soon as possible, we'll let you
67 know which release we're planning them for (we adhere to
68 [semver](http://semver.org/) so please don't be upset if we plan your changes
69 for a later release)
43 If you think you may have discovered a security vulnerability in Capistrano, do not open a GitHub issue. Instead, please send a report to <security@capistranorb.com>.
7044
71 * The code is MIT licenced, your code will fall under the same license if we merge it.
72 * We can't merge it without a [good commit
73 message](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message).
74 If you do this right, Github will use the commit message as the body of your
75 pull request, double win.
76 * If you are referencing an improvement to an existing issue (if we have not
77 yet merged it )
78 * Add an entry to the `CHANGELOG` under the `### master` section, but please
79 don't mess with the version.
80 * If you add a new feature, please make sure to document it, open a
81 corresponding pull request in [the
82 documentation](https://github.com/capistrano/documentation) and mention the
83 code change pull request over there, and Github will link everything up. If
84 it's a simple feature, or a new variable, or something changed, it may be
85 appropriate simply to document it in the generated `Capfile` or `deploy.rb`, or
86 in the `README`
87 * Take care to squash your commit into one single commit with a good message, it
88 saves us a lot of work in maintaining the CHANGELOG if we can generate it from
89 the commit messages between the release tags!
90 * Tests! It's tricky to test some parts of Capistrano, but do your best, it
91 might just serve as a starting point for us to build a reliable test on top of,
92 and help us understand where you are coming from.
45 ## Requesting new features or improvements
46
47 Capistrano continues to improve thanks to people like you! Feel free to open a GitHub issue for any or all of these ideas:
48
49 * New features that would make Capistrano even better
50 * Areas where documentation could be improved
51 * Ways to improve developer happiness
52
53 Generally speaking the maintainers are very conservative about adding new features, and we can’t guarantee that the community will agree with or implement your idea. Please don’t be offended if we say no! The Capistrano team will do our best to review all suggestions and at least weigh in with a comment or suggest a workaround, if applicable.
54
55 **Your idea will have a much better chance of becoming reality if you contribute code for it (even if the code is incomplete!).**
56
57 ## Contributing code or documentation
58
59 So you want to contribute to Capistrano? Awesome! We have a whole separate document just you. It explains our pull request workflow and walks you through setting up the development environment: [DEVELOPMENT.md][].
60
61
62 [DEVELOPMENT.md]: https://github.com/capistrano/capistrano/blob/master/DEVELOPMENT.md
0 Thanks for helping build Capistrano! Here are the development practices followed by our community.
1
2 * [Who can help](#who-can-help)
3 * [Contributing documentation](#contributing-documentation)
4 * [Setting up your development environment](#setting-up-your-development-environment)
5 * [Coding guidelines](#coding-guidelines)
6 * [Submitting a pull request](#submitting-a-pull-request)
7 * [Managing GitHub issues](#managing-github-issues)
8 * [Reviewing and merging pull requests](#reviewing-and-merging-pull-requests)
9
10 ## Who can help
11
12 Everyone can help improve Capistrano. There are ways to contribute even if you aren’t a Ruby programmer. Here’s what you can do to help the project:
13
14 * adding to or fixing typos/quality in documentation
15 * adding failing tests for reported bugs
16 * writing code (no contribution is too small!)
17 * reviewing pull requests and suggesting improvements
18 * reporting bugs or suggesting new features (see [CONTRIBUTING.md][])
19
20 ## Contributing documentation
21
22 Improvements and additions to Capistrano's documentation are very much appreciated. The official documention is stored in the `docs/` directory as Markdown files. These files are used to automatically generate the [capistranorb.com](http://capistranorb.com/) website, which is hosted by GitHub Pages. Feel free to make changes to this documentation as you see fit. Before opening a pull request, make sure your documentation renders correctly by previewing the website in your local environment. Refer to [docs/README.md][] for instructions.
23
24 ## Setting up your development environment
25
26 Capistrano is a Ruby project, so we expect you to have a functioning Ruby environment. To hack on Capistrano you will further need some specialized tools to run its test suite.
27
28 Make sure to install:
29
30 * [Bundler](https://bundler.io/)
31 * [Vagrant](https://www.vagrantup.com/)
32 * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (or another [Vagrant-supported](https://docs.vagrantup.com/v2/getting-started/providers.html) VM host)
33
34
35 ### Running tests
36
37 Capistrano has two test suites: an RSpec suite and a Cucumber suite. The RSpec suite handles quick feedback unit specs. The Cucumber suite is an integration suite that uses Vagrant to deploy to a real virtual server.
38
39 ```
40 # Ensure all dependencies are installed
41 $ bundle install
42
43 # Run the RSpec suite
44 $ bundle exec rake spec
45
46 # Run the Cucumber suite
47 $ bundle exec rake features
48
49 # Run the Cucumber suite and leave the VM running (faster for subsequent runs)
50 $ bundle exec rake features KEEP_RUNNING=1
51 ```
52
53 ### Report failing Cucumber features!
54
55 Currently, the Capistrano Travis build does *not* run the Cucumber suite. This means it is possible for a failing Cucumber feature to sneak in without being noticed by our continuous integration checks.
56
57 **If you come across a failing Cucumber feature, this is a bug.** Please report it by opening a GitHub issue. Or even better: do your best to fix the feature and submit a pull request!
58
59 ## Coding guidelines
60
61 This project uses [RuboCop](https://github.com/bbatsov/rubocop) to enforce standard Ruby coding guidelines.
62
63 * Test that your contributions pass with `rake rubocop`
64 * Rubocop is also run as part of the full test suite with `rake`
65 * Note the Travis build will fail and your PR cannot be merged if Rubocop finds errors
66
67 ## Submitting a pull request
68
69 Pull requests are awesome, and if they arrive with decent tests, and conform to the guidelines below, we'll merge them in as soon as possible, we'll let you know which release we're planning them for (we adhere to [semver](http://semver.org/) so please don't be upset if we plan your changes for a later release).
70
71 Your code should conform to these guidelines:
72
73 * The code is MIT licensed, your code will fall under the same license if we merge it.
74 * We can't merge it without a [good commit message](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message). If you do this right, Github will use the commit message as the body of your pull request, double win.
75 * If you are making an improvement/fix for an existing issue, make sure to mention the issue number (if we have not yet merged it )
76 * Add an entry to the `CHANGELOG` under the `### master` section, but please don't mess with the version.
77 * If you add a new feature, please make sure to document it by modifying the appropriate Markdown files in `docs/` (see [contributing documentation](#contributing-documentation), above). If it's a simple feature, or a new variable, or something changed, it may be appropriate simply to document it in the generated `Capfile` or `deploy.rb`, or in the `README`.
78 * Take care to squash your commit into one single commit with a good message, it saves us a lot of work in maintaining the CHANGELOG if we can generate it from the commit messages between the release tags!
79 * Tests! It's tricky to test some parts of Capistrano, but do your best, it might just serve as a starting point for us to build a reliable test on top of, and help us understand where you are coming from.
80
81 ## Managing GitHub issues
82
83 The Capistrano maintainers will do our best to review all GitHub issues. Here’s how we manage issues:
84
85 1. Issues will be acknowledged with an appropriate label (see below).
86 2. Issues that are duplicates, spam, or irrelevant (e.g. wrong project), or are questions better suited for Stack Overflow (see [CONTRIBUTING.md][]) will be closed.
87 3. Once an issue is fixed or resolved in an upcoming Capistrano release, it will be closed and assigned to a GitHub milestone for that upcoming version number.
88
89 The maintainers do not have time to fix every issue ourselves, but we will gladly accept pull requests, especially for issues labeled as "you can help" (see below).
90
91 ### Issue labels
92
93 Capistrano uses these GitHub labels to categorize issues:
94
95 * **bug?** – Could be a bug (not reproducible or might be user error)
96 * **confirmed bug** – Definitely a bug
97 * **new feature** – A request for Capistrano to add a feature or work differently
98 * **chore** – A TODO that is neither a bug nor a feature (e.g. improve docs, CI infrastructure, etc.)
99
100 Also, the Capistrano team will sometimes add these labels to facilitate communication and encourage community feedback:
101
102 * **discuss!** – The Capistrano team wants more feedback from the community on this issue; e.g. how a new feature should work, or whether a bug is serious or not.
103 * **you can help!** – We want the community to help by submitting a pull request to solve a bug or add a feature. If you are looking for ways to contribute to Capistrano, start here!
104 * **needs more info** – We need more info from the person who opened the issue; e.g. steps to reproduce a bug, clarification on a desired feature, etc.
105
106 *These labels were inspired by Daniel Doubrovkine’s [2014 Golden Gate Ruby Conference talk](http://confreaks.tv/videos/gogaruco2014-taking-over-someone-else-s-open-source-projects).*
107
108 ## Reviewing and merging pull requests
109
110 Pull requests and issues follow similar workflows. Before merging a pull request, the Capistrano maintainers will check that these requirements are met:
111
112 * All CI checks pass
113 * Significant changes in behavior or fixes mentioned in the CHANGELOG
114 * Clean commit history
115 * New features are documented
116 * Code changes/additions are tested
117
118 If any of these are missing, the **needs more info** label is assigned to the pull request to indicate the PR is incomplete.
119
120 Merging a pull request is a decision entrusted to the maintainers of the Capistrano project. Any maintainer is free to merge a pull request if they feel the PR meets the above requirements and is in the best interest of the Capistrano community.
121
122 After a pull request is merged, it is assigned to a GitHub milestone for the upcoming version number.
123
124
125 [CONTRIBUTING.md]: https://github.com/capistrano/capistrano/blob/master/CONTRIBUTING.md
126 [docs/README.md]: https://github.com/capistrano/capistrano/blob/master/docs/README.md
0 danger.import_dangerfile(github: "capistrano/danger")
0 source 'https://rubygems.org'
0 source "https://rubygems.org"
11
22 # Specify your gem's dependencies in capistrano.gemspec
33 gemspec
44
55 group :cucumber do
6 gem 'cucumber'
7 gem 'rspec', '~> 3.0.0'
6 gem "cucumber"
7 gem "rspec"
8 gem "rspec-core", "~> 3.4.4"
89 end
00 MIT License (MIT)
11
2 Copyright (c) 2012-2013 Tom Clements, Lee Hambley
2 Copyright (c) 2012-2016 Tom Clements, Lee Hambley
33
44 Permission is hereby granted, free of charge, to any person obtaining a copy
55 of this software and associated documentation files (the "Software"), to deal
0 # Capistrano [![Build Status](https://travis-ci.org/capistrano/capistrano.svg?branch=master)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](http://img.shields.io/codeclimate/github/capistrano/capistrano.svg)](https://codeclimate.com/github/capistrano/capistrano) <a href="http://codersclan.net/?repo_id=325&source=small"><img src="http://img.shields.io/badge/get-support-blue.svg"></a>
1
2 ## Documentation
3
4 Check out the [online documentation](http://capistranorb.com) of Capistrano 3 hosted via this [repository](https://github.com/capistrano/capistrano.github.io).
5
6 ## Support
7
8 Need help with getting Capistrano up and running? Got a code problem you want to get solved quickly?
9
10 Get <a href="http://codersclan.net/?repo_id=325&source=link">Capistrano support on CodersClan.</a> <a href="http://codersclan.net/?repo_id=325&source=big"><img src="http://www.codersclan.net/gs_button/?repo_id=325" width="150"></a>
11
12 ## Requirements
13
14 * Ruby >= 1.9.3 (JRuby and C-Ruby/YARV are supported)
15
16 Capistrano support these source code version control systems out of the box:
17
18 * Git 1.8 or higher
19 * Mercurial
20 * SVN
21
22 Binaries for these VCS might be required on the local and/or the remote systems.
23
24 ## Installation
25
26 Add this line to your application's Gemfile:
0
1 # Capistrano: A deployment automation tool built on Ruby, Rake, and SSH.
2
3 [![Gem Version](https://badge.fury.io/rb/capistrano.svg)](http://badge.fury.io/rb/capistrano) [![Build Status](https://travis-ci.org/capistrano/capistrano.svg?branch=master)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](https://codeclimate.com/github/capistrano/capistrano/badges/gpa.svg)](https://codeclimate.com/github/capistrano/capistrano) [![CodersClan](https://img.shields.io/badge/get-support-blue.svg)](http://codersclan.net/?repo_id=325&source=small)
4
5 Capistrano is a framework for building automated deployment scripts. Although Capistrano itself is written in Ruby, it can easily be used to deploy projects of any language or framework, be it Rails, Java, or PHP.
6
7 Once installed, Capistrano gives you a `cap` tool to perform your deployments from the comfort of your command line.
8
9 ```
10 $ cd my-capistrano-enabled-project
11 $ cap production deploy
12 ```
13
14 When you run `cap`, Capistrano dutifully connects to your server(s) via SSH and executes the steps necessary to deploy your project. You can define those steps yourself by writing [Rake](https://github.com/ruby/rake) tasks, or by using pre-built task libraries provided by the Capistrano community.
15
16 Tasks are simple to make. Here's an example:
17
18 ```ruby
19 task :restart_sidekiq do
20 on roles(:worker) do
21 execute :service, "sidekiq restart"
22 end
23 end
24 after "deploy:published", "restart_sidekiq"
25 ```
26
27 *Note: This documentation is for the current version of Capistrano (3.x). If you are looking for Capistrano 2.x documentation, you can find it in [this archive](https://github.com/capistrano/capistrano-2.x-docs).*
28
29 ---
30
31 ## Contents
32
33 * [Features](#features)
34 * [Gotchas](#gotchas)
35 * [Quick start](#quick-start)
36 * [Finding help and documentation](#finding-help-and-documentation)
37 * [How to contribute](#how-to-contribute)
38 * [License](#license)
39
40 ## Features
41
42 There are many ways to automate deployments, from simple rsync bash scripts to complex containerized toolchains. Capistrano sits somewhere in the middle: it automates what you already know how to do manually with SSH, but in a repeatable, scalable fashion. There is no magic here!
43
44 Here's what makes Capistrano great:
45
46 #### Strong conventions
47
48 Capistrano defines a standard deployment process that all Capistrano-enabled projects follow by default. You don't have to decide how to structure your scripts, where deployed files should be placed on the server, or how to perform common tasks: Capistrano has done this work for you.
49
50 #### Multiple stages
51
52 Define your deployment once, and then easily parameterize it for multiple *stages* (environments), e.g. `qa`, `staging`, and `production`. No copy-and-paste necessary: you only need to specify what is different for each stage, like IP addresses.
53
54 #### Parallel execution
55
56 Deploying to a fleet of app servers? Capistrano can run each deployment task concurrently across those servers and uses connection pooling for speed.
57
58 #### Server roles
59
60 Your application may need many different types of servers: a database server, an app server, two web servers, and a job queue work server, for example. Capistrano lets you tag each server with one or more roles, so you can control what tasks are executed where.
61
62 #### Community driven
63
64 Capistrano is easily extensible using the rubygems package manager. Deploying a Rails app? Wordpress? Laravel? Chances are, someone has already written Capistrano tasks for your framework of choice and has distributed it as a gem. Many Ruby projects also come with Capistrano tasks built-in.
65
66 #### It's just SSH
67
68 Everything in Capistrano comes down to running SSH commands on remote servers. On the one hand, that makes Capistrano simple. On the other hand, if you aren't comfortable SSH-ing into a Linux box and doing stuff on the command-line, then Capistrano is probably not for you.
69
70 ## Gotchas
71
72 While Capistrano ships with a strong set of conventions that are common for all types of deployments, it needs help understanding the specifics of your project, and there are some things Capistrano is not suited to do.
73
74 #### Project specifics
75
76 Out of the box, Capistrano can deploy your code to server(s), but it does not know how to *execute* your code. Does `foreman` need to be run? Does Apache need to be restarted? You'll need to tell Capistrano how to do this part by writing these deployment steps yourself, or by finding a gem in the Capistrano community that does it for you.
77
78 #### Key-based SSH
79
80 Capistrano depends on connecting to your server(s) with SSH using key-based (i.e. password-less) authentication. You'll need this working before you can use Capistrano.
81
82 #### Provisioning
83
84 Likewise, your server(s) will likely need supporting software installed before you can perform a deployment. Capistrano itself has no requirements other than SSH, but your application probably needs database software, a web server like Apache or Nginx, and a language runtime like Java, Ruby, or PHP. These *server provisioning* steps are not done by Capistrano.
85
86 #### `sudo`, etc.
87
88 Capistrano is designed to deploy using a single, non-privileged SSH user, using a *non-interactive* SSH session. If your deployment requires `sudo`, interactive prompts, authenticating as one user but running commands as another, you can probably accomplish this with Capistrano, but it may be difficult. Your automated deployments will be much smoother if you can avoid such requirements.
89
90 #### Shells
91
92 Capistrano 3 expects a POSIX shell like Bash or Sh. Shells like tcsh, csh, and such may work, but probably will not.
93
94 ## Quick start
95
96 ### Requirements
97
98 * Ruby version 2.0 or higher on your local machine (MRI or Rubinius)
99 * A project that uses source control (Git, Mercurial, and Subversion support is built-in)
100 * The SCM binaries (e.g. `git`, `hg`) needed to check out your project must be installed on the server(s) you are deploying to
101 * [Bundler](http://bundler.io), along with a Gemfile for your project, are recommended
102
103 ### Install the Capistrano gem
104
105 Add Capistrano to your project's Gemfile:
27106
28107 ``` ruby
29 gem 'capistrano', '~> 3.3.0'
30 ```
31
32 And then execute:
108 group :development do
109 gem "capistrano", "~> 3.10"
110 end
111 ```
112
113 Then run Bundler to ensure Capistrano is downloaded and installed:
33114
34115 ``` sh
35116 $ bundle install
36117 ```
37118
38 Capify:
39 *make sure there's no "Capfile" or "capfile" present*
119 ### "Capify" your project
120
121 Make sure your project doesn't already have a "Capfile" or "capfile" present. Then run:
122
40123 ``` sh
41124 $ bundle exec cap install
42125 ```
43126
44 This creates the following files:
127 This creates all the necessary configuration files and directory structure for a Capistrano-enabled project with two stages, `staging` and `production`:
45128
46129 ```
47130 ├── Capfile
55138 └── tasks
56139 ```
57140
58 To create different stages:
141 To customize the stages that are created, use:
59142
60143 ``` sh
61144 $ bundle exec cap install STAGES=local,sandbox,qa,production
62145 ```
63146
64 ## Usage
147 Note that the files that Capistrano creates are simply templates to get you started. Make sure to edit the `deploy.rb` and stage files so that they contain values appropriate for your project and your target servers.
148
149 ### Command-line usage
65150
66151 ``` sh
67152 # list all available tasks
82167
83168 # trace through task invocations
84169 $ bundle exec cap production deploy --trace
85 ```
86
87 ## Testing
88
89 Capistrano has two test suites: an RSpec suite and a Cucumber suite. The
90 RSpec suite handles quick feedback unit specs. The Cucumber features are
91 an integration suite that uses Vagrant to deploy to a real virtual
92 server. In order to run the Cucumber suite you will need to install
93 [Vagrant](http://www.vagrantup.com/) and Vagrant supported
94 virtualization software like
95 [VirtualBox](https://www.virtualbox.org/wiki/Downloads).
96
97 ```
98 # To run the RSpec suite
99 $ rake spec
100
101 # To run the Cucumber suite
102 $ rake features
103
104 # To run the Cucumber suite and leave the VM running (faster for subsequent runs)
105 $ rake features KEEP_RUNNING=1
106 ```
107
108 ## SSHKit
109
110 [SSHKit](https://github.com/leehambley/sshkit) is the driver for SSH
111 connections behind the scenes in Capistrano. Depending on how deep you dig, you
112 might run into interfaces that come directly from SSHKit (the configuration is
113 a good example).
170
171 # lists all config variable before deployment tasks
172 $ bundle exec cap production deploy --print-config-variables
173 ```
174
175 ## Finding help and documentation
176
177 Capistrano is a large project encompassing multiple GitHub repositories and a community of plugins, and it can be overwhelming when you are just getting started. Here are resources that can help:
178
179 * **The [docs](docs) directory contains the official documentation**, and is used to generate the [Capistrano website](http://capistranorb.com)
180 * [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) has a Capistrano tag with lots of activity
181 * [The Capistrano mailing list](https://groups.google.com/forum/#!forum/capistrano) is low-traffic but is monitored by Capistrano contributors
182 * [CodersClan](http://codersclan.net/?repo_id=325&source=link) has Capistrano experts available to solve problems for bounties
183
184 Related GitHub repositories:
185
186 * [capistrano/sshkit](https://github.com/capistrano/sshkit) provides the SSH behavior that underlies Capistrano (when you use `execute` in a Capistrano task, you are using SSHKit)
187 * [capistrano/rails](https://github.com/capistrano/rails) is a very popular gem that adds Ruby on Rails deployment tasks
188 * [mattbrictson/airbrussh](https://github.com/mattbrictson/airbrussh) provides Capistrano's default log formatting
189
190 GitHub issues are for bug reports and feature requests. Please refer to the [CONTRIBUTING](CONTRIBUTING.md) document for guidelines on submitting GitHub issues.
191
192 If you think you may have discovered a security vulnerability in Capistrano, do not open a GitHub issue. Instead, please send a report to <security@capistranorb.com>.
193
194 ## How to contribute
195
196 Contributions to Capistrano, in the form of code, documentation or idea, are gladly accepted. Read the [DEVELOPMENT](DEVELOPMENT.md) document to learn how to hack on Capistrano's code, run the tests, and contribute your first pull request.
114197
115198 ## License
116199
0 # Releasing
1
2 ## Prerequisites
3
4 * You must have commit rights to the Capistrano repository.
5 * You must have push rights for the capistrano gem on rubygems.org.
6
7 ## How to release
8
9 1. Run `bundle install` to make sure that you have all the gems necessary for testing and releasing.
10 2. **Ensure all tests are passing by running `rake spec` and `rake features`.**
11 3. Determine which would be the correct next version number according to [semver](http://semver.org/).
12 4. Update the version in `./lib/capistrano/version.rb`.
13 4. Update the version in the `./README.md` Gemfile example (`gem "capistrano", "~> X.Y"`).
14 5. Update the `CHANGELOG`.
15 6. Commit the changelog and version in a single commit, the message should be "Preparing vX.Y.Z"
16 7. Run `rake release`; this will tag, push to GitHub, and publish to rubygems.org.
00 require "bundler/gem_tasks"
11 require "cucumber/rake/task"
22 require "rspec/core/rake_task"
3 require "rubocop/rake_task"
34
4 task :default => :spec
5 task default: %i(spec rubocop)
56 RSpec::Core::RakeTask.new
67
78 Cucumber::Rake::Task.new(:features)
89
10 desc "Run RuboCop checks"
11 RuboCop::RakeTask.new
0 # Capistrano 3.7.0 upgrade guide
1
2 ## The :scm variable is deprecated
3
4 Up until now, Capistrano's SCM was configured using the `:scm` variable:
5
6 ```ruby
7 # This is now deprecated
8 set :scm, :svn
9 ```
10
11 To avoid deprecation warnings:
12
13 1. Remove `set :scm, ...` from your Capistrano configuration.
14 2. Add *one* of the following SCM declarations to your `Capfile` after `require "capistrano/deploy"`:
15
16 ```ruby
17 # To use Git
18 require "capistrano/scm/git"
19 install_plugin Capistrano::SCM::Git
20
21 # To use Mercurial
22 require "capistrano/scm/hg"
23 install_plugin Capistrano::SCM::Hg
24
25 # To use Subversion
26 require "capistrano/scm/svn"
27 install_plugin Capistrano::SCM::Svn
28 ```
29
30 ## This is the last release where Git is the automatic default
31
32 If you do not specify an SCM, Capistrano assumes Git. However this behavior is
33 now deprecated. Add this to your Capfile to avoid deprecation warnings:
34
35 ```ruby
36 require "capistrano/scm/git"
37 install_plugin Capistrano::SCM::Git
38 ```
39
40 ## :git_strategy, :hg_strategy, and :svn_strategy are removed
41
42 Capistrano 3.7.0 has a rewritten SCM system that relies on "plugins". This
43 system is more flexible than the old "strategy" system that only allowed certain
44 parts of SCM tasks to be customized.
45
46 If your deployment relies on a custom SCM strategy, you will need to rewrite
47 that strategy to be a full-fledged SCM plugin instead. There is a fairly
48 straightforward migration path: write your plugin to be a subclass of the
49 built-in SCM that you want to customize. For example:
50
51 ```ruby
52 require "capistrano/scm/git"
53
54 class MyCustomGit < Capistrano::SCM::Git
55 # Override the methods you wish to customize, e.g.:
56 def clone_repo
57 # ...
58 end
59 end
60 ```
61
62 Then use your plugin in by loading it in the Capfile:
63
64 ```ruby
65 require_relative "path/to/my_custom_git.rb"
66 install_plugin MyCustomGit
67 ```
68
69 ## Existing third-party SCMs are deprecated
70
71 If you are using a third-party SCM, you can continue using it without
72 changes, but you will see deprecation warnings. Contact the maintainer of the
73 third-party SCM gem and ask them about modifying the gem to work with the new
74 Capistrano 3.7.0 SCM plugin system.
75
76 ## remote_file is removed
77
78 The `remote_file` method is no longer in Capistrano 3.7.0. You can read the
79 discussion that led to its removal here:
80 [issue 762](https://github.com/capistrano/capistrano/issues/762).
81
82 There is no direct replacement. To migrate to 3.7.0, you will need to rewrite
83 any parts of your deployment that use `remote_file` to use a different
84 mechanism for uploading files. Consider using the `upload!` method directly in
85 a procedural fashion instead.
00 #!/usr/bin/env ruby
1 require 'capistrano/all'
1 require "capistrano/all"
22 Capistrano::Application.new.run
00 # -*- encoding: utf-8 -*-
1 lib = File.expand_path('../lib', __FILE__)
1
2 lib = File.expand_path("../lib", __FILE__)
23 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3 require 'capistrano/version'
4 require "capistrano/version"
45
56 Gem::Specification.new do |gem|
67 gem.name = "capistrano"
78 gem.version = Capistrano::VERSION
89 gem.authors = ["Tom Clements", "Lee Hambley"]
910 gem.email = ["seenmyfate@gmail.com", "lee.hambley@gmail.com"]
10 gem.description = %q{Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.}
11 gem.summary = %q{Capistrano - Welcome to easy deployment with Ruby over SSH}
11 gem.description = "Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH."
12 gem.summary = "Capistrano - Welcome to easy deployment with Ruby over SSH"
1213 gem.homepage = "http://capistranorb.com/"
1314
14 gem.files = `git ls-files`.split($/)
15 gem.executables = ['cap', 'capify']
15 gem.files = `git ls-files -z`.split("\x0").reject { |f| f =~ /^docs/ }
16 gem.executables = %w(cap capify)
1617 gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
1718 gem.require_paths = ["lib"]
1819
19 gem.licenses = ['MIT']
20 gem.licenses = ["MIT"]
2021
21 gem.post_install_message = <<eos
22 Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr
22 gem.required_ruby_version = ">= 2.0"
23 gem.add_dependency "airbrussh", ">= 1.0.0"
24 gem.add_dependency "i18n"
25 gem.add_dependency "rake", ">= 10.0.0"
26 gem.add_dependency "sshkit", ">= 1.9.0"
2327
24 If you're upgrading Capistrano from 2.x, we recommend to read the upgrade guide: http://goo.gl/4536kB
25
26 The `deploy:restart` hook for passenger applications is now in a separate gem called capistrano-passenger. Just add it to your Gemfile and require it in your Capfile.
27 eos
28
29 gem.required_ruby_version = '>= 1.9.3'
30 gem.add_dependency 'sshkit', '~> 1.3'
31 gem.add_dependency 'rake', '>= 10.0.0'
32 gem.add_dependency 'i18n'
33
34 gem.add_development_dependency 'rspec'
35 gem.add_development_dependency 'mocha'
28 gem.add_development_dependency "danger"
29 gem.add_development_dependency "mocha"
30 gem.add_development_dependency "rspec"
31 gem.add_development_dependency "rubocop", "0.48.1"
3632 end
66 Scenario: Creating the repo
77 When I run cap "git:check"
88 Then the task is successful
9 And references in the remote repo are listed
9 And git wrapper permissions are 0700
1010
1111 Scenario: Creating the directory structure
1212 When I run cap "deploy:check:directories"
5151 When I run cap "deploy:symlink:release"
5252 Then the current directory will be a symlink to the release
5353
54 Scenario: Cleanup
55 Given config stage file has line "set :keep_releases, 3"
56 And 5 valid existing releases
57 And an invalid release named "new"
58 When I run cap "deploy:cleanup"
59 Then 3 valid releases are kept
60 And the invalid "new" release is ignored
61
62 Scenario: Cleanup after many failed releases doesn't remove last good release
63 Given config stage file has line "set :keep_releases, 2"
64 And I make 2 deployments
65 And an invalid release named "77777777777777"
66 And an invalid release named "88888888888888"
67 And an invalid release named "99999999999999"
68 When I run cap "deploy:cleanup"
69 Then 3 valid releases are kept
70 And the current directory will be a symlink to the release
71
72 Scenario: Rolling Back
73 Given I make 2 deployments
74 When I run cap "deploy:rollback"
75 Then the current symlink points to the previous release
76
77 Scenario: Rolling Back to a specific release
78 Given I make 3 deployments
79 When I rollback to a specific release
80 Then the current symlink points to that specific release
81
0 Feature: Doctor
1
2 Background:
3 Given a test app with the default configuration
4
5 Scenario: Running the doctor task
6 When I run cap "doctor"
7 Then the task is successful
8 And contains "Environment" in the output
9 And contains "Gems" in the output
10 And contains "Variables" in the output
00 Feature: Installation
11
22 Background:
3 Given a test app with the default configuration
3 Given a test app without any configuration
4
5 Scenario: The "install" task documentation can be viewed
6 When I run "cap -T"
7 Then the task is successful
8 And contains "cap install" in the output
49
510 Scenario: With default stages
6 When I run cap "install"
11 When I run "cap install"
712 Then the deploy.rb file is created
813 And the default stage files are created
914 And the tasks folder is created
1015
1116 Scenario: With specified stages
12 When I run cap "install STAGES=qa,production"
17 When I run "cap install STAGES=qa,production"
1318 Then the deploy.rb file is created
1419 And the specified stage files are created
1520 And the tasks folder is created
+0
-14
features/remote_file_task.feature less more
0 Feature: Remote file task
1
2 Background:
3 Given a test app with the default configuration
4 And a custom task to generate a file
5 And servers with the roles app and web
6
7 Scenario: Where the file does not exist
8 When I run cap "deploy:check:linked_files"
9 Then it creates the file with the remote_task prerequisite
10
11 Scenario: Where the file already exists
12 When I run cap "deploy:check:linked_files"
13 Then it will not recreate the file
0 Feature: Stage failure
1
2 Background:
3 Given a test app with the default configuration
4 And a stage file named deploy.rb
5
6 Scenario: Running a task
7 When I run cap "doctor"
8 Then the task fails
0 require "shellwords"
1
02 Then(/^references in the remote repo are listed$/) do
1 expect(@output).to include('refs/heads/master')
3 expect(@output).to include("refs/heads/master")
4 end
5
6 Then(/^git wrapper permissions are 0700$/) do
7 permissions_test = %Q([ $(stat -c "%a" #{TestApp.git_wrapper_path.shellescape}) == "700" ])
8 _stdout, _stderr, status = vagrant_cli_command("ssh -c #{permissions_test.shellescape}")
9
10 expect(status).to be_success
211 end
312
413 Then(/^the shared path is created$/) do
918 run_vagrant_command(test_dir_exists(TestApp.releases_path))
1019 end
1120
21 Then(/^(\d+) valid releases are kept/) do |num|
22 test = %Q([ $(ls -g #{TestApp.releases_path} | grep -E '[0-9]{14}' | wc -l) == "#{num}" ])
23 _, _, status = vagrant_cli_command("ssh -c #{test.shellescape}")
24 expect(status).to be_success
25 end
26
27 Then(/^the invalid (.+) release is ignored$/) do |filename|
28 test = "ls -g #{TestApp.releases_path} | grep #{filename}"
29 _, _, status = vagrant_cli_command("ssh -c #{test.shellescape}")
30 expect(status).to be_success
31 end
32
1233 Then(/^directories in :linked_dirs are created in shared$/) do
1334 TestApp.linked_dirs.each do |dir|
1435 run_vagrant_command(test_dir_exists(TestApp.shared_path.join(dir)))
1738
1839 Then(/^directories referenced in :linked_files are created in shared$/) do
1940 dirs = TestApp.linked_files.map { |path| TestApp.shared_path.join(path).dirname }
20 dirs.each do | dir|
41 dirs.each do |dir|
2142 run_vagrant_command(test_dir_exists(dir))
2243 end
2344 end
4465 end
4566
4667 Then(/^the current directory will be a symlink to the release$/) do
47 run_vagrant_command(test_symlink_exists(TestApp.current_path))
68 run_vagrant_command(exists?("e", TestApp.current_path))
4869 end
4970
5071 Then(/^the deploy\.rb file is created$/) do
51 file = TestApp.test_app_path.join('config/deploy.rb')
52 expect(File.exists?(file)).to be true
72 file = TestApp.test_app_path.join("config/deploy.rb")
73 expect(File.exist?(file)).to be true
5374 end
5475
5576 Then(/^the default stage files are created$/) do
56 staging = TestApp.test_app_path.join('config/deploy/staging.rb')
57 production = TestApp.test_app_path.join('config/deploy/production.rb')
58 expect(File.exists?(staging)).to be true
59 expect(File.exists?(production)).to be true
77 staging = TestApp.test_app_path.join("config/deploy/staging.rb")
78 production = TestApp.test_app_path.join("config/deploy/production.rb")
79 expect(File.exist?(staging)).to be true
80 expect(File.exist?(production)).to be true
6081 end
6182
6283 Then(/^the tasks folder is created$/) do
63 path = TestApp.test_app_path.join('lib/capistrano/tasks')
64 expect(Dir.exists?(path)).to be true
84 path = TestApp.test_app_path.join("lib/capistrano/tasks")
85 expect(Dir.exist?(path)).to be true
6586 end
6687
6788 Then(/^the specified stage files are created$/) do
68 qa = TestApp.test_app_path.join('config/deploy/qa.rb')
69 production = TestApp.test_app_path.join('config/deploy/production.rb')
70 expect(File.exists?(qa)).to be true
71 expect(File.exists?(production)).to be true
89 qa = TestApp.test_app_path.join("config/deploy/qa.rb")
90 production = TestApp.test_app_path.join("config/deploy/production.rb")
91 expect(File.exist?(qa)).to be true
92 expect(File.exist?(production)).to be true
7293 end
7394
7495 Then(/^it creates the file with the remote_task prerequisite$/) do
90111 end
91112
92113 Then(/^the failure task will run$/) do
93 failed = TestApp.shared_path.join('failed')
114 failed = TestApp.shared_path.join("failed")
94115 run_vagrant_command(test_file_exists(failed))
95116 end
96117
97118 Then(/^the failure task will not run$/) do
98 failed = TestApp.shared_path.join('failed')
119 failed = TestApp.shared_path.join("failed")
99120 expect { run_vagrant_command(test_file_exists(failed)) }
100121 .to raise_error(VagrantHelpers::VagrantSSHCommandError)
101122 end
102123
103124 When(/^an error is raised$/) do
104 error = TestApp.shared_path.join('fail')
125 error = TestApp.shared_path.join("fail")
105126 run_vagrant_command(test_file_exists(error))
106127 end
107128
116137 Then(/doesn't contain "([^"]*)" in the output/) do |expected|
117138 expect(@output).not_to include(expected)
118139 end
140
141 Then(/the current symlink points to the previous release/) do
142 previous_release_path = @release_paths[-2]
143
144 run_vagrant_command(symlinked?(TestApp.current_path, previous_release_path))
145 end
146
147 Then(/^the current symlink points to that specific release$/) do
148 specific_release_path = TestApp.releases_path.join(@rollback_release)
149
150 run_vagrant_command(symlinked?(TestApp.current_path, specific_release_path))
151 end
00 When(/^I run cap "(.*?)"$/) do |task|
11 @success, @output = TestApp.cap(task)
2 end
3
4 When(/^I run cap "(.*?)" within the "(.*?)" directory$/) do |task, directory|
5 @success, @output = TestApp.cap(task, directory)
26 end
37
48 When(/^I run cap "(.*?)" as part of a release$/) do |task|
913 @success, @output = TestApp.run(command)
1014 end
1115
16 When(/^I rollback to a specific release$/) do
17 @rollback_release = @release_paths.first.split("/").last
18
19 step %Q{I run cap "deploy:rollback ROLLBACK_RELEASE=#{@rollback_release}"}
20 end
11 TestApp.install
22 end
33
4 Given(/^a test app without any configuration$/) do
5 TestApp.create_test_app
6 end
7
48 Given(/^servers with the roles app and web$/) do
5 vagrant_cli_command('up') rescue nil
9 begin
10 vagrant_cli_command("up")
11 rescue
12 nil
13 end
614 end
715
816 Given(/^a linked file "(.*?)"$/) do |file|
1220
1321 Given(/^file "(.*?)" exists in shared path$/) do |file|
1422 file_shared_path = TestApp.shared_path.join(file)
15 run_vagrant_command("mkdir -p #{TestApp.shared_path}")
23 run_vagrant_command("mkdir -p #{file_shared_path.dirname}")
1624 run_vagrant_command("touch #{file_shared_path}")
25 end
26
27 Given(/^all linked files exists in shared path$/) do
28 TestApp.linked_files.each do |linked_file|
29 step %Q{file "#{linked_file}" exists in shared path}
30 end
1731 end
1832
1933 Given(/^file "(.*?)" does not exist in shared path$/) do |file|
2337 end
2438
2539 Given(/^a custom task to generate a file$/) do
26 TestApp.copy_task_to_test_app('spec/support/tasks/database.rake')
40 TestApp.copy_task_to_test_app("spec/support/tasks/database.rake")
2741 end
2842
2943 Given(/^a task which executes as root$/) do
30 TestApp.copy_task_to_test_app('spec/support/tasks/root.rake')
44 TestApp.copy_task_to_test_app("spec/support/tasks/root.rake")
3145 end
3246
3347 Given(/config stage file has line "(.*?)"/) do |line|
3549 end
3650
3751 Given(/^the configuration is in a custom location$/) do
38 TestApp.move_configuration_to_custom_location('app')
52 TestApp.move_configuration_to_custom_location("app")
3953 end
4054
4155 Given(/^a custom task that will simulate a failure$/) do
42 safely_remove_file(TestApp.shared_path.join('failed'))
43 TestApp.copy_task_to_test_app('spec/support/tasks/fail.rake')
56 safely_remove_file(TestApp.shared_path.join("failed"))
57 TestApp.copy_task_to_test_app("spec/support/tasks/fail.rake")
4458 end
4559
4660 Given(/^a custom task to run in the event of a failure$/) do
47 safely_remove_file(TestApp.shared_path.join('failed'))
48 TestApp.copy_task_to_test_app('spec/support/tasks/failed.rake')
61 safely_remove_file(TestApp.shared_path.join("failed"))
62 TestApp.copy_task_to_test_app("spec/support/tasks/failed.rake")
4963 end
64
65 Given(/^a stage file named (.+)$/) do |filename|
66 TestApp.write_local_stage_file(filename)
67 end
68
69 Given(/^I make (\d+) deployments$/) do |count|
70 step "all linked files exists in shared path"
71
72 @release_paths = (1..count.to_i).map do
73 TestApp.cap("deploy")
74 stdout, _stderr = run_vagrant_command("readlink #{TestApp.current_path}")
75
76 stdout.strip
77 end
78 end
79
80 Given(/^(\d+) valid existing releases$/) do |num|
81 a_day = 86_400 # in seconds
82 offset = -(a_day * num.to_i)
83 num.to_i.times do
84 run_vagrant_command("mkdir -p #{TestApp.release_path(TestApp.timestamp(offset))}")
85 offset += a_day
86 end
87 end
88
89 Given(/^an invalid release named "(.+)"$/) do |filename|
90 run_vagrant_command("mkdir -p #{TestApp.release_path(filename)}")
91 end
0 Feature: cap can be run from a subdirectory, and will still find the Capfile
1
2 Background:
3 Given a test app with the default configuration
4 And servers with the roles app and web
5
6 Scenario: Running cap from a subdirectory
7 When I run cap "git:check" within the "config" directory
8 Then the task is successful
0 PROJECT_ROOT = File.expand_path('../../../', __FILE__)
1 VAGRANT_ROOT = File.join(PROJECT_ROOT, 'spec/support')
2 VAGRANT_BIN = ENV['VAGRANT_BIN'] || "vagrant"
0 PROJECT_ROOT = File.expand_path("../../../", __FILE__)
1 VAGRANT_ROOT = File.join(PROJECT_ROOT, "spec/support")
2 VAGRANT_BIN = ENV["VAGRANT_BIN"] || "vagrant"
33
44 at_exit do
5 if ENV['KEEP_RUNNING']
5 if ENV["KEEP_RUNNING"]
66 VagrantHelpers.run_vagrant_command("rm -rf /home/vagrant/var")
77 end
88 end
99
10 require_relative '../../spec/support/test_app'
10 require_relative "../../spec/support/test_app"
00 module RemoteCommandHelpers
11 def test_dir_exists(path)
2 exists?('d', path)
2 exists?("d", path)
33 end
44
55 def test_symlink_exists(path)
6 exists?('L', path)
6 exists?("L", path)
77 end
88
99 def test_file_exists(path)
10 exists?('f', path)
10 exists?("f", path)
1111 end
1212
1313 def exists?(type, path)
14 %{[ -#{type} "#{path}" ]}
14 %Q{[ -#{type} "#{path}" ]}
1515 end
1616
17 def safely_remove_file(path)
18 run_vagrant_command("rm #{test_file}") rescue VagrantHelpers::VagrantSSHCommandError
17 def symlinked?(symlink_path, target_path)
18 "[ #{symlink_path} -ef #{target_path} ]"
19 end
20
21 def safely_remove_file(_path)
22 run_vagrant_command("rm #{test_file}")
23 rescue
24 VagrantHelpers::VagrantSSHCommandError
1925 end
2026 end
2127
0 require "open3"
1
02 module VagrantHelpers
13 extend self
24
35 class VagrantSSHCommandError < RuntimeError; end
46
57 at_exit do
6 if ENV['KEEP_RUNNING']
8 if ENV["KEEP_RUNNING"]
79 puts "Vagrant vm will be left up because KEEP_RUNNING is set."
810 puts "Rerun without KEEP_RUNNING set to cleanup the vm."
911 else
1315
1416 def vagrant_cli_command(command)
1517 puts "[vagrant] #{command}"
16 Dir.chdir(VAGRANT_ROOT) do
17 `#{VAGRANT_BIN} #{command} 2>&1`.split("\n").each do |line|
18 puts "[vagrant] #{line}"
19 end
18 stdout, stderr, status = Dir.chdir(VAGRANT_ROOT) do
19 Open3.capture3("#{VAGRANT_BIN} #{command}")
2020 end
21 $?
21
22 (stdout + stderr).each_line { |line| puts "[vagrant] #{line}" }
23
24 [stdout, stderr, status]
2225 end
2326
2427 def run_vagrant_command(command)
25 if (status = vagrant_cli_command("ssh -c #{command.inspect}")).success?
26 true
27 else
28 fail VagrantSSHCommandError, status
29 end
28 stdout, stderr, status = vagrant_cli_command("ssh -c #{command.inspect}")
29 return [stdout, stderr] if status.success?
30 raise VagrantSSHCommandError, status
3031 end
31
3232 end
3333
3434 World(VagrantHelpers)
00 #!/usr/bin/env cap
11 include Capistrano::DSL
2 require 'capistrano/install'
2 require "capistrano/install"
0 require 'rake'
1 require 'sshkit'
0 require "rake"
1 require "sshkit"
22
3 require 'io/console'
3 require "io/console"
44
55 Rake.application.options.trace = true
66
7 require 'capistrano/version'
8 require 'capistrano/version_validator'
9 require 'capistrano/i18n'
10 require 'capistrano/dsl'
11 require 'capistrano/application'
12 require 'capistrano/configuration'
7 require "capistrano/version"
8 require "capistrano/version_validator"
9 require "capistrano/i18n"
10 require "capistrano/dsl"
11 require "capistrano/application"
12 require "capistrano/configuration"
13 require "capistrano/configuration/scm_resolver"
1314
1415 module Capistrano
15
1616 end
00 module Capistrano
11 class Application < Rake::Application
2
32 def initialize
43 super
5 @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} << capfile
4 @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb}
65 end
76
87 def name
2019 switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/
2120 end
2221
23 super.push(version, dry_run, roles, hostfilter)
22 super.push(version, dry_run, roles, hostfilter, print_config_variables)
2423 end
2524
2625 def handle_options
27 options.rakelib = ['rakelib']
26 options.rakelib = ["rakelib"]
2827 options.trace_output = $stderr
2928
3029 OptionParser.new do |opts|
4746 end
4847
4948 standard_rake_options.each { |args| opts.on(*args) }
50 opts.environment('RAKEOPT')
49 opts.environment("RAKEOPT")
5150 end.parse!
5251 end
53
5452
5553 def top_level_tasks
5654 if tasks_without_stage_dependency.include?(@top_level_tasks.first)
6260
6361 def display_error_message(ex)
6462 unless options.backtrace
65 if loc = Rake.application.find_rakefile_location
66 whitelist = (@imported.dup << loc[0]).map{|f| File.absolute_path(f, loc[1])}
67 pattern = %r@^(?!#{whitelist.map{|p| Regexp.quote(p)}.join('|')})@
68 Rake.application.options.suppress_backtrace_pattern = pattern
69 end
63 Rake.application.options.suppress_backtrace_pattern = backtrace_pattern if backtrace_pattern
7064 trace "(Backtrace restricted to imported tasks)"
7165 end
66
7267 super
7368 end
7469
8075 end
8176 end
8277
78 # allows the `cap install` task to load without a capfile
79 def find_rakefile_location
80 if (location = super).nil?
81 [capfile, Dir.pwd]
82 else
83 location
84 end
85 end
86
8387 private
8488
89 def backtrace_pattern
90 loc = Rake.application.find_rakefile_location
91 return unless loc
92
93 whitelist = (@imported.dup << loc[0]).map { |f| File.absolute_path(f, loc[1]) }
94 /^(?!#{whitelist.map { |p| Regexp.quote(p) }.join('|')})/
95 end
96
8597 def load_imports
86 if options.show_tasks
87 invoke 'load:defaults'
88 set(:stage, '')
98 if options.show_tasks && Rake::Task.task_defined?("load:defaults")
99 invoke "load:defaults"
100 set(:stage, "")
89101 Dir[deploy_config_path].each { |f| add_import f }
90102 end
91103
92104 super
93105 end
94106
95 # allows the `cap install` task to load without a capfile
96107 def capfile
97 File.expand_path(File.join(File.dirname(__FILE__),'..','Capfile'))
108 File.expand_path(File.join(File.dirname(__FILE__), "..", "Capfile"))
98109 end
99110
100111 def version
101 ['--version', '-V',
112 ["--version", "-V",
102113 "Display the program version.",
103 lambda { |value|
104 puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{RAKEVERSION})"
114 lambda do |_value|
115 puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{Rake::VERSION})"
105116 exit
106 }
107 ]
117 end]
108118 end
109119
110120 def dry_run
111 ['--dry-run', '-n',
121 ["--dry-run", "-n",
112122 "Do a dry run without executing actions",
113 lambda { |value|
123 lambda do |_value|
114124 Configuration.env.set(:sshkit_backend, SSHKit::Backend::Printer)
115 }
116 ]
125 end]
117126 end
118127
119128 def roles
120 ['--roles ROLES', '-r',
129 ["--roles ROLES", "-r",
121130 "Run SSH commands only on hosts matching these roles",
122 lambda { |value|
131 lambda do |value|
123132 Configuration.env.add_cmdline_filter(:role, value)
124 }
125 ]
133 end]
126134 end
127135
128136 def hostfilter
129 ['--hosts HOSTS', '-z',
137 ["--hosts HOSTS", "-z",
130138 "Run SSH commands only on matching hosts",
131 lambda { |value|
139 lambda do |value|
132140 Configuration.env.add_cmdline_filter(:host, value)
133 }
134 ]
141 end]
135142 end
136143
144 def print_config_variables
145 ["--print-config-variables", "-p",
146 "Display the defined config variables before starting the deployment tasks.",
147 lambda do |_value|
148 Configuration.env.set(:print_config_variables, true)
149 end]
150 end
137151 end
138
139152 end
0 module Capistrano
1 class Configuration
2 class EmptyFilter
3 def filter(_servers)
4 []
5 end
6 end
7 end
8 end
0 require 'capistrano/configuration'
0 require "capistrano/configuration"
1 require "capistrano/configuration/empty_filter"
2 require "capistrano/configuration/host_filter"
3 require "capistrano/configuration/null_filter"
4 require "capistrano/configuration/role_filter"
15
26 module Capistrano
37 class Configuration
48 class Filter
5 def initialize type, values = nil
6 raise "Invalid filter type #{type}" unless [:host,:role].include? type
7 av = Array(values).dup
8 @mode = case
9 when av.size == 0 then :none
10 when av.include?(:all) then :all
11 else type
12 end
13 @rex = case @mode
14 when :host
15 av.map!{|v| (v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/) ? v.split(',') : v }
16 av.flatten!
17 av.map! do |v|
18 case v
19 when Regexp then v
20 else
21 vs = v.to_s
22 vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
23 end
24 end
25 Regexp.union av
26 when :role
27 av.map!{|v| v.is_a?(String) ? v.split(',') : v }
28 av.flatten!
29 av.map! do |v|
30 case v
31 when Regexp then v
32 else
33 vs = v.to_s
34 vs =~ %r{^/(.+)/$} ? Regexp.new($1) : %r{^#{vs}$}
35 end
36 end
37 Regexp.union av
38 else
39 nil
40 end
9 def initialize(type, values=nil)
10 raise "Invalid filter type #{type}" unless %i(host role).include? type
11 av = Array(values)
12 @strategy = if av.empty? then EmptyFilter.new
13 elsif av.include?(:all) || av.include?("all") then NullFilter.new
14 elsif type == :host then HostFilter.new(values)
15 elsif type == :role then RoleFilter.new(values)
16 else NullFilter.new
17 end
4118 end
42 def filter servers
43 as = Array(servers)
44 case @mode
45 when :none then return []
46 when :all then return servers
47 when :host
48 as.select {|s| @rex.match s.hostname}
49 when :role
50 as.select {|s| s.roles.any? {|r| @rex.match r} }
51 end
19
20 def filter(servers)
21 @strategy.filter servers
5222 end
5323 end
5424 end
0 module Capistrano
1 class Configuration
2 class HostFilter
3 def initialize(values)
4 av = Array(values).dup
5 av = av.flat_map { |v| v.is_a?(String) && v =~ /^(?<name>[-A-Za-z1-9.]+)(,\g<name>)*$/ ? v.split(",") : v }
6 @rex = regex_matcher(av)
7 end
8
9 def filter(servers)
10 Array(servers).select { |s| @rex.match s.to_s }
11 end
12
13 private
14
15 def regex_matcher(values)
16 values.map! do |v|
17 case v
18 when Regexp then v
19 else
20 vs = v.to_s
21 vs =~ /^[-A-Za-z0-9.]+$/ ? /^#{Regexp.quote(vs)}$/ : Regexp.new(vs)
22 end
23 end
24 Regexp.union values
25 end
26 end
27 end
28 end
0 module Capistrano
1 class Configuration
2 class NullFilter
3 def filter(servers)
4 servers
5 end
6 end
7 end
8 end
0 # Encapsulates the logic for installing plugins into Capistrano. Plugins must
1 # simply conform to a basic API; the PluginInstaller takes care of invoking the
2 # API at appropriate times.
3 #
4 # This class is not used directly; instead it is typically accessed via the
5 # `install_plugin` method of the Capistrano DSL.
6 #
7 module Capistrano
8 class Configuration
9 class PluginInstaller
10 # "Installs" a Plugin into Capistrano by loading its tasks, hooks, and
11 # defaults at the appropriate time. The hooks in particular can be
12 # skipped, if you want full control over when and how the plugin's tasks
13 # are executed. Simply pass `load_hooks:false` to opt out.
14 #
15 # The plugin class or instance may be provided. These are equivalent:
16 #
17 # install(Capistrano::SCM::Git)
18 # install(Capistrano::SCM::Git.new)
19 #
20 # Note that the :load_immediately flag is for internal use only and will
21 # be removed in an upcoming release.
22 #
23 def install(plugin, load_hooks: true, load_immediately: false)
24 plugin = plugin.is_a?(Class) ? plugin.new : plugin
25
26 plugin.define_tasks
27 plugin.register_hooks if load_hooks
28 @scm_installed ||= provides_scm?(plugin)
29
30 if load_immediately
31 plugin.set_defaults
32 else
33 Rake::Task.define_task("load:defaults") do
34 plugin.set_defaults
35 end
36 end
37 end
38
39 def scm_installed?
40 @scm_installed
41 end
42
43 private
44
45 def provides_scm?(plugin)
46 plugin.respond_to?(:scm?) && plugin.scm?
47 end
48 end
49 end
50 end
00 module Capistrano
11 class Configuration
22 class Question
3
4 def initialize(key, default, options = {})
5 @key, @default, @options = key, default, options
3 def initialize(key, default, options={})
4 @key = key
5 @default = default
6 @options = options
67 end
78
89 def call
1112 end
1213
1314 private
15
1416 attr_reader :key, :default, :options
1517
1618 def ask_question
1719 $stdout.print question
20 $stdout.flush
1821 end
1922
2023 def value_or_default
2730
2831 def response
2932 return @response if defined? @response
30
33
3134 @response = (gets || "").chomp
3235 end
33
36
3437 def gets
3538 if echo?
3639 $stdin.gets
3740 else
38 $stdin.noecho(&:gets).tap{ $stdout.print "\n" }
41 $stdin.noecho(&:gets).tap { $stdout.print "\n" }
3942 end
4043 rescue Errno::EIO
4144 # when stdio gets closed
45 return
4246 end
43
47
4448 def question
45 I18n.t(:question, key: key, default_value: default, scope: :capistrano)
49 if default.nil?
50 I18n.t(:question, key: key, scope: :capistrano)
51 else
52 I18n.t(:question_default, key: key, default_value: default, scope: :capistrano)
53 end
4654 end
4755
4856 def echo?
0 module Capistrano
1 class Configuration
2 class RoleFilter
3 def initialize(values)
4 av = Array(values).dup
5 av = av.flat_map { |v| v.is_a?(String) ? v.split(",") : v }
6 @rex = regex_matcher(av)
7 end
8
9 def filter(servers)
10 Array(servers).select { |s| s.is_a?(String) ? false : s.roles.any? { |r| @rex.match r } }
11 end
12
13 private
14
15 def regex_matcher(values)
16 values.map! do |v|
17 case v
18 when Regexp then v
19 else
20 vs = v.to_s
21 vs =~ %r{^/(.+)/$} ? Regexp.new($1) : /^#{Regexp.quote(vs)}$/
22 end
23 end
24 Regexp.union values
25 end
26 end
27 end
28 end
0 module Capistrano
1 class Configuration
2 # In earlier versions of Capistrano, users would specify the desired SCM
3 # implementation using `set :scm, :git`, for example. Capistrano would then
4 # load the matching .rb file based on this variable.
5 #
6 # Now we expect users to explicitly `require` and call `new` on the desired
7 # SCM implementation in their Capfile. The `set` technique is deprecated.
8 #
9 # This SCMResolver class takes care of managing the transition from the old
10 # to new system. It maintains the legacy behavior, but prints deprecation
11 # warnings when it is used.
12 #
13 # To maintain backwards compatibility, the resolver will load the Git SCM by
14 # if default it determines that no SCM has been explicitly specified or
15 # loaded. To force no SCM to be used at all, use `set :scm, nil`. This hack
16 # won't be necessary once backwards compatibility is removed in a future
17 # version.
18 #
19 # TODO: Remove this class entirely in Capistrano 4.0.
20 #
21 class SCMResolver
22 DEFAULT_GIT = :"default-git"
23
24 include Capistrano::DSL
25
26 def resolve
27 return if scm_name.nil?
28 set(:scm, :git) if using_default_scm?
29
30 print_deprecation_warnings_if_applicable
31
32 # Note that `scm_plugin_installed?` comes from Capistrano::DSL
33 if scm_plugin_installed?
34 delete(:scm)
35 return
36 end
37
38 if built_in_scm_name?
39 load_built_in_scm
40 else
41 # Compatibility with existing 3.x third-party SCMs
42 register_legacy_scm_hooks
43 load_legacy_scm_by_name
44 end
45 end
46
47 private
48
49 def using_default_scm?
50 return @using_default_scm if defined? @using_default_scm
51 @using_default_scm = (fetch(:scm) == DEFAULT_GIT)
52 end
53
54 def scm_name
55 fetch(:scm)
56 end
57
58 def load_built_in_scm
59 require "capistrano/scm/#{scm_name}"
60 scm_class = Object.const_get(built_in_scm_plugin_class_name)
61 # We use :load_immediately because we are initializing the SCM plugin
62 # late in the load process and therefore can't use the standard
63 # load:defaults technique.
64 install_plugin(scm_class, load_immediately: true)
65 end
66
67 def load_legacy_scm_by_name
68 load("capistrano/#{scm_name}.rb")
69 end
70
71 def third_party_scm_name?
72 !built_in_scm_name?
73 end
74
75 def built_in_scm_name?
76 %w(git hg svn).include?(scm_name.to_s.downcase)
77 end
78
79 def built_in_scm_plugin_class_name
80 "Capistrano::SCM::#{scm_name.to_s.capitalize}"
81 end
82
83 # rubocop:disable Style/GuardClause
84 def register_legacy_scm_hooks
85 if Rake::Task.task_defined?("deploy:new_release_path")
86 after "deploy:new_release_path", "#{scm_name}:create_release"
87 end
88
89 if Rake::Task.task_defined?("deploy:check")
90 before "deploy:check", "#{scm_name}:check"
91 end
92
93 if Rake::Task.task_defined?("deploy:set_current_revision")
94 before "deploy:set_current_revision",
95 "#{scm_name}:set_current_revision"
96 end
97 end
98 # rubocop:enable Style/GuardClause
99
100 def print_deprecation_warnings_if_applicable
101 if using_default_scm?
102 warn_add_git_to_capfile unless scm_plugin_installed?
103 elsif built_in_scm_name?
104 warn_set_scm_is_deprecated
105 elsif third_party_scm_name?
106 warn_third_party_scm_must_be_upgraded
107 end
108 end
109
110 def warn_set_scm_is_deprecated
111 $stderr.puts(<<-MESSAGE)
112 [Deprecation Notice] `set :scm, #{scm_name.inspect}` is deprecated.
113 To ensure your project is compatible with future versions of Capistrano,
114 remove the :scm setting and instead add these lines to your Capfile after
115 `require "capistrano/deploy"`:
116
117 require "capistrano/scm/#{scm_name}"
118 install_plugin #{built_in_scm_plugin_class_name}
119
120 MESSAGE
121 end
122
123 def warn_add_git_to_capfile
124 $stderr.puts(<<-MESSAGE)
125 [Deprecation Notice] Future versions of Capistrano will not load the Git SCM
126 plugin by default. To silence this deprecation warning, add the following to
127 your Capfile after `require "capistrano/deploy"`:
128
129 require "capistrano/scm/git"
130 install_plugin Capistrano::SCM::Git
131
132 MESSAGE
133 end
134
135 def warn_third_party_scm_must_be_upgraded
136 $stderr.puts(<<-MESSAGE)
137 [Deprecation Notice] `set :scm, #{scm_name.inspect}` is deprecated.
138 To ensure this custom SCM will work with future versions of Capistrano,
139 please upgrade it to a version that uses the new SCM plugin mechanism
140 documented here:
141
142 http://capistranorb.com/documentation/advanced-features/custom-scm
143
144 MESSAGE
145 end
146 end
147 end
148 end
0 require 'set'
0 require "set"
11 module Capistrano
22 class Configuration
33 class Server < SSHKit::Host
2424 end
2525
2626 def select?(options)
27 options.each do |k,v|
28 callable = v.respond_to?(:call) ? v: ->(server){server.fetch(v)}
29 result = case k
30 when :filter, :select
31 callable.call(self)
32 when :exclude
33 !callable.call(self)
34 else
35 self.fetch(k) == v
36 end
27 options.each do |k, v|
28 callable = v.respond_to?(:call) ? v : ->(server) { server.fetch(v) }
29 result = \
30 case k
31 when :filter, :select
32 callable.call(self)
33 when :exclude
34 !callable.call(self)
35 else
36 fetch(k) == v
37 end
3738 return false unless result
3839 end
39 return true
40
41 true
4042 end
4143
4244 def primary
5355 end
5456
5557 def netssh_options
56 @netssh_options ||= super.merge( fetch(:ssh_options) || {} )
58 @netssh_options ||= super.merge(fetch(:ssh_options) || {})
5759 end
5860
5961 def roles_array
6163 end
6264
6365 def matches?(other)
64 hostname == other.hostname
66 # This matching logic must stay in sync with `Servers#add_host`.
67 hostname == other.hostname && port == other.port
6568 end
6669
6770 private
7578 end
7679
7780 class Properties
78
7981 def initialize
8082 @properties = {}
8183 end
8284
8385 def set(key, value)
8486 pval = @properties[key]
85 if pval.is_a? Hash and value.is_a? Hash
87 if pval.is_a?(Hash) && value.is_a?(Hash)
8688 pval.merge!(value)
87 elsif pval.is_a? Set and value.is_a? Set
89 elsif pval.is_a?(Set) && value.is_a?(Set)
8890 pval.merge(value)
89 elsif pval.is_a? Array and value.is_a? Array
91 elsif pval.is_a?(Array) && value.is_a?(Array)
9092 pval.concat value
9193 else
9294 @properties[key] = value
9799 @properties[key]
98100 end
99101
100 def respond_to?(method, include_all=false)
101 @properties.has_key?(method)
102 def respond_to_missing?(method, _include_all=false)
103 @properties.key?(method) || super
102104 end
103105
104106 def roles
109111 @properties.keys
110112 end
111113
114 # rubocop:disable Style/MethodMissing
112115 def method_missing(key, value=nil)
113116 if value
114117 set(lvalue(key), value)
116119 fetch(key)
117120 end
118121 end
122 # rubocop:enable Style/MethodMissing
123
124 def to_h
125 @properties
126 end
119127
120128 private
121129
122130 def lvalue(key)
123 key.to_s.chomp('=').to_sym
131 key.to_s.chomp("=").to_sym
124132 end
125
126133 end
127
128134 end
129135 end
130136 end
0 require 'set'
1 require 'capistrano/configuration'
2 require 'capistrano/configuration/filter'
0 require "set"
1 require "capistrano/configuration"
2 require "capistrano/configuration/filter"
33
44 module Capistrano
55 class Configuration
88
99 def add_host(host, properties={})
1010 new_host = Server[host]
11 if server = servers.find { |s| s.matches? new_host }
12 server.user = new_host.user if new_host.user
13 server.port = new_host.port if new_host.port
14 server.with(properties)
11 new_host.port = properties[:port] if properties.key?(:port)
12 # This matching logic must stay in sync with `Server#matches?`.
13 key = ServerKey.new(new_host.hostname, new_host.port)
14 existing = servers_by_key[key]
15 if existing
16 existing.user = new_host.user if new_host.user
17 existing.with(properties)
1518 else
16 servers << new_host.with(properties)
19 servers_by_key[key] = new_host.with(properties)
1720 end
1821 end
1922
23 # rubocop:disable Security/MarshalLoad
2024 def add_role(role, hosts, options={})
2125 options_deepcopy = Marshal.dump(options.merge(roles: role))
2226 Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) }
2327 end
28 # rubocop:enable Security/MarshalLoad
2429
2530 def roles_for(names)
2631 options = extract_options(names)
27 s = Filter.new(:role, names).filter(servers)
32 s = Filter.new(:role, names).filter(servers_by_key.values)
2833 s.select { |server| server.select?(options) }
2934 end
3035
3742 if block_given?
3843 yield host, role, props
3944 else
40 rps << (props || {}).merge( role: role, hostname: host.hostname )
45 rps << (props || {}).merge(role: role, hostname: host.hostname)
4146 end
4247 end
4348 end
4449 end
45 block_given? ? nil: rps
50 block_given? ? nil : rps
4651 end
4752
4853 def fetch_primary(role)
5156 end
5257
5358 def each
54 servers.each { |server| yield server }
59 servers_by_key.values.each { |server| yield server }
5560 end
5661
5762 private
5863
59 def servers
60 @servers ||= []
64 ServerKey = Struct.new(:hostname, :port)
65
66 def servers_by_key
67 @servers_by_key ||= {}
6168 end
6269
6370 def extract_options(array)
0 require "capistrano/proc_helpers"
1 require "delegate"
2
3 module Capistrano
4 class Configuration
5 # Decorates a Variables object to additionally perform an optional set of
6 # user-supplied validation rules. Each rule for a given key is invoked
7 # immediately whenever `set` is called with a value for that key.
8 #
9 # If `set` is called with a callable value or a block, validation is not
10 # performed immediately. Instead, the validation rules are invoked the first
11 # time `fetch` is used to access the value.
12 #
13 # A rule is simply a block that accepts two arguments: key and value. It is
14 # up to the rule to raise an exception when it deems the value is invalid
15 # (or just print a warning).
16 #
17 # Rules can be registered using the DSL like this:
18 #
19 # validate(:my_key) do |key, value|
20 # # rule goes here
21 # end
22 #
23 class ValidatedVariables < SimpleDelegator
24 include Capistrano::ProcHelpers
25
26 def initialize(variables)
27 super(variables)
28 @validators = {}
29 end
30
31 # Decorate Variables#set to add validation behavior.
32 def set(key, value=nil, &block)
33 assert_value_or_block_not_both(value, block)
34
35 # Skip validation behavior if no validators are registered for this key
36 return super unless validators.key?(key)
37
38 value_to_evaluate = block || value
39
40 if callable_without_parameters?(value_to_evaluate)
41 super(key, assert_valid_later(key, value_to_evaluate), &nil)
42 else
43 assert_valid_now(key, value_to_evaluate)
44 super
45 end
46 end
47
48 # Register a validation rule for the given key.
49 def validate(key, &validator)
50 vs = (validators[key] || [])
51 vs << validator
52 validators[key] = vs
53 end
54
55 private
56
57 attr_reader :validators
58
59 # Given a callable that provides a value, wrap the callable with another
60 # object that responds to `call`. This new object will perform validation
61 # and then return the original callable's value.
62 #
63 # If the callable is a `Question`, the object returned by this method will
64 # also be a `Question` (a `ValidatedQuestion`, to be precise). This
65 # ensures that `is_a?(Question)` remains true even after the validation
66 # wrapper is applied. This is needed so that `Configuration#is_question?`
67 # works as expected.
68 #
69 def assert_valid_later(key, callable)
70 validation_callback = lambda do
71 value = callable.call
72 assert_valid_now(key, value)
73 value
74 end
75
76 if callable.is_a?(Question)
77 ValidatedQuestion.new(validation_callback)
78 else
79 validation_callback
80 end
81 end
82
83 # Runs all validation rules registered for the given key against the
84 # user-supplied value for that variable. If no validator raises an
85 # exception, the value is assumed to be valid.
86 def assert_valid_now(key, value)
87 validators[key].each do |validator|
88 validator.call(key, value)
89 end
90 end
91
92 def assert_value_or_block_not_both(value, block)
93 return if value.nil? || block.nil?
94 raise Capistrano::ValidationError,
95 "Value and block both passed to Configuration#set"
96 end
97
98 class ValidatedQuestion < Question
99 def initialize(validator)
100 @validator = validator
101 end
102
103 def call
104 @validator.call
105 end
106 end
107 end
108 end
109 end
0 require "capistrano/proc_helpers"
1
2 module Capistrano
3 class Configuration
4 # Holds the variables assigned at Capistrano runtime via `set` and retrieved
5 # with `fetch`. Does internal bookkeeping to help identify user mistakes
6 # like spelling errors or unused variables that may lead to unexpected
7 # behavior.
8 class Variables
9 CAPISTRANO_LOCATION = File.expand_path("../..", __FILE__).freeze
10 IGNORED_LOCATIONS = [
11 "#{CAPISTRANO_LOCATION}/configuration/variables.rb:",
12 "#{CAPISTRANO_LOCATION}/configuration.rb:",
13 "#{CAPISTRANO_LOCATION}/dsl/env.rb:",
14 "/dsl.rb:",
15 "/forwardable.rb:"
16 ].freeze
17 private_constant :CAPISTRANO_LOCATION, :IGNORED_LOCATIONS
18
19 include Capistrano::ProcHelpers
20
21 def initialize(values={})
22 @trusted_keys = []
23 @fetched_keys = []
24 @locations = {}
25 @values = values
26 @trusted = true
27 end
28
29 def untrusted!
30 @trusted = false
31 yield
32 ensure
33 @trusted = true
34 end
35
36 def set(key, value=nil, &block)
37 @trusted_keys << key if trusted? && !@trusted_keys.include?(key)
38 remember_location(key)
39 values[key] = block || value
40 trace_set(key)
41 values[key]
42 end
43
44 def fetch(key, default=nil, &block)
45 fetched_keys << key unless fetched_keys.include?(key)
46 peek(key, default, &block)
47 end
48
49 # Internal use only.
50 def peek(key, default=nil, &block)
51 value = fetch_for(key, default, &block)
52 while callable_without_parameters?(value)
53 value = (values[key] = value.call)
54 end
55 value
56 end
57
58 def fetch_for(key, default, &block)
59 block ? values.fetch(key, &block) : values.fetch(key, default)
60 end
61
62 def delete(key)
63 values.delete(key)
64 end
65
66 def trusted_keys
67 @trusted_keys.dup
68 end
69
70 def untrusted_keys
71 keys - @trusted_keys
72 end
73
74 def keys
75 values.keys
76 end
77
78 # Keys that have been set, but which have never been fetched.
79 def unused_keys
80 keys - fetched_keys
81 end
82
83 # Returns an array of source file location(s) where the given key was
84 # assigned (i.e. where `set` was called). If the key was never assigned,
85 # returns `nil`.
86 def source_locations(key)
87 locations[key]
88 end
89
90 private
91
92 attr_reader :locations, :values, :fetched_keys
93
94 def trusted?
95 @trusted
96 end
97
98 def remember_location(key)
99 location = caller.find do |line|
100 IGNORED_LOCATIONS.none? { |i| line.include?(i) }
101 end
102 (locations[key] ||= []) << location
103 end
104
105 def trace_set(key)
106 return unless fetch(:print_config_variables, false)
107 puts "Config variable set: #{key.inspect} => #{values[key].inspect}"
108 end
109 end
110 end
111 end
0 require_relative 'configuration/filter'
1 require_relative 'configuration/question'
2 require_relative 'configuration/server'
3 require_relative 'configuration/servers'
0 require_relative "configuration/filter"
1 require_relative "configuration/question"
2 require_relative "configuration/plugin_installer"
3 require_relative "configuration/server"
4 require_relative "configuration/servers"
5 require_relative "configuration/validated_variables"
6 require_relative "configuration/variables"
47
58 module Capistrano
9 class ValidationError < RuntimeError; end
10
611 class Configuration
7
8 def initialize(config = nil)
9 @config ||= config
10 end
11
1212 def self.env
1313 @env ||= new
1414 end
1717 @env = new
1818 end
1919
20 extend Forwardable
21 attr_reader :variables
22 def_delegators :variables,
23 :set, :fetch, :fetch_for, :delete, :keys, :validate
24
25 def initialize(values={})
26 @variables = ValidatedVariables.new(Variables.new(values))
27 end
28
2029 def ask(key, default=nil, options={})
2130 question = Question.new(key, default, options)
2231 set(key, question)
2332 end
2433
25 def set(key, value)
26 config[key] = value
34 def set_if_empty(key, value=nil, &block)
35 set(key, value, &block) unless keys.include?(key)
2736 end
2837
29 def set_if_empty(key, value)
30 config[key] = value unless config.has_key? key
38 def append(key, *values)
39 set(key, Array(fetch(key)).concat(values))
3140 end
3241
33 def delete(key)
34 config.delete(key)
42 def remove(key, *values)
43 set(key, Array(fetch(key)) - values)
3544 end
3645
37 def fetch(key, default=nil, &block)
38 value = fetch_for(key, default, &block)
39 while callable_without_parameters?(value)
40 value = set(key, value.call)
46 def any?(key)
47 value = fetch(key)
48 if value && value.respond_to?(:any?)
49 value.any?
50 else
51 !fetch(key).nil?
4152 end
42 return value
4353 end
4454
45 def keys
46 config.keys
55 def is_question?(key)
56 value = fetch_for(key, nil)
57 !value.nil? && value.is_a?(Question)
4758 end
4859
4960 def role(name, hosts, options={})
5061 if name == :all
51 raise ArgumentError.new("#{name} reserved name for role. Please choose another name")
62 raise ArgumentError, "#{name} reserved name for role. Please choose another name"
5263 end
5364
5465 servers.add_role(name, hosts, options)
7889
7990 def configure_backend
8091 backend.configure do |sshkit|
81 sshkit.format = fetch(:format)
92 configure_sshkit_output(sshkit)
8293 sshkit.output_verbosity = fetch(:log_level)
8394 sshkit.default_env = fetch(:default_env)
8495 sshkit.backend = fetch(:sshkit_backend, SSHKit::Backend::Netssh)
8596 sshkit.backend.configure do |backend|
8697 backend.pty = fetch(:pty)
8798 backend.connection_timeout = fetch(:connection_timeout)
88 backend.ssh_options = (backend.ssh_options || {}).merge(fetch(:ssh_options,{}))
99 backend.ssh_options = (backend.ssh_options || {}).merge(fetch(:ssh_options, {}))
89100 end
90101 end
102 end
103
104 def configure_scm
105 Capistrano::Configuration::SCMResolver.new.resolve
91106 end
92107
93108 def timestamp
94109 @timestamp ||= Time.now.utc
95110 end
96111
112 def add_filter(filter=nil, &block)
113 if block
114 raise ArgumentError, "Both a block and an object were given" if filter
115
116 filter = Object.new
117 def filter.filter(servers)
118 block.call(servers)
119 end
120 elsif !filter.respond_to? :filter
121 raise TypeError, "Provided custom filter <#{filter.inspect}> does " \
122 "not have a public 'filter' method"
123 end
124 @custom_filters ||= []
125 @custom_filters << filter
126 end
127
97128 def setup_filters
98 @filters = cmdline_filters.clone
99 @filters << Filter.new(:role, ENV['ROLES']) if ENV['ROLES']
100 @filters << Filter.new(:host, ENV['HOSTS']) if ENV['HOSTS']
101 fh = fetch_for(:filter,{})
129 @filters = cmdline_filters
130 @filters += @custom_filters if @custom_filters
131 @filters << Filter.new(:role, ENV["ROLES"]) if ENV["ROLES"]
132 @filters << Filter.new(:host, ENV["HOSTS"]) if ENV["HOSTS"]
133 fh = fetch_for(:filter, {}) || {}
134 @filters << Filter.new(:host, fh[:hosts]) if fh[:hosts]
135 @filters << Filter.new(:role, fh[:roles]) if fh[:roles]
102136 @filters << Filter.new(:host, fh[:host]) if fh[:host]
103137 @filters << Filter.new(:role, fh[:role]) if fh[:role]
104138 end
107141 cmdline_filters << Filter.new(type, values)
108142 end
109143
110 def filter list
144 def filter(list)
111145 setup_filters if @filters.nil?
112 @filters.reduce(list) { |l,f| f.filter l }
146 @filters.reduce(list) { |l, f| f.filter l }
147 end
148
149 def dry_run?
150 fetch(:sshkit_backend) == SSHKit::Backend::Printer
151 end
152
153 def install_plugin(plugin, load_hooks: true, load_immediately: false)
154 installer.install(plugin,
155 load_hooks: load_hooks,
156 load_immediately: load_immediately)
157 end
158
159 def scm_plugin_installed?
160 installer.scm_installed?
161 end
162
163 def servers
164 @servers ||= Servers.new
113165 end
114166
115167 private
118170 @cmdline_filters ||= []
119171 end
120172
121 def servers
122 @servers ||= Servers.new
173 def installer
174 @installer ||= PluginInstaller.new
123175 end
124176
125 def config
126 @config ||= Hash.new
127 end
177 def configure_sshkit_output(sshkit)
178 format_args = [fetch(:format)]
179 format_args.push(fetch(:format_options)) if any?(:format_options)
128180
129 def fetch_for(key, default, &block)
130 if block_given?
131 config.fetch(key, &block)
132 else
133 config.fetch(key, default)
134 end
135 end
136
137 def callable_without_parameters?(x)
138 x.respond_to?(:call) && ( !x.respond_to?(:arity) || x.arity == 0)
181 sshkit.use_format(*format_args)
139182 end
140183 end
141184 end
0 set_if_empty :scm, :git
1 set_if_empty :branch, :master
0 validate :application do |_key, value|
1 changed_value = value.gsub(/[^A-Z0-9\.\-]/i, "_")
2 if value != changed_value
3 warn %Q(The :application value "#{value}" is invalid!)
4 warn "Use only letters, numbers, hyphens, dots, and underscores. For example:"
5 warn " set :application, '#{changed_value}'"
6 raise Capistrano::ValidationError
7 end
8 end
9
10 %i(git_strategy hg_strategy svn_strategy).each do |strategy|
11 validate(strategy) do |key, _value|
12 warn(
13 "[Deprecation Warning] #{key} is deprecated and will be removed in "\
14 "Capistrano 3.7.0.\n"\
15 "https://github.com/capistrano/capistrano/blob/master/UPGRADING-3.7.md"
16 )
17 end
18 end
19
20 # We use a special :_default_git value so that SCMResolver can tell whether the
21 # default has been replaced by the user via `set`.
22 set_if_empty :scm, Capistrano::Configuration::SCMResolver::DEFAULT_GIT
23 set_if_empty :branch, "master"
224 set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" }
325 set_if_empty :tmp_dir, "/tmp"
426
527 set_if_empty :default_env, {}
628 set_if_empty :keep_releases, 5
729
8 set_if_empty :format, :pretty
30 set_if_empty :format, :airbrussh
931 set_if_empty :log_level, :debug
1032
1133 set_if_empty :pty, false
1234
13 set_if_empty :local_user, -> { Etc.getlogin }
35 set_if_empty :local_user, -> { ENV["USER"] || ENV["LOGNAME"] || ENV["USERNAME"] }
0 require 'capistrano/framework'
0 require "capistrano/framework"
11
22 load File.expand_path("../tasks/deploy.rake", __FILE__)
0 require "capistrano/doctor/output_helpers"
1
2 module Capistrano
3 module Doctor
4 class EnvironmentDoctor
5 include Capistrano::Doctor::OutputHelpers
6
7 def call
8 title("Environment")
9 puts <<-OUT.gsub(/^\s+/, "")
10 Ruby #{RUBY_DESCRIPTION}
11 Rubygems #{Gem::VERSION}
12 Bundler #{defined?(Bundler::VERSION) ? Bundler::VERSION : 'N/A'}
13 Command #{$PROGRAM_NAME} #{ARGV.join(' ')}
14 OUT
15 end
16 end
17 end
18 end
0 require "capistrano/doctor/output_helpers"
1
2 module Capistrano
3 module Doctor
4 # Prints table of all Capistrano-related gems and their version numbers. If
5 # there is a newer version of a gem available, call attention to it.
6 class GemsDoctor
7 include Capistrano::Doctor::OutputHelpers
8
9 def call
10 title("Gems")
11 table(all_gem_names) do |gem, row|
12 row.yellow if update_available?(gem)
13 row << gem
14 row << installed_gem_version(gem)
15 row << "(update available)" if update_available?(gem)
16 end
17 end
18
19 private
20
21 def installed_gem_version(gem_name)
22 Gem.loaded_specs[gem_name].version
23 end
24
25 def update_available?(gem_name)
26 latest = Gem.latest_version_for(gem_name)
27 return false if latest.nil?
28 latest > installed_gem_version(gem_name)
29 end
30
31 def all_gem_names
32 core_gem_names + plugin_gem_names
33 end
34
35 def core_gem_names
36 %w(capistrano airbrussh rake sshkit net-ssh) & Gem.loaded_specs.keys
37 end
38
39 def plugin_gem_names
40 (Gem.loaded_specs.keys - ["capistrano"]).grep(/capistrano/).sort
41 end
42 end
43 end
44 end
0 module Capistrano
1 module Doctor
2 # Helper methods for pretty-printing doctor output to stdout. All output
3 # (other than `title`) is indented by four spaces to facilitate copying and
4 # pasting this output into e.g. GitHub or Stack Overflow to achieve code
5 # formatting.
6 module OutputHelpers
7 class Row
8 attr_reader :color
9 attr_reader :values
10
11 def initialize
12 @values = []
13 end
14
15 def <<(value)
16 values << value
17 end
18
19 def yellow
20 @color = :yellow
21 end
22 end
23
24 # Prints a table for a given array of records. For each record, the block
25 # is yielded two arguments: the record and a Row object. To print values
26 # for that record, add values using `row << "some value"`. A row can
27 # optionally be highlighted in yellow using `row.yellow`.
28 def table(records, &block)
29 return if records.empty?
30 rows = collect_rows(records, &block)
31 col_widths = calculate_column_widths(rows)
32
33 rows.each do |row|
34 line = row.values.each_with_index.map do |value, col|
35 value.to_s.ljust(col_widths[col])
36 end.join(" ").rstrip
37 line = color.colorize(line, row.color) if row.color
38 puts line
39 end
40 end
41
42 # Prints a title in blue with surrounding newlines.
43 def title(text)
44 # Use $stdout directly to bypass the indentation that our `puts` does.
45 $stdout.puts(color.colorize("\n#{text}\n", :blue))
46 end
47
48 # Prints text in yellow.
49 def warning(text)
50 puts color.colorize(text, :yellow)
51 end
52
53 # Override `Kernel#puts` to prepend four spaces to each line.
54 def puts(string=nil)
55 $stdout.puts(string.to_s.gsub(/^/, " "))
56 end
57
58 private
59
60 def collect_rows(records)
61 records.map do |rec|
62 Row.new.tap { |row| yield(rec, row) }
63 end
64 end
65
66 def calculate_column_widths(rows)
67 num_columns = rows.map { |row| row.values.length }.max
68 Array.new(num_columns) do |col|
69 rows.map { |row| row.values[col].to_s.length }.max
70 end
71 end
72
73 def color
74 @color ||= SSHKit::Color.new($stdout)
75 end
76 end
77 end
78 end
0 require "capistrano/doctor/output_helpers"
1
2 module Capistrano
3 module Doctor
4 class ServersDoctor
5 include Capistrano::Doctor::OutputHelpers
6
7 def initialize(env=Capistrano::Configuration.env)
8 @servers = env.servers.to_a
9 end
10
11 def call
12 title("Servers (#{servers.size})")
13 rwc = RoleWhitespaceChecker.new(servers)
14
15 table(servers) do |server, row|
16 sd = ServerDecorator.new(server)
17
18 row << sd.uri_form
19 row << sd.roles
20 row << sd.properties
21 row.yellow if rwc.any_has_whitespace?(server.roles)
22 end
23
24 if rwc.whitespace_roles.any?
25 warning "\nWhitespace detected in role(s) #{rwc.whitespace_roles_decorated}. " \
26 "This might be a result of a mistyped \"%w()\" array literal."
27 end
28 puts
29 end
30
31 private
32
33 attr_reader :servers
34
35 class RoleWhitespaceChecker
36 attr_reader :whitespace_roles, :servers
37
38 def initialize(servers)
39 @servers = servers
40 @whitespace_roles = find_whitespace_roles
41 end
42
43 def any_has_whitespace?(roles)
44 roles.any? { |role| include_whitespace?(role) }
45 end
46
47 def include_whitespace?(role)
48 role =~ /\s/
49 end
50
51 def whitespace_roles_decorated
52 whitespace_roles.map(&:inspect).join(", ")
53 end
54
55 private
56
57 def find_whitespace_roles
58 servers.map(&:roles).flat_map(&:to_a).uniq
59 .select { |role| include_whitespace?(role) }
60 end
61 end
62
63 class ServerDecorator
64 def initialize(server)
65 @server = server
66 end
67
68 def uri_form
69 [
70 server.user,
71 server.user && "@",
72 server.hostname,
73 server.port && ":",
74 server.port
75 ].compact.join
76 end
77
78 def roles
79 server.roles.to_a.inspect
80 end
81
82 def properties
83 return "" unless server.properties.keys.any?
84 pretty_inspect(server.properties.to_h)
85 end
86
87 private
88
89 attr_reader :server
90
91 # Hashes with proper padding
92 def pretty_inspect(element)
93 return element.inspect unless element.is_a?(Hash)
94
95 pairs_string = element.keys.map do |key|
96 [pretty_inspect(key), pretty_inspect(element.fetch(key))].join(" => ")
97 end.join(", ")
98
99 "{ #{pairs_string} }"
100 end
101 end
102 end
103 end
104 end
0 require "capistrano/doctor/output_helpers"
1
2 module Capistrano
3 module Doctor
4 # Prints a table of all Capistrano variables and their current values. If
5 # there are unrecognized variables, print warnings for them.
6 class VariablesDoctor
7 # These are keys that are recognized by Capistrano, but do not have values
8 # set by default.
9 WHITELIST = %i(
10 application
11 current_directory
12 releases_directory
13 repo_url
14 repo_tree
15 shared_directory
16 ).freeze
17 private_constant :WHITELIST
18
19 include Capistrano::Doctor::OutputHelpers
20
21 def initialize(env=Capistrano::Configuration.env)
22 @env = env
23 end
24
25 def call
26 title("Variables")
27 values = inspect_all_values
28
29 table(variables.keys.sort_by(&:to_s)) do |key, row|
30 row.yellow if suspicious_keys.include?(key)
31 row << key.inspect
32 row << values[key]
33 end
34
35 puts if suspicious_keys.any?
36
37 suspicious_keys.sort_by(&:to_s).each do |key|
38 warning("#{key.inspect} is not a recognized Capistrano setting "\
39 "(#{location(key)})")
40 end
41 end
42
43 private
44
45 attr_reader :env
46
47 def variables
48 env.variables
49 end
50
51 def inspect_all_values
52 variables.keys.each_with_object({}) do |key, inspected|
53 inspected[key] = if env.is_question?(key)
54 "<ask>"
55 else
56 variables.peek(key).inspect
57 end
58 end
59 end
60
61 def suspicious_keys
62 (variables.untrusted_keys & variables.unused_keys) - WHITELIST
63 end
64
65 def location(key)
66 loc = variables.source_locations(key).first
67 loc && loc.sub(/^#{Regexp.quote(Dir.pwd)}/, "").sub(/:in.*/, "")
68 end
69 end
70 end
71 end
0 require "capistrano/doctor/environment_doctor"
1 require "capistrano/doctor/gems_doctor"
2 require "capistrano/doctor/variables_doctor"
3 require "capistrano/doctor/servers_doctor"
4
5 load File.expand_path("../tasks/doctor.rake", __FILE__)
0 dotfile = Pathname.new(File.join(Dir.home, '.capfile'))
0 dotfile = Pathname.new(File.join(Dir.home, ".capfile"))
11 load dotfile if dotfile.file?
2
0 require "forwardable"
1
02 module Capistrano
13 module DSL
24 module Env
3
4 def configure_backend
5 env.configure_backend
6 end
7
8 def fetch(key, default=nil, &block)
9 env.fetch(key, default, &block)
10 end
11
12 def any?(key)
13 value = fetch(key)
14 if value && value.respond_to?(:any?)
15 value.any?
16 else
17 !fetch(key).nil?
18 end
19 end
20
21 def set(key, value)
22 env.set(key, value)
23 end
24
25 def set_if_empty(key, value)
26 env.set_if_empty(key, value)
27 end
28
29 def delete(key)
30 env.delete(key)
31 end
32
33 def ask(key, value, options={})
34 env.ask(key, value, options)
35 end
36
37 def role(name, servers, options={})
38 env.role(name, servers, options)
39 end
40
41 def server(name, properties={})
42 env.server(name, properties)
43 end
5 extend Forwardable
6 def_delegators :env,
7 :configure_backend, :fetch, :set, :set_if_empty, :delete,
8 :ask, :role, :server, :primary, :validate, :append,
9 :remove, :dry_run?, :install_plugin, :any?, :is_question?,
10 :configure_scm, :scm_plugin_installed?
4411
4512 def roles(*names)
4613 env.roles_for(names.flatten)
5219
5320 def release_roles(*names)
5421 if names.last.is_a? Hash
55 names.last.merge!({ :exclude => :no_release })
22 names.last[:exclude] = :no_release
5623 else
5724 names << { exclude: :no_release }
5825 end
5926 roles(*names)
60 end
61
62 def primary(role)
63 env.primary(role)
6427 end
6528
6629 def env
7437 def asset_timestamp
7538 env.timestamp.strftime("%Y%m%d%H%M.%S")
7639 end
77
7840 end
7941 end
8042 end
0 require 'pathname'
0 require "pathname"
11 module Capistrano
22 module DSL
33 module Paths
4
54 def deploy_to
65 fetch(:deploy_to)
76 end
1110 end
1211
1312 def current_path
14 deploy_path.join('current')
13 deploy_path.join(fetch(:current_directory, "current"))
1514 end
1615
1716 def releases_path
18 deploy_path.join('releases')
17 deploy_path.join(fetch(:releases_directory, "releases"))
1918 end
2019
2120 def release_path
22 fetch(:release_path, current_path)
21 fetch(:release_path) { current_path }
2322 end
2423
2524 def set_release_path(timestamp=now)
2827 end
2928
3029 def stage_config_path
31 Pathname.new fetch(:stage_config_path, 'config/deploy')
30 Pathname.new fetch(:stage_config_path, "config/deploy")
3231 end
3332
3433 def deploy_config_path
35 Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
34 Pathname.new fetch(:deploy_config_path, "config/deploy.rb")
3635 end
3736
3837 def repo_url
39 require 'cgi'
40 require 'uri'
41 if fetch(:git_http_username) and fetch(:git_http_password)
42 URI.parse(fetch(:repo_url)).tap do |repo_uri|
43 repo_uri.user = fetch(:git_http_username)
44 repo_uri.password = CGI.escape(fetch(:git_http_password))
45 end.to_s
46 elsif fetch(:git_http_username)
47 URI.parse(fetch(:repo_url)).tap do |repo_uri|
48 repo_uri.user = fetch(:git_http_username)
49 end.to_s
50 else
51 fetch(:repo_url)
52 end
38 fetch(:repo_url)
5339 end
5440
5541 def repo_path
56 Pathname.new(fetch(:repo_path, ->(){deploy_path.join('repo')}))
42 Pathname.new(fetch(:repo_path, ->() { deploy_path.join("repo") }))
5743 end
5844
5945 def shared_path
60 deploy_path.join('shared')
46 deploy_path.join(fetch(:shared_directory, "shared"))
6147 end
6248
6349 def revision_log
64 deploy_path.join('revisions.log')
50 deploy_path.join("revisions.log")
6551 end
6652
6753 def now
9581 end
9682
9783 def map_dirnames(paths)
98 paths.map { |path| path.dirname }
84 paths.map(&:dirname).uniq
9985 end
10086 end
10187 end
00 module Capistrano
11 module DSL
22 module Stages
3 RESERVED_NAMES = %w(deploy doctor install).freeze
4 private_constant :RESERVED_NAMES
35
46 def stages
5 Dir[stage_definitions].map { |f| File.basename(f, '.rb') }
7 names = Dir[stage_definitions].map { |f| File.basename(f, ".rb") }
8 assert_valid_stage_names(names)
9 names
610 end
711
812 def stage_definitions
9 stage_config_path.join('*.rb')
13 stage_config_path.join("*.rb")
1014 end
1115
1216 def stage_set?
1317 !!fetch(:stage, false)
1418 end
1519
20 private
21
22 def assert_valid_stage_names(names)
23 invalid = names.find { |n| RESERVED_NAMES.include?(n) }
24 return if invalid.nil?
25
26 raise t("error.invalid_stage_name", name: invalid, path: stage_config_path.join("#{invalid}.rb"))
27 end
1628 end
1729 end
1830 end
0 require 'capistrano/upload_task'
0 require "capistrano/upload_task"
11
22 module Capistrano
33 module TaskEnhancements
88
99 def after(task, post_task, *args, &block)
1010 Rake::Task.define_task(post_task, *args, &block) if block_given?
11 post_task = Rake::Task[post_task]
12 Rake::Task[task].enhance do
13 post_task.invoke
11 task = Rake::Task[task]
12 task.enhance do
13 post = Rake.application.lookup(post_task, task.scope)
14 raise ArgumentError, "Task #{post_task.inspect} not found" unless post
15 post.invoke
1416 end
15 end
16
17 def remote_file(task)
18 target_roles = task.delete(:roles) { :all }
19 define_remote_file_task(task, target_roles)
2017 end
2118
2219 def define_remote_file_task(task, target_roles)
3027 upload! File.open(prerequisite_file), file
3128 end
3229 end
33
3430 end
3531 end
3632
5349
5450 def exit_deploy_because_of_exception(ex)
5551 warn t(:deploy_failed, ex: ex.message)
56 invoke 'deploy:failed'
52 invoke "deploy:failed"
5753 exit(false)
5854 end
5955
6056 def deploying?
6157 fetch(:deploying, false)
6258 end
63
6459 end
6560 end
0 require 'etc'
1 require 'capistrano/dsl/task_enhancements'
2 require 'capistrano/dsl/paths'
3 require 'capistrano/dsl/stages'
4 require 'capistrano/dsl/env'
5 require 'capistrano/configuration/filter'
0 require "capistrano/dsl/task_enhancements"
1 require "capistrano/dsl/paths"
2 require "capistrano/dsl/stages"
3 require "capistrano/dsl/env"
4 require "capistrano/configuration/filter"
65
76 module Capistrano
87 module DSL
1110 include Paths
1211 include Stages
1312
14 def invoke(task, *args)
15 Rake::Task[task].invoke(*args)
13 def invoke(task_name, *args)
14 task = Rake::Task[task_name]
15 # NOTE: We access instance variable since the accessor was only added recently. Once Capistrano depends on rake 11+, we can revert the following line
16 if task && task.instance_variable_get(:@already_invoked)
17 file, line, = caller.first.split(":")
18 colors = SSHKit::Color.new($stderr)
19 $stderr.puts colors.colorize("Skipping task `#{task_name}'.", :yellow)
20 $stderr.puts "Capistrano tasks may only be invoked once. Since task `#{task}' was previously invoked, invoke(\"#{task_name}\") at #{file}:#{line} will be skipped."
21 $stderr.puts "If you really meant to run this task again, use invoke!(\"#{task_name}\")"
22 $stderr.puts colors.colorize("THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.", :red)
23 $stderr.puts colors.colorize("https://github.com/capistrano/capistrano/issues/1686", :red)
24 end
25 task.invoke(*args)
26 end
27
28 def invoke!(task_name, *args)
29 task = Rake::Task[task_name]
30 task.reenable
31 task.invoke(*args)
1632 end
1733
1834 def t(key, options={})
2945
3046 def revision_log_message
3147 fetch(:revision_log_message,
32 t(:revision_log_message,
33 branch: fetch(:branch),
34 user: local_user,
35 sha: fetch(:current_revision),
36 release: fetch(:release_timestamp))
37 )
48 t(:revision_log_message,
49 branch: fetch(:branch),
50 user: local_user,
51 sha: fetch(:current_revision),
52 release: fetch(:release_timestamp)))
3853 end
3954
4055 def rollback_log_message
4964 VersionValidator.new(locked_version).verify
5065 end
5166
67 # rubocop:disable Security/MarshalLoad
5268 def on(hosts, options={}, &block)
5369 subset_copy = Marshal.dump(Configuration.env.filter(hosts))
5470 SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
5571 end
72 # rubocop:enable Security/MarshalLoad
5673
5774 def run_locally(&block)
5875 SSHKit::Backend::Local.new(&block).run
5976 end
6077
78 # Catch common beginner mistake and give a helpful error message on stderr
79 def execute(*)
80 file, line, = caller.first.split(":")
81 colors = SSHKit::Color.new($stderr)
82 $stderr.puts colors.colorize("Warning: `execute' should be wrapped in an `on' scope in #{file}:#{line}.", :red)
83 $stderr.puts
84 $stderr.puts " task :example do"
85 $stderr.puts colors.colorize(" on roles(:app) do", :yellow)
86 $stderr.puts " execute 'whoami'"
87 $stderr.puts colors.colorize(" end", :yellow)
88 $stderr.puts " end"
89 $stderr.puts
90 raise NoMethodError, "undefined method `execute' for main:Object"
91 end
6192 end
6293 end
63 self.extend Capistrano::DSL
94 extend Capistrano::DSL
00 load File.expand_path("../tasks/framework.rake", __FILE__)
1 require 'capistrano/install'
1 require "capistrano/install"
+0
-46
lib/capistrano/git.rb less more
0 load File.expand_path("../tasks/git.rake", __FILE__)
1
2 require 'capistrano/scm'
3
4 class Capistrano::Git < Capistrano::SCM
5
6 # execute git with argument in the context
7 #
8 def git(*args)
9 args.unshift :git
10 context.execute *args
11 end
12
13 # The Capistrano default strategy for git. You should want to use this.
14 module DefaultStrategy
15 def test
16 test! " [ -f #{repo_path}/HEAD ] "
17 end
18
19 def check
20 git :'ls-remote --heads', repo_url
21 end
22
23 def clone
24 git :clone, '--mirror', repo_url, repo_path
25 end
26
27 def update
28 git :remote, :update
29 end
30
31 def release
32 if tree = fetch(:repo_tree)
33 tree = tree.slice %r#^/?(.*?)/?$#, 1
34 components = tree.split('/').size
35 git :archive, fetch(:branch), tree, "| tar -x --strip-components #{components} -f - -C", release_path
36 else
37 git :archive, fetch(:branch), '| tar -x -f - -C', release_path
38 end
39 end
40
41 def fetch_revision
42 context.capture(:git, "rev-list --max-count=1 --abbrev-commit #{fetch(:branch)}")
43 end
44 end
45 end
+0
-43
lib/capistrano/hg.rb less more
0 load File.expand_path("../tasks/hg.rake", __FILE__)
1
2 require 'capistrano/scm'
3
4 class Capistrano::Hg < Capistrano::SCM
5 # execute hg in context with arguments
6 def hg(*args)
7 args.unshift(:hg)
8 context.execute *args
9 end
10
11 module DefaultStrategy
12 def test
13 test! " [ -d #{repo_path}/.hg ] "
14 end
15
16 def check
17 hg "id", repo_url
18 end
19
20 def clone
21 hg "clone", "--noupdate", repo_url, repo_path
22 end
23
24 def update
25 hg "pull"
26 end
27
28 def release
29 if tree = fetch(:repo_tree)
30 tree = tree.slice %r#^/?(.*?)/?$#, 1
31 components = tree.split('/').size
32 hg "archive --type tgz -p . -I", tree, "--rev", fetch(:branch), "| tar -x --strip-components #{components} -f - -C", release_path
33 else
34 hg "archive", release_path, "--rev", fetch(:branch)
35 end
36 end
37
38 def fetch_revision
39 context.capture(:hg, "log --rev #{fetch(:branch)} --template \"{node}\n\"")
40 end
41 end
42 end
0 require 'i18n'
0 require "i18n"
11
22 en = {
3 starting: 'Starting',
4 capified: 'Capified',
5 start: 'Start',
6 update: 'Update',
7 finalize: 'Finalise',
8 finishing: 'Finishing',
9 finished: 'Finished',
10 stage_not_set: 'Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.',
11 written_file: 'create %{file}',
12 question: 'Please enter %{key} (%{default_value}): ',
13 keeping_releases: 'Keeping %{keep_releases} of %{releases} deployed releases on %{host}',
14 no_old_releases: 'No old releases (keeping newest %{keep_releases}) on %{host}',
15 linked_file_does_not_exist: 'linked file %{file} does not exist on %{host}',
16 cannot_rollback: 'There are no older releases to rollback to',
17 mirror_exists: 'The repository mirror is at %{at}',
18 revision_log_message: 'Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}',
19 rollback_log_message: '%{user} rolled back to release %{release}',
20 deploy_failed: 'The deploy has failed with an error: %{ex}',
3 starting: "Starting",
4 capified: "Capified",
5 start: "Start",
6 update: "Update",
7 finalize: "Finalise",
8 finishing: "Finishing",
9 finished: "Finished",
10 stage_not_set: "Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.",
11 written_file: "create %{file}",
12 question: "Please enter %{key}: ",
13 question_default: "Please enter %{key} (%{default_value}): ",
14 keeping_releases: "Keeping %{keep_releases} of %{releases} deployed releases on %{host}",
15 skip_cleanup: "Skipping cleanup of invalid releases on %{host}; unexpected foldername found (should be timestamp)",
16 wont_delete_current_release: "Current release was marked for being removed but it's going to be skipped on %{host}",
17 no_current_release: "There is no current release present on %{host}",
18 no_old_releases: "No old releases (keeping newest %{keep_releases}) on %{host}",
19 linked_file_does_not_exist: "linked file %{file} does not exist on %{host}",
20 cannot_rollback: "There are no older releases to rollback to",
21 cannot_found_rollback_release: "Cannot rollback because release %{release} does not exist",
22 mirror_exists: "The repository mirror is at %{at}",
23 revision_log_message: "Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}",
24 rollback_log_message: "%{user} rolled back to release %{release}",
25 deploy_failed: "The deploy has failed with an error: %{ex}",
2126 console: {
22 welcome: 'capistrano console - enter command to execute on %{stage}',
23 bye: 'bye'
27 welcome: "capistrano console - enter command to execute on %{stage}",
28 bye: "bye"
2429 },
2530 error: {
31 invalid_stage_name: '"%{name}" is a reserved word and cannot be used as a stage. Rename "%{path}" to something else.',
2632 user: {
27 does_not_exist: 'User %{user} does not exists',
28 cannot_switch: 'Cannot switch to user %{user}'
33 does_not_exist: "User %{user} does not exists",
34 cannot_switch: "Cannot switch to user %{user}"
2935 }
3036 }
3137 }
3238
33 I18n.backend.store_translations(:en, { capistrano: en })
39 I18n.backend.store_translations(:en, capistrano: en)
3440
3541 if I18n.respond_to?(:enforce_available_locales=)
3642 I18n.enforce_available_locales = true
0 module Capistrano
1 # This module extends a Rake::Task to freeze it to prevent it from being
2 # enhanced. This is used to prevent users from enhancing a task at the wrong
3 # point of Capistrano's boot process, which can happen if a Capistrano plugin
4 # is loaded in deploy.rb by mistake (instead of in the Capfile).
5 #
6 # Usage:
7 #
8 # task = Rake.application["load:defaults"]
9 # task.invoke
10 # task.extend(Capistrano::ImmutableTask) # prevent further modifications
11 #
12 module ImmutableTask
13 def self.extended(task)
14 task.freeze
15 end
16
17 def enhance(*args, &block)
18 $stderr.puts <<-MESSAGE
19 ERROR: #{name} has already been invoked and can no longer be modified.
20 Check that you haven't loaded a Capistrano plugin in deploy.rb or a stage
21 (e.g. deploy/production.rb) by mistake.
22 Plugins must be loaded in the Capfile to initialize properly.
23 MESSAGE
24
25 # This will raise a frozen object error
26 super(*args, &block)
27 end
28 end
29 end
0 load File.expand_path(File.join(File.dirname(__FILE__),'tasks/install.rake'))
0 load File.expand_path(File.join(File.dirname(__FILE__), "tasks/install.rake"))
0 require "capistrano/all"
1 require "rake/tasklib"
2
3 # IMPORTANT: The Capistrano::Plugin system is not yet considered a stable,
4 # public API, and is subject to change without notice. Eventually it will be
5 # officially documented and supported, but for now, use it at your own risk.
6 #
7 # Base class for Capistrano plugins. Makes building a Capistrano plugin as easy
8 # as writing a `Capistrano::Plugin` subclass and overriding any or all of its
9 # three template methods:
10 #
11 # * set_defaults
12 # * register_hooks
13 # * define_tasks
14 #
15 # Within the plugin you can use any methods of the Rake or Capistrano DSLs, like
16 # `fetch`, `invoke`, etc. In cases when you need to use SSHKit's backend outside
17 # of an `on` block, use the `backend` convenience method. E.g. `backend.test`,
18 # `backend.execute`, or `backend.capture`.
19 #
20 # Package up and distribute your plugin class as a gem and you're good to go!
21 #
22 # To use a plugin, all a user has to do is install it in the Capfile, like this:
23 #
24 # # Capfile
25 # require "capistrano/superfancy"
26 # install_plugin Capistrano::Superfancy
27 #
28 # Or, to install the plugin without its hooks:
29 #
30 # # Capfile
31 # require "capistrano/superfancy"
32 # install_plugin Capistrano::Superfancy, load_hooks: false
33 #
34 class Capistrano::Plugin < Rake::TaskLib
35 include Capistrano::DSL
36
37 # Implemented by subclasses to provide default values for settings needed by
38 # this plugin. Typically done using the `set_if_empty` Capistrano DSL method.
39 #
40 # Example:
41 #
42 # def set_defaults
43 # set_if_empty :my_plugin_option, true
44 # end
45 #
46 def set_defaults; end
47
48 # Implemented by subclasses to hook into Capistrano's deployment flow using
49 # using the `before` and `after` DSL methods. Note that `register_hooks` will
50 # not be called if the user has opted-out of hooks when installing the plugin.
51 #
52 # Example:
53 #
54 # def register_hooks
55 # after "deploy:updated", "my_plugin:do_something"
56 # end
57 #
58 def register_hooks; end
59
60 # Implemented by subclasses to define Rake tasks. Typically a plugin will call
61 # `eval_rakefile` to load Rake tasks from a separate .rake file.
62 #
63 # Example:
64 #
65 # def define_tasks
66 # eval_rakefile File.expand_path("../tasks.rake", __FILE__)
67 # end
68 #
69 # For simple tasks, you can define them inline. No need for a separate file.
70 #
71 # def define_tasks
72 # desc "Do something fantastic."
73 # task "my_plugin:fantastic" do
74 # ...
75 # end
76 # end
77 #
78 def define_tasks; end
79
80 private
81
82 # Read and eval a .rake file in such a way that `self` within the .rake file
83 # refers to this plugin instance. This gives the tasks in the file access to
84 # helper methods defined by the plugin.
85 def eval_rakefile(path)
86 contents = IO.read(path)
87 instance_eval(contents, path, 1)
88 end
89
90 # Convenience to access the current SSHKit backend outside of an `on` block.
91 def backend
92 SSHKit::Backend.current
93 end
94 end
0 module Capistrano
1 module ProcHelpers
2 module_function
3
4 # Tests whether the given object appears to respond to `call` with
5 # zero parameters. In Capistrano, such a proc is used to represent a
6 # "deferred value". That is, a value that is resolved by invoking `call` at
7 # the time it is first needed.
8 def callable_without_parameters?(x)
9 x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity.zero?)
10 end
11 end
12 end
0 require "capistrano/scm/plugin"
1 require "cgi"
2 require "shellwords"
3 require "uri"
4
5 class Capistrano::SCM::Git < Capistrano::SCM::Plugin
6 def set_defaults
7 set_if_empty :git_shallow_clone, false
8 set_if_empty :git_wrapper_path, lambda {
9 # Try to avoid permissions issues when multiple users deploy the same app
10 # by using different file names in the same dir for each deployer and stage.
11 suffix = %i(application stage local_user).map { |key| fetch(key).to_s }.join("-")
12 "#{fetch(:tmp_dir)}/git-ssh-#{suffix}.sh"
13 }
14 set_if_empty :git_environmental_variables, lambda {
15 {
16 git_askpass: "/bin/echo",
17 git_ssh: fetch(:git_wrapper_path)
18 }
19 }
20 end
21
22 def register_hooks
23 after "deploy:new_release_path", "git:create_release"
24 before "deploy:check", "git:check"
25 before "deploy:set_current_revision", "git:set_current_revision"
26 end
27
28 def define_tasks
29 eval_rakefile File.expand_path("../tasks/git.rake", __FILE__)
30 end
31
32 def repo_mirror_exists?
33 backend.test " [ -f #{repo_path}/HEAD ] "
34 end
35
36 def check_repo_is_reachable
37 git :'ls-remote', git_repo_url, "HEAD"
38 end
39
40 def clone_repo
41 if (depth = fetch(:git_shallow_clone))
42 git :clone, "--mirror", "--depth", depth, "--no-single-branch", git_repo_url, repo_path.to_s
43 else
44 git :clone, "--mirror", git_repo_url, repo_path.to_s
45 end
46 end
47
48 def update_mirror
49 # Update the origin URL if necessary.
50 git :remote, "set-url", "origin", git_repo_url
51
52 # Note: Requires git version 1.9 or greater
53 if (depth = fetch(:git_shallow_clone))
54 git :fetch, "--depth", depth, "origin", fetch(:branch)
55 else
56 git :remote, :update, "--prune"
57 end
58 end
59
60 def archive_to_release_path
61 if (tree = fetch(:repo_tree))
62 tree = tree.slice %r#^/?(.*?)/?$#, 1
63 components = tree.split("/").size
64 git :archive, fetch(:branch), tree, "| #{SSHKit.config.command_map[:tar]} -x --strip-components #{components} -f - -C", release_path
65 else
66 git :archive, fetch(:branch), "| #{SSHKit.config.command_map[:tar]} -x -f - -C", release_path
67 end
68 end
69
70 def fetch_revision
71 backend.capture(:git, "rev-list --max-count=1 #{fetch(:branch)}")
72 end
73
74 def git(*args)
75 args.unshift :git
76 backend.execute(*args)
77 end
78
79 def git_repo_url
80 if fetch(:git_http_username) && fetch(:git_http_password)
81 URI.parse(repo_url).tap do |repo_uri|
82 repo_uri.user = fetch(:git_http_username)
83 repo_uri.password = CGI.escape(fetch(:git_http_password))
84 end.to_s
85 elsif fetch(:git_http_username)
86 URI.parse(repo_url).tap do |repo_uri|
87 repo_uri.user = fetch(:git_http_username)
88 end.to_s
89 else
90 repo_url
91 end
92 end
93 end
0 require "capistrano/scm/plugin"
1 require "securerandom"
2
3 class Capistrano::SCM::Hg < Capistrano::SCM::Plugin
4 def register_hooks
5 after "deploy:new_release_path", "hg:create_release"
6 before "deploy:check", "hg:check"
7 before "deploy:set_current_revision", "hg:set_current_revision"
8 end
9
10 def define_tasks
11 eval_rakefile File.expand_path("../tasks/hg.rake", __FILE__)
12 end
13
14 def hg(*args)
15 args.unshift(:hg)
16 backend.execute(*args)
17 end
18
19 def repo_mirror_exists?
20 backend.test " [ -d #{repo_path}/.hg ] "
21 end
22
23 def check_repo_is_reachable
24 hg "id", repo_url
25 end
26
27 def clone_repo
28 hg "clone", "--noupdate", repo_url, repo_path.to_s
29 end
30
31 def update_mirror
32 hg "pull"
33 end
34
35 def archive_to_release_path
36 if (tree = fetch(:repo_tree))
37 tree = tree.slice %r#^/?(.*?)/?$#, 1
38 components = tree.split("/").size
39 temp_tar = "#{fetch(:tmp_dir)}/#{SecureRandom.hex(10)}.tar"
40
41 hg "archive -p . -I", tree, "--rev", fetch(:branch), temp_tar
42
43 backend.execute :mkdir, "-p", release_path
44 backend.execute :tar, "-x --strip-components #{components} -f", temp_tar, "-C", release_path
45 backend.execute :rm, temp_tar
46 else
47 hg "archive", release_path, "--rev", fetch(:branch)
48 end
49 end
50
51 def fetch_revision
52 backend.capture(:hg, "log --rev #{fetch(:branch)} --template \"{node}\n\"")
53 end
54 end
0 require "capistrano/plugin"
1 require "capistrano/scm"
2
3 # Base class for all built-in and third-party SCM plugins. Notice that this
4 # class doesn't really do anything other than provide an `scm?` predicate. This
5 # tells Capistrano that the plugin provides SCM functionality. All other plugin
6 # features are inherited from Capistrano::Plugin.
7 #
8 class Capistrano::SCM::Plugin < Capistrano::Plugin
9 def scm?
10 true
11 end
12 end
0 require "capistrano/scm/plugin"
1
2 class Capistrano::SCM::Svn < Capistrano::SCM::Plugin
3 def register_hooks
4 after "deploy:new_release_path", "svn:create_release"
5 before "deploy:check", "svn:check"
6 before "deploy:set_current_revision", "svn:set_current_revision"
7 end
8
9 def define_tasks
10 eval_rakefile File.expand_path("../tasks/svn.rake", __FILE__)
11 end
12
13 def svn(*args)
14 args.unshift(:svn)
15 args.push "--username #{fetch(:svn_username)}" if fetch(:svn_username)
16 args.push "--password #{fetch(:svn_password)}" if fetch(:svn_password)
17 args.push "--revision #{fetch(:svn_revision)}" if fetch(:svn_revision)
18 backend.execute(*args)
19 end
20
21 def repo_mirror_exists?
22 backend.test " [ -d #{repo_path}/.svn ] "
23 end
24
25 def check_repo_is_reachable
26 svn_username = fetch(:svn_username) ? "--username #{fetch(:svn_username)}" : ""
27 svn_password = fetch(:svn_password) ? "--password #{fetch(:svn_password)}" : ""
28 backend.test :svn, :info, repo_url, svn_username, svn_password
29 end
30
31 def clone_repo
32 svn :checkout, repo_url, repo_path.to_s
33 end
34
35 def update_mirror
36 # Switch the repository URL if necessary.
37 repo_mirror_url = fetch_repo_mirror_url
38 svn :switch, repo_url unless repo_mirror_url == repo_url
39 svn :update
40 end
41
42 def archive_to_release_path
43 svn :export, "--force", ".", release_path
44 end
45
46 def fetch_revision
47 backend.capture(:svnversion, repo_path.to_s)
48 end
49
50 def fetch_repo_mirror_url
51 backend.capture(:svn, :info, repo_path.to_s).each_line do |line|
52 return $1 if /\AURL: (.*)\n\z/ =~ line
53 end
54 end
55 end
0 # This trick lets us access the Git plugin within `on` blocks.
1 git_plugin = self
2
3 namespace :git do
4 desc "Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt"
5 task :wrapper do
6 on release_roles :all do
7 execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path)).shellescape
8 upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), fetch(:git_wrapper_path)
9 execute :chmod, "700", fetch(:git_wrapper_path).shellescape
10 end
11 end
12
13 desc "Check that the repository is reachable"
14 task check: :'git:wrapper' do
15 fetch(:branch)
16 on release_roles :all do
17 with fetch(:git_environmental_variables) do
18 git_plugin.check_repo_is_reachable
19 end
20 end
21 end
22
23 desc "Clone the repo to the cache"
24 task clone: :'git:wrapper' do
25 on release_roles :all do
26 if git_plugin.repo_mirror_exists?
27 info t(:mirror_exists, at: repo_path)
28 else
29 within deploy_path do
30 with fetch(:git_environmental_variables) do
31 git_plugin.clone_repo
32 end
33 end
34 end
35 end
36 end
37
38 desc "Update the repo mirror to reflect the origin state"
39 task update: :'git:clone' do
40 on release_roles :all do
41 within repo_path do
42 with fetch(:git_environmental_variables) do
43 git_plugin.update_mirror
44 end
45 end
46 end
47 end
48
49 desc "Copy repo to releases"
50 task create_release: :'git:update' do
51 on release_roles :all do
52 with fetch(:git_environmental_variables) do
53 within repo_path do
54 execute :mkdir, "-p", release_path
55 git_plugin.archive_to_release_path
56 end
57 end
58 end
59 end
60
61 desc "Determine the revision that will be deployed"
62 task :set_current_revision do
63 on release_roles :all do
64 within repo_path do
65 with fetch(:git_environmental_variables) do
66 set :current_revision, git_plugin.fetch_revision
67 end
68 end
69 end
70 end
71 end
0 # TODO: this is nearly identical to git.rake. DRY up?
1
2 # This trick lets us access the Hg plugin within `on` blocks.
3 hg_plugin = self
4
5 namespace :hg do
6 desc "Check that the repo is reachable"
7 task :check do
8 on release_roles :all do
9 hg_plugin.check_repo_is_reachable
10 end
11 end
12
13 desc "Clone the repo to the cache"
14 task :clone do
15 on release_roles :all do
16 if hg_plugin.repo_mirror_exists?
17 info t(:mirror_exists, at: repo_path)
18 else
19 within deploy_path do
20 hg_plugin.clone_repo
21 end
22 end
23 end
24 end
25
26 desc "Pull changes from the remote repo"
27 task update: :'hg:clone' do
28 on release_roles :all do
29 within repo_path do
30 hg_plugin.update_mirror
31 end
32 end
33 end
34
35 desc "Copy repo to releases"
36 task create_release: :'hg:update' do
37 on release_roles :all do
38 within repo_path do
39 hg_plugin.archive_to_release_path
40 end
41 end
42 end
43
44 desc "Determine the revision that will be deployed"
45 task :set_current_revision do
46 on release_roles :all do
47 within repo_path do
48 set :current_revision, hg_plugin.fetch_revision
49 end
50 end
51 end
52 end
0 # TODO: this is nearly identical to git.rake. DRY up?
1
2 # This trick lets us access the Svn plugin within `on` blocks.
3 svn_plugin = self
4
5 namespace :svn do
6 desc "Check that the repo is reachable"
7 task :check do
8 on release_roles :all do
9 svn_plugin.check_repo_is_reachable
10 end
11 end
12
13 desc "Clone the repo to the cache"
14 task :clone do
15 on release_roles :all do
16 if svn_plugin.repo_mirror_exists?
17 info t(:mirror_exists, at: repo_path)
18 else
19 within deploy_path do
20 svn_plugin.clone_repo
21 end
22 end
23 end
24 end
25
26 desc "Pull changes from the remote repo"
27 task update: :'svn:clone' do
28 on release_roles :all do
29 within repo_path do
30 svn_plugin.update_mirror
31 end
32 end
33 end
34
35 desc "Copy repo to releases"
36 task create_release: :'svn:update' do
37 on release_roles :all do
38 within repo_path do
39 svn_plugin.archive_to_release_path
40 end
41 end
42 end
43
44 desc "Determine the revision that will be deployed"
45 task :set_current_revision do
46 on release_roles :all do
47 within repo_path do
48 set :current_revision, svn_plugin.fetch_revision
49 end
50 end
51 end
52 end
00 module Capistrano
1
21 # Base class for SCM strategy providers.
32 #
43 # @abstract
2423
2524 # Call test in context
2625 def test!(*args)
27 context.test *args
26 context.test(*args)
2827 end
2928
3029 # The repository URL according to the context
5857 # @return [Boolean]
5958 #
6059 def test
61 raise NotImplementedError.new(
62 "Your SCM strategy module should provide a #test method"
63 )
60 raise NotImplementedError, "Your SCM strategy module should provide a #test method"
6461 end
6562
6663 # @abstract
7168 # @return [Boolean]
7269 #
7370 def check
74 raise NotImplementedError.new(
75 "Your SCM strategy module should provide a #check method"
76 )
71 raise NotImplementedError, "Your SCM strategy module should provide a #check method"
7772 end
7873
7974 # @abstract
8378 # @return void
8479 #
8580 def clone
86 raise NotImplementedError.new(
87 "Your SCM strategy module should provide a #clone method"
88 )
81 raise NotImplementedError, "Your SCM strategy module should provide a #clone method"
8982 end
9083
9184 # @abstract
9588 # @return void
9689 #
9790 def update
98 raise NotImplementedError.new(
99 "Your SCM strategy module should provide a #update method"
100 )
91 raise NotImplementedError, "Your SCM strategy module should provide a #update method"
10192 end
10293
10394 # @abstract
10798 # @return void
10899 #
109100 def release
110 raise NotImplementedError.new(
111 "Your SCM strategy module should provide a #release method"
112 )
101 raise NotImplementedError, "Your SCM strategy module should provide a #release method"
113102 end
114103
115104 # @abstract
119108 # @return void
120109 #
121110 def fetch_revision
122 raise NotImplementedError.new(
123 "Your SCM strategy module should provide a #fetch_revision method"
124 )
111 raise NotImplementedError, "Your SCM strategy module should provide a #fetch_revision method"
125112 end
126113 end
127114 end
0 require "capistrano/doctor"
1 require "capistrano/immutable_task"
02 include Capistrano::DSL
13
24 namespace :load do
35 task :defaults do
4 load 'capistrano/defaults.rb'
6 load "capistrano/defaults.rb"
57 end
8 end
9
10 require "airbrussh/capistrano"
11 # We don't need to show the "using Airbrussh" banner announcement since
12 # Airbrussh is now the built-in formatter. Also enable command output by
13 # default; hiding the output might be confusing to users new to Capistrano.
14 Airbrussh.configure do |airbrussh|
15 airbrussh.banner = false
16 airbrussh.command_output = true
617 end
718
819 stages.each do |stage|
920 Rake::Task.define_task(stage) do
1021 set(:stage, stage.to_sym)
1122
12 invoke 'load:defaults'
13 load deploy_config_path
14 load stage_config_path.join("#{stage}.rb")
15 load "capistrano/#{fetch(:scm)}.rb"
23 invoke "load:defaults"
24 Rake.application["load:defaults"].extend(Capistrano::ImmutableTask)
25 env.variables.untrusted! do
26 load deploy_config_path
27 load stage_config_path.join("#{stage}.rb")
28 end
29 configure_scm
1630 I18n.locale = fetch(:locale, :en)
1731 configure_backend
1832 end
1933 end
2034
21 require 'capistrano/dotfile'
35 require "capistrano/dotfile"
+0
-38
lib/capistrano/svn.rb less more
0 load File.expand_path("../tasks/svn.rake", __FILE__)
1
2 require 'capistrano/scm'
3
4 class Capistrano::Svn < Capistrano::SCM
5
6 # execute svn in context with arguments
7 def svn(*args)
8 args.unshift(:svn)
9 context.execute *args
10 end
11
12 module DefaultStrategy
13 def test
14 test! " [ -d #{repo_path}/.svn ] "
15 end
16
17 def check
18 test! :svn, :info, repo_url
19 end
20
21 def clone
22 svn :checkout, repo_url, repo_path
23 end
24
25 def update
26 svn :update
27 end
28
29 def release
30 svn :export, '--force', '.', release_path
31 end
32
33 def fetch_revision
34 context.capture(:svnversion, repo_path)
35 end
36 end
37 end
0 desc 'Execute remote commands'
0 desc "Execute remote commands"
11 task :console do
22 stage = fetch(:stage)
3 puts I18n.t('console.welcome', scope: :capistrano, stage: stage)
3 puts I18n.t("console.welcome", scope: :capistrano, stage: stage)
44 loop do
55 print "#{stage}> "
66
7 if input = $stdin.gets
8 command = input.chomp
9 else
10 command = 'exit'
11 end
7 command = (input = $stdin.gets) ? input.chomp : "exit"
128
139 next if command.empty?
1410
1511 if %w{quit exit q}.include? command
16 puts t('console.bye')
12 puts t("console.bye")
1713 break
1814 else
1915 begin
00 namespace :deploy do
1
21 task :starting do
3 invoke 'deploy:check'
4 invoke 'deploy:set_previous_revision'
5 end
6
7 task :updating => :new_release_path do
8 invoke "#{scm}:create_release"
2 invoke "deploy:print_config_variables" if fetch(:print_config_variables, false)
3 invoke "deploy:check"
4 invoke "deploy:set_previous_revision"
5 end
6
7 task :print_config_variables do
8 puts
9 puts "------- Printing current config variables -------"
10 env.keys.each do |config_variable_key|
11 if is_question?(config_variable_key)
12 puts "#{config_variable_key.inspect} => Question (awaits user input on next fetch(#{config_variable_key.inspect}))"
13 else
14 puts "#{config_variable_key.inspect} => #{fetch(config_variable_key).inspect}"
15 end
16 end
17
18 puts
19 puts "------- Printing current config variables of SSHKit mechanism -------"
20 puts env.backend.config.inspect
21 # puts env.backend.config.backend.config.ssh_options.inspect
22 # puts env.backend.config.command_map.defaults.inspect
23
24 puts
25 end
26
27 task updating: :new_release_path do
928 invoke "deploy:set_current_revision"
10 invoke 'deploy:symlink:shared'
29 invoke "deploy:symlink:shared"
1130 end
1231
1332 task :reverting do
14 invoke 'deploy:revert_release'
33 invoke "deploy:revert_release"
1534 end
1635
1736 task :publishing do
18 invoke 'deploy:symlink:release'
37 invoke "deploy:symlink:release"
1938 end
2039
2140 task :finishing do
22 invoke 'deploy:cleanup'
41 invoke "deploy:cleanup"
2342 end
2443
2544 task :finishing_rollback do
26 invoke 'deploy:cleanup_rollback'
45 invoke "deploy:cleanup_rollback"
2746 end
2847
2948 task :finished do
30 invoke 'deploy:log_revision'
31 end
32
33 desc 'Check required files and directories exist'
49 invoke "deploy:log_revision"
50 end
51
52 desc "Check required files and directories exist"
3453 task :check do
35 invoke "#{scm}:check"
36 invoke 'deploy:check:directories'
37 invoke 'deploy:check:linked_dirs'
38 invoke 'deploy:check:make_linked_dirs'
39 invoke 'deploy:check:linked_files'
54 invoke "deploy:check:directories"
55 invoke "deploy:check:linked_dirs"
56 invoke "deploy:check:make_linked_dirs"
57 invoke "deploy:check:linked_files"
4058 end
4159
4260 namespace :check do
43 desc 'Check shared and release directories exist'
61 desc "Check shared and release directories exist"
4462 task :directories do
4563 on release_roles :all do
46 execute :mkdir, '-p', shared_path, releases_path
47 end
48 end
49
50 desc 'Check directories to be linked exist in shared'
64 execute :mkdir, "-p", shared_path, releases_path
65 end
66 end
67
68 desc "Check directories to be linked exist in shared"
5169 task :linked_dirs do
5270 next unless any? :linked_dirs
5371 on release_roles :all do
54 execute :mkdir, '-p', linked_dirs(shared_path)
55 end
56 end
57
58 desc 'Check directories of files to be linked exist in shared'
72 execute :mkdir, "-p", linked_dirs(shared_path)
73 end
74 end
75
76 desc "Check directories of files to be linked exist in shared"
5977 task :make_linked_dirs do
6078 next unless any? :linked_files
61 on release_roles :all do |host|
62 execute :mkdir, '-p', linked_file_dirs(shared_path)
63 end
64 end
65
66 desc 'Check files to be linked exist in shared'
79 on release_roles :all do |_host|
80 execute :mkdir, "-p", linked_file_dirs(shared_path)
81 end
82 end
83
84 desc "Check files to be linked exist in shared"
6785 task :linked_files do
6886 next unless any? :linked_files
6987 on release_roles :all do |host|
7896 end
7997
8098 namespace :symlink do
81 desc 'Symlink release to current'
99 desc "Symlink release to current"
82100 task :release do
83101 on release_roles :all do
84102 tmp_current_path = release_path.parent.join(current_path.basename)
85 execute :ln, '-s', release_path, tmp_current_path
103 execute :ln, "-s", release_path, tmp_current_path
86104 execute :mv, tmp_current_path, current_path.parent
87105 end
88106 end
89107
90 desc 'Symlink files and directories from shared to release'
108 desc "Symlink files and directories from shared to release"
91109 task :shared do
92 invoke 'deploy:symlink:linked_files'
93 invoke 'deploy:symlink:linked_dirs'
94 end
95
96 desc 'Symlink linked directories'
110 invoke "deploy:symlink:linked_files"
111 invoke "deploy:symlink:linked_dirs"
112 end
113
114 desc "Symlink linked directories"
97115 task :linked_dirs do
98116 next unless any? :linked_dirs
99117 on release_roles :all do
100 execute :mkdir, '-p', linked_dir_parents(release_path)
118 execute :mkdir, "-p", linked_dir_parents(release_path)
101119
102120 fetch(:linked_dirs).each do |dir|
103121 target = release_path.join(dir)
104122 source = shared_path.join(dir)
105 unless test "[ -L #{target} ]"
106 if test "[ -d #{target} ]"
107 execute :rm, '-rf', target
108 end
109 execute :ln, '-s', source, target
110 end
111 end
112 end
113 end
114
115 desc 'Symlink linked files'
123 next if test "[ -L #{target} ]"
124 execute :rm, "-rf", target if test "[ -d #{target} ]"
125 execute :ln, "-s", source, target
126 end
127 end
128 end
129
130 desc "Symlink linked files"
116131 task :linked_files do
117132 next unless any? :linked_files
118133 on release_roles :all do
119 execute :mkdir, '-p', linked_file_dirs(release_path)
134 execute :mkdir, "-p", linked_file_dirs(release_path)
120135
121136 fetch(:linked_files).each do |file|
122137 target = release_path.join(file)
123138 source = shared_path.join(file)
124 unless test "[ -L #{target} ]"
125 if test "[ -f #{target} ]"
126 execute :rm, target
127 end
128 execute :ln, '-s', source, target
129 end
130 end
131 end
132 end
133 end
134
135 desc 'Clean up old releases'
139 next if test "[ -L #{target} ]"
140 execute :rm, target if test "[ -f #{target} ]"
141 execute :ln, "-s", source, target
142 end
143 end
144 end
145 end
146
147 desc "Clean up old releases"
136148 task :cleanup do
137149 on release_roles :all do |host|
138 releases = capture(:ls, '-xtr', releases_path).split
139 if releases.count >= fetch(:keep_releases)
140 info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count)
141 directories = (releases - releases.last(fetch(:keep_releases)))
150 releases = capture(:ls, "-x", releases_path).split
151 valid, invalid = releases.partition { |e| /^\d{14}$/ =~ e }
152
153 warn t(:skip_cleanup, host: host.to_s) if invalid.any?
154
155 if valid.count >= fetch(:keep_releases)
156 info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: valid.count)
157 directories = (valid - valid.last(fetch(:keep_releases))).map do |release|
158 releases_path.join(release).to_s
159 end
160 if test("[ -d #{current_path} ]")
161 current_release = capture(:readlink, current_path).to_s
162 if directories.include?(current_release)
163 warn t(:wont_delete_current_release, host: host.to_s)
164 directories.delete(current_release)
165 end
166 else
167 debug t(:no_current_release, host: host.to_s)
168 end
142169 if directories.any?
143 directories_str = directories.map do |release|
144 releases_path.join(release)
145 end.join(" ")
146 execute :rm, '-rf', directories_str
170 directories_str = directories.join(" ")
171 execute :rm, "-rf", directories_str
147172 else
148173 info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases))
149174 end
151176 end
152177 end
153178
154 desc 'Remove and archive rolled-back release.'
179 desc "Remove and archive rolled-back release."
155180 task :cleanup_rollback do
156181 on release_roles(:all) do
157 last_release = capture(:ls, '-xt', releases_path).split.first
182 last_release = capture(:ls, "-xt", releases_path).split.first
158183 last_release_path = releases_path.join(last_release)
159184 if test "[ `readlink #{current_path}` != #{last_release_path} ]"
160 execute :tar, '-czf',
161 deploy_path.join("rolled-back-release-#{last_release}.tar.gz"),
162 last_release_path
163 execute :rm, '-rf', last_release_path
185 execute :tar, "-czf",
186 deploy_path.join("rolled-back-release-#{last_release}.tar.gz"),
187 last_release_path
188 execute :rm, "-rf", last_release_path
164189 else
165 debug 'Last release is the current release, skip cleanup_rollback.'
166 end
167 end
168 end
169
170 desc 'Log details of the deploy'
190 debug "Last release is the current release, skip cleanup_rollback."
191 end
192 end
193 end
194
195 desc "Log details of the deploy"
171196 task :log_revision do
172197 on release_roles(:all) do
173198 within releases_path do
174 execute %{echo "#{revision_log_message}" >> #{revision_log}}
175 end
176 end
177 end
178
179 desc 'Revert to previous release timestamp'
180 task :revert_release => :rollback_release_path do
199 execute :echo, %Q{"#{revision_log_message}" >> #{revision_log}}
200 end
201 end
202 end
203
204 desc "Revert to previous release timestamp"
205 task revert_release: :rollback_release_path do
181206 on release_roles(:all) do
182207 set(:revision_log_message, rollback_log_message)
183208 end
189214
190215 task :rollback_release_path do
191216 on release_roles(:all) do
192 releases = capture(:ls, '-xt', releases_path).split
217 releases = capture(:ls, "-xt", releases_path).split
193218 if releases.count < 2
194219 error t(:cannot_rollback)
195220 exit 1
196221 end
197 last_release = releases[1]
222
223 rollback_release = ENV["ROLLBACK_RELEASE"]
224 index = rollback_release.nil? ? 1 : releases.index(rollback_release)
225 if index.nil?
226 error t(:cannot_found_rollback_release, release: rollback_release)
227 exit 1
228 end
229
230 last_release = releases[index]
198231 set_release_path(last_release)
199232 set(:rollback_timestamp, last_release)
200233 end
202235
203236 desc "Place a REVISION file with the current revision SHA in the current release path"
204237 task :set_current_revision do
205 invoke "#{scm}:set_current_revision"
206238 on release_roles(:all) do
207239 within release_path do
208240 execute :echo, "\"#{fetch(:current_revision)}\" >> REVISION"
212244
213245 task :set_previous_revision do
214246 on release_roles(:all) do
215 target = release_path.join('REVISION')
247 target = release_path.join("REVISION")
216248 if test "[ -f #{target} ]"
217 set(:previous_revision, capture(:cat, target, '2>/dev/null'))
249 set(:previous_revision, capture(:cat, target, "2>/dev/null"))
218250 end
219251 end
220252 end
221253
222254 task :restart
223255 task :failed
224
225256 end
0 desc "Display a Capistrano troubleshooting report (all doctor: tasks)"
1 task doctor: ["doctor:environment", "doctor:gems", "doctor:variables", "doctor:servers"]
2
3 namespace :doctor do
4 desc "Display Ruby environment details"
5 task :environment do
6 Capistrano::Doctor::EnvironmentDoctor.new.call
7 end
8
9 desc "Display Capistrano gem versions"
10 task :gems do
11 Capistrano::Doctor::GemsDoctor.new.call
12 end
13
14 desc "Display the values of all Capistrano variables"
15 task :variables do
16 Capistrano::Doctor::VariablesDoctor.new.call
17 end
18
19 desc "Display the effective servers configuration"
20 task :servers do
21 Capistrano::Doctor::ServersDoctor.new.call
22 end
23 end
00 namespace :deploy do
1
2 desc 'Start a deployment, make sure server(s) ready.'
1 desc "Start a deployment, make sure server(s) ready."
32 task :starting do
43 end
54
6 desc 'Started'
5 desc "Started"
76 task :started do
87 end
98
10 desc 'Update server(s) by setting up a new release.'
9 desc "Update server(s) by setting up a new release."
1110 task :updating do
1211 end
1312
14 desc 'Updated'
13 desc "Updated"
1514 task :updated do
1615 end
1716
18 desc 'Revert server(s) to previous release.'
17 desc "Revert server(s) to previous release."
1918 task :reverting do
2019 end
2120
22 desc 'Reverted'
21 desc "Reverted"
2322 task :reverted do
2423 end
2524
26 desc 'Publish the release.'
25 desc "Publish the release."
2726 task :publishing do
2827 end
2928
30 desc 'Published'
29 desc "Published"
3130 task :published do
3231 end
3332
34 desc 'Finish the deployment, clean up server(s).'
33 desc "Finish the deployment, clean up server(s)."
3534 task :finishing do
3635 end
3736
38 desc 'Finish the rollback, clean up server(s).'
37 desc "Finish the rollback, clean up server(s)."
3938 task :finishing_rollback do
4039 end
4140
42 desc 'Finished'
41 desc "Finished"
4342 task :finished do
4443 end
4544
46 desc 'Rollback to previous release.'
45 desc "Rollback to previous release."
4746 task :rollback do
4847 %w{ starting started
4948 reverting reverted
5453 end
5554 end
5655
57 desc 'Deploy a new release.'
56 desc "Deploy a new release."
5857 task :deploy do
5958 set(:deploying, true)
6059 %w{ starting started
+0
-81
lib/capistrano/tasks/git.rake less more
0 namespace :git do
1
2 def strategy
3 @strategy ||= Capistrano::Git.new(self, fetch(:git_strategy, Capistrano::Git::DefaultStrategy))
4 end
5
6 set :git_environmental_variables, ->() {
7 {
8 git_askpass: "/bin/echo",
9 git_ssh: "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
10 }
11 }
12
13 desc 'Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt'
14 task :wrapper do
15 on release_roles :all do
16 execute :mkdir, "-p", "#{fetch(:tmp_dir)}/#{fetch(:application)}/"
17 upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
18 execute :chmod, "+x", "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
19 end
20 end
21
22 desc 'Check that the repository is reachable'
23 task check: :'git:wrapper' do
24 fetch(:branch)
25 on release_roles :all do
26 with fetch(:git_environmental_variables) do
27 strategy.check
28 end
29 end
30 end
31
32 desc 'Clone the repo to the cache'
33 task clone: :'git:wrapper' do
34 on release_roles :all do
35 if strategy.test
36 info t(:mirror_exists, at: repo_path)
37 else
38 within deploy_path do
39 with fetch(:git_environmental_variables) do
40 strategy.clone
41 end
42 end
43 end
44 end
45 end
46
47 desc 'Update the repo mirror to reflect the origin state'
48 task update: :'git:clone' do
49 on release_roles :all do
50 within repo_path do
51 with fetch(:git_environmental_variables) do
52 strategy.update
53 end
54 end
55 end
56 end
57
58 desc 'Copy repo to releases'
59 task create_release: :'git:update' do
60 on release_roles :all do
61 with fetch(:git_environmental_variables) do
62 within repo_path do
63 execute :mkdir, '-p', release_path
64 strategy.release
65 end
66 end
67 end
68 end
69
70 desc 'Determine the revision that will be deployed'
71 task :set_current_revision do
72 on release_roles :all do
73 within repo_path do
74 with fetch(:git_environmental_variables) do
75 set :current_revision, strategy.fetch_revision
76 end
77 end
78 end
79 end
80 end
+0
-52
lib/capistrano/tasks/hg.rake less more
0 namespace :hg do
1 def strategy
2 @strategy ||= Capistrano::Hg.new(self, fetch(:hg_strategy, Capistrano::Hg::DefaultStrategy))
3 end
4
5 desc 'Check that the repo is reachable'
6 task :check do
7 on release_roles :all do
8 strategy.check
9 end
10 end
11
12 desc 'Clone the repo to the cache'
13 task :clone do
14 on release_roles :all do
15 if strategy.test
16 info t(:mirror_exists, at: repo_path)
17 else
18 within deploy_path do
19 strategy.clone
20 end
21 end
22 end
23 end
24
25 desc 'Pull changes from the remote repo'
26 task :update => :'hg:clone' do
27 on release_roles :all do
28 within repo_path do
29 strategy.update
30 end
31 end
32 end
33
34 desc 'Copy repo to releases'
35 task :create_release => :'hg:update' do
36 on release_roles :all do
37 within repo_path do
38 strategy.release
39 end
40 end
41 end
42
43 desc 'Determine the revision that will be deployed'
44 task :set_current_revision do
45 on release_roles :all do
46 within repo_path do
47 set :current_revision, strategy.fetch_revision
48 end
49 end
50 end
51 end
0 require 'erb'
1 require 'pathname'
2 desc 'Install Capistrano, cap install STAGES=staging,production'
0 require "erb"
1 require "pathname"
2 desc "Install Capistrano, cap install STAGES=staging,production"
33 task :install do
4 envs = ENV['STAGES'] || 'staging,production'
4 envs = ENV["STAGES"] || "staging,production"
55
6 tasks_dir = Pathname.new('lib/capistrano/tasks')
7 config_dir = Pathname.new('config')
8 deploy_dir = config_dir.join('deploy')
6 tasks_dir = Pathname.new("lib/capistrano/tasks")
7 config_dir = Pathname.new("config")
8 deploy_dir = config_dir.join("deploy")
99
1010 deploy_rb = File.expand_path("../../templates/deploy.rb.erb", __FILE__)
1111 stage_rb = File.expand_path("../../templates/stage.rb.erb", __FILE__)
1313
1414 mkdir_p deploy_dir
1515
16 entries = [{template: deploy_rb, file: config_dir.join('deploy.rb')}]
17 entries += envs.split(',').map { |stage| {template: stage_rb, file: deploy_dir.join("#{stage}.rb")} }
16 entries = [{ template: deploy_rb, file: config_dir.join("deploy.rb") }]
17 entries += envs.split(",").map { |stage| { template: stage_rb, file: deploy_dir.join("#{stage}.rb") } }
1818
1919 entries.each do |entry|
20 if File.exists?(entry[:file])
20 if File.exist?(entry[:file])
2121 warn "[skip] #{entry[:file]} already exists"
2222 else
23 File.open(entry[:file], 'w+') do |f|
23 File.open(entry[:file], "w+") do |f|
2424 f.write(ERB.new(File.read(entry[:template])).result(binding))
2525 puts I18n.t(:written_file, scope: :capistrano, file: entry[:file])
2626 end
2929
3030 mkdir_p tasks_dir
3131
32 if File.exists?('Capfile')
32 if File.exist?("Capfile")
3333 warn "[skip] Capfile already exists"
3434 else
35 FileUtils.cp(capfile, 'Capfile')
36 puts I18n.t(:written_file, scope: :capistrano, file: 'Capfile')
35 FileUtils.cp(capfile, "Capfile")
36 puts I18n.t(:written_file, scope: :capistrano, file: "Capfile")
3737 end
38
3938
4039 puts I18n.t :capified, scope: :capistrano
4140 end
+0
-52
lib/capistrano/tasks/svn.rake less more
0 namespace :svn do
1 def strategy
2 @strategy ||= Capistrano::Svn.new(self, fetch(:svn_strategy, Capistrano::Svn::DefaultStrategy))
3 end
4
5 desc 'Check that the repo is reachable'
6 task :check do
7 on release_roles :all do
8 strategy.check
9 end
10 end
11
12 desc 'Clone the repo to the cache'
13 task :clone do
14 on release_roles :all do
15 if strategy.test
16 info t(:mirror_exists, at: repo_path)
17 else
18 within deploy_path do
19 strategy.clone
20 end
21 end
22 end
23 end
24
25 desc 'Pull changes from the remote repo'
26 task :update => :'svn:clone' do
27 on release_roles :all do
28 within repo_path do
29 strategy.update
30 end
31 end
32 end
33
34 desc 'Copy repo to releases'
35 task :create_release => :'svn:update' do
36 on release_roles :all do
37 within repo_path do
38 strategy.release
39 end
40 end
41 end
42
43 desc 'Determine the revision that will be deployed'
44 task :set_current_revision do
45 on release_roles :all do
46 within repo_path do
47 set :current_revision, strategy.fetch_revision
48 end
49 end
50 end
51 end
00 # Load DSL and set up stages
1 require 'capistrano/setup'
1 require "capistrano/setup"
22
33 # Include default deployment tasks
4 require 'capistrano/deploy'
4 require "capistrano/deploy"
5
6 # Load the SCM plugin appropriate to your project:
7 #
8 # require "capistrano/scm/hg"
9 # install_plugin Capistrano::SCM::Hg
10 # or
11 # require "capistrano/scm/svn"
12 # install_plugin Capistrano::SCM::Svn
13 # or
14 require "capistrano/scm/git"
15 install_plugin Capistrano::SCM::Git
516
617 # Include tasks from other gems included in your Gemfile
718 #
1425 # https://github.com/capistrano/rails
1526 # https://github.com/capistrano/passenger
1627 #
17 # require 'capistrano/rvm'
18 # require 'capistrano/rbenv'
19 # require 'capistrano/chruby'
20 # require 'capistrano/bundler'
21 # require 'capistrano/rails/assets'
22 # require 'capistrano/rails/migrations'
23 # require 'capistrano/passenger'
28 # require "capistrano/rvm"
29 # require "capistrano/rbenv"
30 # require "capistrano/chruby"
31 # require "capistrano/bundler"
32 # require "capistrano/rails/assets"
33 # require "capistrano/rails/migrations"
34 # require "capistrano/passenger"
2435
2536 # Load custom tasks from `lib/capistrano/tasks` if you have any defined
26 Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
37 Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
0 # config valid only for current version of Capistrano
1 lock '<%= Capistrano::VERSION %>'
0 # config valid for current version and patch releases of Capistrano
1 lock "~> <%= Capistrano::VERSION %>"
22
3 set :application, 'my_app_name'
4 set :repo_url, 'git@example.com:me/my_repo.git'
3 set :application, "my_app_name"
4 set :repo_url, "git@example.com:me/my_repo.git"
55
66 # Default branch is :master
77 # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
88
99 # Default deploy_to directory is /var/www/my_app_name
10 # set :deploy_to, '/var/www/my_app_name'
10 # set :deploy_to, "/var/www/my_app_name"
1111
12 # Default value for :scm is :git
13 # set :scm, :git
12 # Default value for :format is :airbrussh.
13 # set :format, :airbrussh
1414
15 # Default value for :format is :pretty
16 # set :format, :pretty
17
18 # Default value for :log_level is :debug
19 # set :log_level, :debug
15 # You can configure the Airbrussh format using :format_options.
16 # These are the defaults.
17 # set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto
2018
2119 # Default value for :pty is false
2220 # set :pty, true
2321
2422 # Default value for :linked_files is []
25 # set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
23 # append :linked_files, "config/database.yml", "config/secrets.yml"
2624
2725 # Default value for linked_dirs is []
28 # set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
26 # append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
2927
3028 # Default value for default_env is {}
3129 # set :default_env, { path: "/opt/ruby/bin:$PATH" }
3230
31 # Default value for local_user is ENV['USER']
32 # set :local_user, -> { `git config user.name`.chomp }
33
3334 # Default value for keep_releases is 5
3435 # set :keep_releases, 5
3536
36 namespace :deploy do
37
38 after :restart, :clear_cache do
39 on roles(:web), in: :groups, limit: 3, wait: 10 do
40 # Here we can do anything such as:
41 # within release_path do
42 # execute :rake, 'cache:clear'
43 # end
44 end
45 end
46
47 end
37 # Uncomment the following to require manually verifying the host key before first deploy.
38 # set :ssh_options, verify_host_key: :secure
22 # Defines a single server with a list of roles and multiple properties.
33 # You can define all roles on a single server, or split them:
44
5 # server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
6 # server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
7 # server 'db.example.com', user: 'deploy', roles: %w{db}
5 # server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
6 # server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
7 # server "db.example.com", user: "deploy", roles: %w{db}
88
99
1010
1212 # ==================
1313
1414 # Defines a role with one or multiple servers. The primary server in each
15 # group is considered to be the first unless any hosts have the primary
15 # group is considered to be the first unless any hosts have the primary
1616 # property set. Specify the username and a domain or IP for the server.
1717 # Don't use `:all`, it's a meta role.
1818
4848 #
4949 # The server-based syntax can be used to override options:
5050 # ------------------------------------
51 # server 'example.com',
52 # user: 'user_name',
51 # server "example.com",
52 # user: "user_name",
5353 # roles: %w{web app},
5454 # ssh_options: {
55 # user: 'user_name', # overrides user setting above
55 # user: "user_name", # overrides user setting above
5656 # keys: %w(/home/user_name/.ssh/id_rsa),
5757 # forward_agent: false,
5858 # auth_methods: %w(publickey password)
59 # # password: 'please use keys'
59 # # password: "please use keys"
6060 # }
0 require 'rake/file_creation_task'
0 require "rake/file_creation_task"
11
22 module Capistrano
33 class UploadTask < Rake::FileCreationTask
00 module Capistrano
1 VERSION = "3.4.0"
1 VERSION = "3.10.0".freeze
22 end
00 module Capistrano
11 class VersionValidator
2
32 def initialize(version)
43 @version = version
54 end
65
76 def verify
8 if match?
9 self
10 else
11 fail "Capfile locked at #{version}, but #{current_version} is loaded"
12 end
7 return self if match?
8 raise "Capfile locked at #{version}, but #{current_version} is loaded"
139 end
1410
1511 private
12
1613 attr_reader :version
17
1814
1915 def match?
2016 available =~ requested
2521 end
2622
2723 def available
28 Gem::Dependency.new('cap', version)
24 Gem::Dependency.new("cap", version)
2925 end
3026
3127 def requested
32 Gem::Dependency.new('cap', current_version)
28 Gem::Dependency.new("cap", current_version)
3329 end
34
3530 end
3631 end
+0
-252
metadata.yml less more
0 --- !ruby/object:Gem::Specification
1 name: capistrano
2 version: !ruby/object:Gem::Version
3 version: 3.4.0
4 platform: ruby
5 authors:
6 - Tom Clements
7 - Lee Hambley
8 autorequire:
9 bindir: bin
10 cert_chain: []
11 date: 2015-03-02 00:00:00.000000000 Z
12 dependencies:
13 - !ruby/object:Gem::Dependency
14 name: sshkit
15 requirement: !ruby/object:Gem::Requirement
16 requirements:
17 - - "~>"
18 - !ruby/object:Gem::Version
19 version: '1.3'
20 type: :runtime
21 prerelease: false
22 version_requirements: !ruby/object:Gem::Requirement
23 requirements:
24 - - "~>"
25 - !ruby/object:Gem::Version
26 version: '1.3'
27 - !ruby/object:Gem::Dependency
28 name: rake
29 requirement: !ruby/object:Gem::Requirement
30 requirements:
31 - - ">="
32 - !ruby/object:Gem::Version
33 version: 10.0.0
34 type: :runtime
35 prerelease: false
36 version_requirements: !ruby/object:Gem::Requirement
37 requirements:
38 - - ">="
39 - !ruby/object:Gem::Version
40 version: 10.0.0
41 - !ruby/object:Gem::Dependency
42 name: i18n
43 requirement: !ruby/object:Gem::Requirement
44 requirements:
45 - - ">="
46 - !ruby/object:Gem::Version
47 version: '0'
48 type: :runtime
49 prerelease: false
50 version_requirements: !ruby/object:Gem::Requirement
51 requirements:
52 - - ">="
53 - !ruby/object:Gem::Version
54 version: '0'
55 - !ruby/object:Gem::Dependency
56 name: rspec
57 requirement: !ruby/object:Gem::Requirement
58 requirements:
59 - - ">="
60 - !ruby/object:Gem::Version
61 version: '0'
62 type: :development
63 prerelease: false
64 version_requirements: !ruby/object:Gem::Requirement
65 requirements:
66 - - ">="
67 - !ruby/object:Gem::Version
68 version: '0'
69 - !ruby/object:Gem::Dependency
70 name: mocha
71 requirement: !ruby/object:Gem::Requirement
72 requirements:
73 - - ">="
74 - !ruby/object:Gem::Version
75 version: '0'
76 type: :development
77 prerelease: false
78 version_requirements: !ruby/object:Gem::Requirement
79 requirements:
80 - - ">="
81 - !ruby/object:Gem::Version
82 version: '0'
83 description: Capistrano is a utility and framework for executing commands in parallel
84 on multiple remote machines, via SSH.
85 email:
86 - seenmyfate@gmail.com
87 - lee.hambley@gmail.com
88 executables:
89 - cap
90 - capify
91 extensions: []
92 extra_rdoc_files: []
93 files:
94 - ".gitignore"
95 - ".travis.yml"
96 - CHANGELOG.md
97 - CONTRIBUTING.md
98 - Gemfile
99 - LICENSE.txt
100 - README.md
101 - Rakefile
102 - bin/cap
103 - bin/capify
104 - capistrano.gemspec
105 - features/configuration.feature
106 - features/deploy.feature
107 - features/deploy_failure.feature
108 - features/installation.feature
109 - features/remote_file_task.feature
110 - features/sshconnect.feature
111 - features/step_definitions/assertions.rb
112 - features/step_definitions/cap_commands.rb
113 - features/step_definitions/setup.rb
114 - features/support/env.rb
115 - features/support/remote_command_helpers.rb
116 - features/support/vagrant_helpers.rb
117 - lib/Capfile
118 - lib/capistrano.rb
119 - lib/capistrano/all.rb
120 - lib/capistrano/application.rb
121 - lib/capistrano/configuration.rb
122 - lib/capistrano/configuration/filter.rb
123 - lib/capistrano/configuration/question.rb
124 - lib/capistrano/configuration/server.rb
125 - lib/capistrano/configuration/servers.rb
126 - lib/capistrano/console.rb
127 - lib/capistrano/defaults.rb
128 - lib/capistrano/deploy.rb
129 - lib/capistrano/dotfile.rb
130 - lib/capistrano/dsl.rb
131 - lib/capistrano/dsl/env.rb
132 - lib/capistrano/dsl/paths.rb
133 - lib/capistrano/dsl/stages.rb
134 - lib/capistrano/dsl/task_enhancements.rb
135 - lib/capistrano/framework.rb
136 - lib/capistrano/git.rb
137 - lib/capistrano/hg.rb
138 - lib/capistrano/i18n.rb
139 - lib/capistrano/install.rb
140 - lib/capistrano/scm.rb
141 - lib/capistrano/setup.rb
142 - lib/capistrano/svn.rb
143 - lib/capistrano/tasks/console.rake
144 - lib/capistrano/tasks/deploy.rake
145 - lib/capistrano/tasks/framework.rake
146 - lib/capistrano/tasks/git.rake
147 - lib/capistrano/tasks/hg.rake
148 - lib/capistrano/tasks/install.rake
149 - lib/capistrano/tasks/svn.rake
150 - lib/capistrano/templates/Capfile
151 - lib/capistrano/templates/deploy.rb.erb
152 - lib/capistrano/templates/stage.rb.erb
153 - lib/capistrano/upload_task.rb
154 - lib/capistrano/version.rb
155 - lib/capistrano/version_validator.rb
156 - spec/integration/dsl_spec.rb
157 - spec/integration_spec_helper.rb
158 - spec/lib/capistrano/application_spec.rb
159 - spec/lib/capistrano/configuration/filter_spec.rb
160 - spec/lib/capistrano/configuration/question_spec.rb
161 - spec/lib/capistrano/configuration/server_spec.rb
162 - spec/lib/capistrano/configuration/servers_spec.rb
163 - spec/lib/capistrano/configuration_spec.rb
164 - spec/lib/capistrano/dsl/paths_spec.rb
165 - spec/lib/capistrano/dsl/task_enhancements_spec.rb
166 - spec/lib/capistrano/dsl_spec.rb
167 - spec/lib/capistrano/git_spec.rb
168 - spec/lib/capistrano/hg_spec.rb
169 - spec/lib/capistrano/scm_spec.rb
170 - spec/lib/capistrano/svn_spec.rb
171 - spec/lib/capistrano/upload_task_spec.rb
172 - spec/lib/capistrano/version_validator_spec.rb
173 - spec/lib/capistrano_spec.rb
174 - spec/spec_helper.rb
175 - spec/support/.gitignore
176 - spec/support/Vagrantfile
177 - spec/support/matchers.rb
178 - spec/support/tasks/database.rake
179 - spec/support/tasks/fail.rake
180 - spec/support/tasks/failed.rake
181 - spec/support/tasks/root.rake
182 - spec/support/test_app.rb
183 homepage: http://capistranorb.com/
184 licenses:
185 - MIT
186 metadata: {}
187 post_install_message: |
188 Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr
189
190 If you're upgrading Capistrano from 2.x, we recommend to read the upgrade guide: http://goo.gl/4536kB
191
192 The `deploy:restart` hook for passenger applications is now in a separate gem called capistrano-passenger. Just add it to your Gemfile and require it in your Capfile.
193 rdoc_options: []
194 require_paths:
195 - lib
196 required_ruby_version: !ruby/object:Gem::Requirement
197 requirements:
198 - - ">="
199 - !ruby/object:Gem::Version
200 version: 1.9.3
201 required_rubygems_version: !ruby/object:Gem::Requirement
202 requirements:
203 - - ">="
204 - !ruby/object:Gem::Version
205 version: '0'
206 requirements: []
207 rubyforge_project:
208 rubygems_version: 2.4.3
209 signing_key:
210 specification_version: 4
211 summary: Capistrano - Welcome to easy deployment with Ruby over SSH
212 test_files:
213 - features/configuration.feature
214 - features/deploy.feature
215 - features/deploy_failure.feature
216 - features/installation.feature
217 - features/remote_file_task.feature
218 - features/sshconnect.feature
219 - features/step_definitions/assertions.rb
220 - features/step_definitions/cap_commands.rb
221 - features/step_definitions/setup.rb
222 - features/support/env.rb
223 - features/support/remote_command_helpers.rb
224 - features/support/vagrant_helpers.rb
225 - spec/integration/dsl_spec.rb
226 - spec/integration_spec_helper.rb
227 - spec/lib/capistrano/application_spec.rb
228 - spec/lib/capistrano/configuration/filter_spec.rb
229 - spec/lib/capistrano/configuration/question_spec.rb
230 - spec/lib/capistrano/configuration/server_spec.rb
231 - spec/lib/capistrano/configuration/servers_spec.rb
232 - spec/lib/capistrano/configuration_spec.rb
233 - spec/lib/capistrano/dsl/paths_spec.rb
234 - spec/lib/capistrano/dsl/task_enhancements_spec.rb
235 - spec/lib/capistrano/dsl_spec.rb
236 - spec/lib/capistrano/git_spec.rb
237 - spec/lib/capistrano/hg_spec.rb
238 - spec/lib/capistrano/scm_spec.rb
239 - spec/lib/capistrano/svn_spec.rb
240 - spec/lib/capistrano/upload_task_spec.rb
241 - spec/lib/capistrano/version_validator_spec.rb
242 - spec/lib/capistrano_spec.rb
243 - spec/spec_helper.rb
244 - spec/support/.gitignore
245 - spec/support/Vagrantfile
246 - spec/support/matchers.rb
247 - spec/support/tasks/database.rake
248 - spec/support/tasks/fail.rake
249 - spec/support/tasks/failed.rake
250 - spec/support/tasks/root.rake
251 - spec/support/test_app.rb
0 require 'spec_helper'
0 require "spec_helper"
11
22 describe Capistrano::DSL do
3
43 let(:dsl) { Class.new.extend Capistrano::DSL }
54
65 before do
76 Capistrano::Configuration.reset!
87 end
98
10 describe 'setting and fetching hosts' do
11 describe 'when defining a host using the `server` syntax' do
9 describe "setting and fetching hosts" do
10 describe "when defining a host using the `server` syntax" do
1211 before do
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
18 end
19
20 describe 'fetching all servers' do
12 dsl.server "example1.com", roles: %w{web}, active: true
13 dsl.server "example2.com", roles: %w{web}
14 dsl.server "example3.com", roles: %w{app web}, active: true
15 dsl.server "example4.com", roles: %w{app}, primary: true
16 dsl.server "example5.com", roles: %w{db}, no_release: true, active: true
17 end
18
19 describe "fetching all servers" do
2120 subject { dsl.roles(:all) }
2221
23 it 'returns all servers' do
22 it "returns all servers" do
2423 expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com}
2524 end
2625 end
2726
28 describe 'fetching all release servers' do
29
30 context 'with no additional options' do
27 describe "fetching all release servers" do
28 context "with no additional options" do
3129 subject { dsl.release_roles(:all) }
3230
33 it 'returns all release servers' do
31 it "returns all release servers" do
3432 expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com}
3533 end
3634 end
3735
38 context 'with property filter options' do
36 context "with property filter options" do
3937 subject { dsl.release_roles(:all, filter: :active) }
4038
41 it 'returns all release servers that match the property filter' do
39 it "returns all release servers that match the property filter" do
4240 expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
4341 end
4442 end
4543 end
4644
47 describe 'fetching servers by multiple roles' do
45 describe "fetching servers by multiple roles" do
4846 it "does not confuse the last role with options" do
4947 expect(dsl.roles(:app, :web).count).to eq 4
5048 expect(dsl.roles(:app, :web, filter: :active).count).to eq 2
5149 end
5250 end
5351
54 describe 'fetching servers by role' do
55 subject { dsl.roles(:app) }
56
57 it 'returns the servers' do
52 describe "fetching servers by role" do
53 subject { dsl.roles(:app) }
54
55 it "returns the servers" do
5856 expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
5957 end
6058 end
6159
62 describe 'fetching servers by an array of roles' do
60 describe "fetching servers by an array of roles" do
6361 subject { dsl.roles([:app]) }
6462
65 it 'returns the servers' do
63 it "returns the servers" do
6664 expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
6765 end
6866 end
6967
70 describe 'fetching filtered servers by role' do
68 describe "fetching filtered servers by role" do
7169 subject { dsl.roles(:app, filter: :active) }
7270
73 it 'returns the servers' do
71 it "returns the servers" do
7472 expect(subject.map(&:hostname)).to eq %w{example3.com}
7573 end
7674 end
7775
78 describe 'fetching selected servers by role' do
76 describe "fetching selected servers by role" do
7977 subject { dsl.roles(:app, select: :active) }
8078
81 it 'returns the servers' do
79 it "returns the servers" do
8280 expect(subject.map(&:hostname)).to eq %w{example3.com}
8381 end
8482 end
8583
86 describe 'fetching the primary server by role' do
87 context 'when inferring primary status based on order' do
84 describe "fetching the primary server by role" do
85 context "when inferring primary status based on order" do
8886 subject { dsl.primary(:web) }
89 it 'returns the servers' do
90 expect(subject.hostname).to eq 'example1.com'
91 end
92 end
93
94 context 'when the attribute `primary` is explicitly set' do
87 it "returns the servers" do
88 expect(subject.hostname).to eq "example1.com"
89 end
90 end
91
92 context "when the attribute `primary` is explicitly set" do
9593 subject { dsl.primary(:app) }
96 it 'returns the servers' do
97 expect(subject.hostname).to eq 'example4.com'
98 end
99 end
100 end
101
102 describe 'setting an internal host filter' do
103 subject { dsl.roles(:app) }
104 it 'is ignored' do
105 dsl.set :filter, { host: 'example3.com' }
106 expect(subject.map(&:hostname)).to eq(['example3.com', 'example4.com'])
107 end
108 end
109
110 describe 'setting an internal role filter' do
111 subject { dsl.roles(:app) }
112 it 'ignores it' do
113 dsl.set :filter, { role: :web }
114 expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
115 end
116 end
117
118 describe 'setting an internal host and role filter' do
119 subject { dsl.roles(:app) }
120 it 'ignores it' do
121 dsl.set :filter, { role: :web, host: 'example1.com' }
122 expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
123 end
124 end
125
126 describe 'setting an internal regexp host filter' do
94 it "returns the servers" do
95 expect(subject.hostname).to eq "example4.com"
96 end
97 end
98 end
99
100 describe "setting an internal host filter" do
101 subject { dsl.roles(:app) }
102 it "is ignored" do
103 dsl.set :filter, host: "example3.com"
104 expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
105 end
106 end
107
108 describe "setting an internal role filter" do
109 subject { dsl.roles(:app) }
110 it "ignores it" do
111 dsl.set :filter, role: :web
112 expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
113 end
114 end
115
116 describe "setting an internal host and role filter" do
117 subject { dsl.roles(:app) }
118 it "ignores it" do
119 dsl.set :filter, role: :web, host: "example1.com"
120 expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
121 end
122 end
123
124 describe "setting an internal regexp host filter" do
127125 subject { dsl.roles(:all) }
128 it 'is ignored' do
129 dsl.set :filter, { host: /1/ }
126 it "is ignored" do
127 dsl.set :filter, host: /1/
130128 expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
131129 end
132130 end
133131
134 end
135
136 describe 'when defining role with reserved name' do
137 it 'fails with ArgumentError' do
138 expect {
132 describe "setting an internal hosts filter" do
133 subject { dsl.roles(:app) }
134 it "is ignored" do
135 dsl.set :filter, hosts: "example3.com"
136 expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
137 end
138 end
139
140 describe "setting an internal roles filter" do
141 subject { dsl.roles(:app) }
142 it "ignores it" do
143 dsl.set :filter, roles: :web
144 expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
145 end
146 end
147
148 describe "setting an internal hosts and roles filter" do
149 subject { dsl.roles(:app) }
150 it "ignores it" do
151 dsl.set :filter, roles: :web, hosts: "example1.com"
152 expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
153 end
154 end
155
156 describe "setting an internal regexp hosts filter" do
157 subject { dsl.roles(:all) }
158 it "is ignored" do
159 dsl.set :filter, hosts: /1/
160 expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
161 end
162 end
163 end
164
165 describe "when defining role with reserved name" do
166 it "fails with ArgumentError" do
167 expect do
139168 dsl.role :all, %w{example1.com}
140 }.to raise_error(ArgumentError, "all reserved name for role. Please choose another name")
141 end
142 end
143
144 describe 'when defining hosts using the `role` syntax' do
169 end.to raise_error(ArgumentError, "all reserved name for role. Please choose another name")
170 end
171 end
172
173 describe "when defining hosts using the `role` syntax" do
145174 before do
146175 dsl.role :web, %w{example1.com example2.com example3.com}
147176 dsl.role :web, %w{example1.com}, active: true
151180 dsl.role :db, %w{example5.com}, no_release: true
152181 end
153182
154 describe 'fetching all servers' do
183 describe "fetching all servers" do
155184 subject { dsl.roles(:all) }
156185
157 it 'returns all servers' do
186 it "returns all servers" do
158187 expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com}
159188 end
160189 end
161190
162 describe 'fetching all release servers' do
163
164 context 'with no additional options' do
191 describe "fetching all release servers" do
192 context "with no additional options" do
165193 subject { dsl.release_roles(:all) }
166194
167 it 'returns all release servers' do
195 it "returns all release servers" do
168196 expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com}
169197 end
170198 end
171199
172 context 'with filter options' do
200 context "with filter options" do
173201 subject { dsl.release_roles(:all, filter: :active) }
174202
175 it 'returns all release servers that match the filter' do
203 it "returns all release servers that match the filter" do
176204 expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
177205 end
178206 end
179207 end
180208
181
182 describe 'fetching servers by role' do
183 subject { dsl.roles(:app) }
184
185 it 'returns the servers' do
209 describe "fetching servers by role" do
210 subject { dsl.roles(:app) }
211
212 it "returns the servers" do
186213 expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
187214 end
188215 end
189216
190 describe 'fetching servers by an array of roles' do
217 describe "fetching servers by an array of roles" do
191218 subject { dsl.roles([:app]) }
192219
193 it 'returns the servers' do
220 it "returns the servers" do
194221 expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
195222 end
196223 end
197224
198 describe 'fetching filtered servers by role' do
225 describe "fetching filtered servers by role" do
199226 subject { dsl.roles(:app, filter: :active) }
200227
201 it 'returns the servers' do
228 it "returns the servers" do
202229 expect(subject.map(&:hostname)).to eq %w{example3.com}
203230 end
204231 end
205232
206 describe 'fetching selected servers by role' do
233 describe "fetching selected servers by role" do
207234 subject { dsl.roles(:app, select: :active) }
208235
209 it 'returns the servers' do
236 it "returns the servers" do
210237 expect(subject.map(&:hostname)).to eq %w{example3.com}
211238 end
212239 end
213240
214 describe 'fetching the primary server by role' do
215 context 'when inferring primary status based on order' do
241 describe "fetching the primary server by role" do
242 context "when inferring primary status based on order" do
216243 subject { dsl.primary(:web) }
217 it 'returns the servers' do
218 expect(subject.hostname).to eq 'example1.com'
219 end
220 end
221
222 context 'when the attribute `primary` is explicity set' do
244 it "returns the servers" do
245 expect(subject.hostname).to eq "example1.com"
246 end
247 end
248
249 context "when the attribute `primary` is explicity set" do
223250 subject { dsl.primary(:app) }
224 it 'returns the servers' do
225 expect(subject.hostname).to eq 'example4.com'
226 end
227 end
228 end
229
230 end
231
232 describe 'when defining a host using a combination of the `server` and `role` syntax' do
233
251 it "returns the servers" do
252 expect(subject.hostname).to eq "example4.com"
253 end
254 end
255 end
256 end
257
258 describe "when defining a host using a combination of the `server` and `role` syntax" do
234259 before do
235 dsl.server 'db@example1.com:1234', roles: %w{db}, active: true
236 dsl.server 'root@example1.com:1234', roles: %w{web}, active: true
237 dsl.server 'example1.com:5678', roles: %w{web}, active: true
260 dsl.server "db@example1.com:1234", roles: %w{db}, active: true
261 dsl.server "root@example1.com:1234", roles: %w{web}, active: true
262 dsl.server "example1.com:5678", roles: %w{web}, active: true
238263 dsl.role :app, %w{deployer@example1.com:1234}
239264 dsl.role :app, %w{example1.com:5678}
240265 end
241266
242 describe 'fetching all servers' do
243 it 'creates one server per hostname, ignoring user and port combinations' do
244 expect(dsl.roles(:all).size).to eq(1)
245 end
246 end
247
248 describe 'fetching servers for a role' do
249 it 'roles defined using the `server` syntax are included' do
267 describe "fetching all servers" do
268 it "creates one server per hostname, ignoring user combinations" do
269 expect(dsl.roles(:all).size).to eq(2)
270 end
271 end
272
273 describe "fetching servers for a role" do
274 it "roles defined using the `server` syntax are included" do
250275 as = dsl.roles(:web).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
251 expect(as.size).to eq(1)
252 expect(as[0]).to eq("deployer@example1.com:5678")
253 end
254
255 it 'roles defined using the `role` syntax are included' do
276 expect(as.size).to eq(2)
277 expect(as[0]).to eq("deployer@example1.com:1234")
278 expect(as[1]).to eq("@example1.com:5678")
279 end
280
281 it "roles defined using the `role` syntax are included" do
256282 as = dsl.roles(:app).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
257 expect(as.size).to eq(1)
258 expect(as[0]).to eq("deployer@example1.com:5678")
259 end
260 end
261
262 end
263
264 describe 'when setting user and port' do
283 expect(as.size).to eq(2)
284 expect(as[0]).to eq("deployer@example1.com:1234")
285 expect(as[1]).to eq("@example1.com:5678")
286 end
287 end
288 end
289
290 describe "when setting user and port" do
265291 subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first }
266292
267293 describe "using the :user property" do
268294 it "takes precedence over in the host string" do
269 dsl.server 'db@example1.com:1234', roles: %w{db}, active: true, user: 'brian'
295 dsl.server "db@example1.com:1234", roles: %w{db}, active: true, user: "brian"
270296 expect(subject).to eq("brian@example1.com:1234")
271297 end
272298 end
273299
274300 describe "using the :port property" do
275301 it "takes precedence over in the host string" do
276 dsl.server 'db@example1.com:9090', roles: %w{db}, active: true, port: 1234
302 dsl.server "db@example1.com:9090", roles: %w{db}, active: true, port: 1234
277303 expect(subject).to eq("db@example1.com:1234")
278304 end
279305 end
280306 end
281
282 end
283
284 describe 'setting and fetching variables' do
285
307 end
308
309 describe "setting and fetching variables" do
286310 before do
287311 dsl.set :scm, :git
288312 end
289313
290 context 'without a default' do
291 context 'when the variables is defined' do
292 it 'returns the variable' do
314 context "without a default" do
315 context "when the variables is defined" do
316 it "returns the variable" do
293317 expect(dsl.fetch(:scm)).to eq :git
294318 end
295319 end
296320
297 context 'when the variables is undefined' do
298 it 'returns nil' do
321 context "when the variables is undefined" do
322 it "returns nil" do
299323 expect(dsl.fetch(:source_control)).to be_nil
300324 end
301325 end
302326 end
303327
304 context 'with a default' do
305 context 'when the variables is defined' do
306 it 'returns the variable' do
328 context "with a default" do
329 context "when the variables is defined" do
330 it "returns the variable" do
307331 expect(dsl.fetch(:scm, :svn)).to eq :git
308332 end
309333 end
310334
311 context 'when the variables is undefined' do
312 it 'returns the default' do
335 context "when the variables is undefined" do
336 it "returns the default" do
313337 expect(dsl.fetch(:source_control, :svn)).to eq :svn
314338 end
315339 end
316340 end
317341
318 context 'with a block' do
319 context 'when the variables is defined' do
320 it 'returns the variable' do
342 context "with a block" do
343 context "when the variables is defined" do
344 it "returns the variable" do
321345 expect(dsl.fetch(:scm) { :svn }).to eq :git
322346 end
323347 end
324348
325 context 'when the variables is undefined' do
326 it 'calls the block' do
349 context "when the variables is undefined" do
350 it "calls the block" do
327351 expect(dsl.fetch(:source_control) { :svn }).to eq :svn
328352 end
329353 end
330354 end
331
332 end
333
334 describe 'asking for a variable' do
355 end
356
357 describe "asking for a variable" do
335358 before do
336359 dsl.ask(:scm, :svn)
337360 $stdout.stubs(:print)
338361 end
339362
340 context 'variable is provided' do
363 context "variable is provided" do
341364 before do
342 $stdin.expects(:gets).returns('git')
343 end
344
345 it 'sets the input as the variable' do
346 expect(dsl.fetch(:scm)).to eq 'git'
347 end
348 end
349
350 context 'variable is not provided' do
365 $stdin.expects(:gets).returns("git")
366 end
367
368 it "sets the input as the variable" do
369 expect(dsl.fetch(:scm)).to eq "git"
370 end
371 end
372
373 context "variable is not provided" do
351374 before do
352 $stdin.expects(:gets).returns('')
353 end
354
355 it 'sets the variable as the default' do
375 $stdin.expects(:gets).returns("")
376 end
377
378 it "sets the variable as the default" do
356379 expect(dsl.fetch(:scm)).to eq :svn
357380 end
358381 end
359382 end
360383
361 describe 'checking for presence' do
384 describe "checking for presence" do
362385 subject { dsl.any? :linked_files }
363386
364387 before do
365388 dsl.set(:linked_files, linked_files)
366389 end
367390
368 context 'variable is an non-empty array' do
391 context "variable is an non-empty array" do
369392 let(:linked_files) { %w{1} }
370393
371394 it { expect(subject).to be_truthy }
372395 end
373396
374 context 'variable is an empty array' do
397 context "variable is an empty array" do
375398 let(:linked_files) { [] }
376399 it { expect(subject).to be_falsey }
377400 end
378401
379 context 'variable exists, is not an array' do
402 context "variable exists, is not an array" do
380403 let(:linked_files) { stub }
381404 it { expect(subject).to be_truthy }
382405 end
383406
384 context 'variable is nil' do
407 context "variable is nil" do
385408 let(:linked_files) { nil }
386409 it { expect(subject).to be_falsey }
387410 end
388411 end
389412
390 describe 'configuration SSHKit' do
413 describe "configuration SSHKit" do
391414 let(:config) { SSHKit.config }
392415 let(:backend) { SSHKit.config.backend.config }
393416 let(:default_env) { { rails_env: :production } }
398421 dsl.set(:default_env, default_env)
399422 dsl.set(:pty, true)
400423 dsl.set(:connection_timeout, 10)
401 dsl.set(:ssh_options, {
402 keys: %w(/home/user/.ssh/id_rsa),
403 forward_agent: false,
404 auth_methods: %w(publickey password)
405 })
424 dsl.set(:ssh_options, keys: %w(/home/user/.ssh/id_rsa),
425 forward_agent: false,
426 auth_methods: %w(publickey password))
406427 dsl.configure_backend
407428 end
408429
409 it 'sets the output' do
430 it "sets the output" do
410431 expect(config.output).to be_a SSHKit::Formatter::Dot
411432 end
412433
413 it 'sets the output verbosity' do
434 it "sets the output verbosity" do
414435 expect(config.output_verbosity).to eq 0
415436 end
416437
417 it 'sets the default env' do
438 it "sets the default env" do
418439 expect(config.default_env).to eq default_env
419440 end
420441
421 it 'sets the backend pty' do
442 it "sets the backend pty" do
422443 expect(backend.pty).to be_truthy
423444 end
424445
425 it 'sets the backend connection timeout' do
446 it "sets the backend connection timeout" do
426447 expect(backend.connection_timeout).to eq 10
427448 end
428449
429 it 'sets the backend ssh_options' do
450 it "sets the backend ssh_options" do
430451 expect(backend.ssh_options[:keys]).to eq %w(/home/user/.ssh/id_rsa)
431452 expect(backend.ssh_options[:forward_agent]).to eq false
432453 expect(backend.ssh_options[:auth_methods]).to eq %w(publickey password)
433454 end
434
435 end
436
437 describe 'local_user' do
438 before do
439 dsl.set :local_user, -> { Etc.getlogin }
440 end
441
442 describe 'fetching local_user' do
443 subject { dsl.local_user }
444
445 context 'where a local_user is not set' do
446 before do
447 Etc.expects(:getlogin).returns('login')
448 end
449
450 it 'returns the login name' do
451 expect(subject.to_s).to eq 'login'
452 end
453 end
454
455 context 'where a local_user is set' do
456 before do
457 dsl.set(:local_user, -> { 'custom login' })
458 end
459
460 it 'returns the custom name' do
461 expect(subject.to_s).to eq 'custom login'
462 end
463 end
464 end
465 end
466
467 describe 'on()' do
468
469 before do
470 dsl.server 'example1.com', roles: %w{web}, active: true
471 dsl.server 'example2.com', roles: %w{web}
472 dsl.server 'example3.com', roles: %w{app web}, active: true
473 dsl.server 'example4.com', roles: %w{app}, primary: true
474 dsl.server 'example5.com', roles: %w{db}, no_release: true
475 @coordinator = mock('coordinator')
476 @coordinator.expects(:each).returns(nil)
477 ENV.delete 'ROLES'
478 ENV.delete 'HOSTS'
479
480 end
481
482 it 'filters by role from the :filter variable' do
483 hosts = dsl.roles(:web)
484 all = dsl.roles(:all)
485 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
486 dsl.set :filter, { role: 'web' }
487 dsl.on(all)
488 end
489
490 it 'filters by host and role from the :filter variable' do
491 all = dsl.roles(:all)
492 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
493 dsl.set :filter, { role: 'db', host: 'example3.com' }
494 dsl.on(all)
495 end
496
497 it 'filters from ENV[ROLES]' do
498 hosts = dsl.roles(:db)
499 all = dsl.roles(:all)
500 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
501 ENV['ROLES'] = 'db'
502 dsl.on(all)
503 end
504
505 it 'filters from ENV[HOSTS]' do
506 hosts = dsl.roles(:db)
507 all = dsl.roles(:all)
508 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
509 ENV['HOSTS'] = 'example5.com'
510 dsl.on(all)
511 end
512
513 it 'filters by ENV[HOSTS] && ENV[ROLES]' do
514 all = dsl.roles(:all)
515 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
516 ENV['HOSTS'] = 'example5.com'
517 ENV['ROLES'] = 'web'
518 dsl.on(all)
519 end
520
521 end
522
523 describe 'role_properties()' do
524
455 end
456
457 describe "on()" do
458 describe "when passed server objects" do
459 before do
460 dsl.server "example1.com", roles: %w{web}, active: true
461 dsl.server "example2.com", roles: %w{web}
462 dsl.server "example3.com", roles: %w{app web}, active: true
463 dsl.server "example4.com", roles: %w{app}, primary: true
464 dsl.server "example5.com", roles: %w{db}, no_release: true
465 @coordinator = mock("coordinator")
466 @coordinator.expects(:each).returns(nil)
467 ENV.delete "ROLES"
468 ENV.delete "HOSTS"
469 end
470
471 it "filters by role from the :filter variable" do
472 hosts = dsl.roles(:web)
473 all = dsl.roles(:all)
474 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
475 dsl.set :filter, role: "web"
476 dsl.on(all)
477 end
478
479 it "filters by host and role from the :filter variable" do
480 all = dsl.roles(:all)
481 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
482 dsl.set :filter, role: "db", host: "example3.com"
483 dsl.on(all)
484 end
485
486 it "filters by roles from the :filter variable" do
487 hosts = dsl.roles(:web)
488 all = dsl.roles(:all)
489 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
490 dsl.set :filter, roles: "web"
491 dsl.on(all)
492 end
493
494 it "filters by hosts and roles from the :filter variable" do
495 all = dsl.roles(:all)
496 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
497 dsl.set :filter, roles: "db", hosts: "example3.com"
498 dsl.on(all)
499 end
500
501 it "filters from ENV[ROLES]" do
502 hosts = dsl.roles(:db)
503 all = dsl.roles(:all)
504 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
505 ENV["ROLES"] = "db"
506 dsl.on(all)
507 end
508
509 it "filters from ENV[HOSTS]" do
510 hosts = dsl.roles(:db)
511 all = dsl.roles(:all)
512 SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
513 ENV["HOSTS"] = "example5.com"
514 dsl.on(all)
515 end
516
517 it "filters by ENV[HOSTS] && ENV[ROLES]" do
518 all = dsl.roles(:all)
519 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
520 ENV["HOSTS"] = "example5.com"
521 ENV["ROLES"] = "web"
522 dsl.on(all)
523 end
524 end
525
526 describe "when passed server literal names" do
527 before do
528 ENV.delete "ROLES"
529 ENV.delete "HOSTS"
530 @coordinator = mock("coordinator")
531 @coordinator.expects(:each).returns(nil)
532 end
533
534 it "selects nothing when a role filter is present" do
535 dsl.set :filter, role: "web"
536 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
537 dsl.on("my.server")
538 end
539
540 it "selects using the string when a host filter is present" do
541 dsl.set :filter, host: "server.local"
542 SSHKit::Coordinator.expects(:new).with(["server.local"]).returns(@coordinator)
543 dsl.on("server.local")
544 end
545
546 it "doesn't select when a host filter is present that doesn't match" do
547 dsl.set :filter, host: "ruby.local"
548 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
549 dsl.on("server.local")
550 end
551
552 it "selects nothing when a roles filter is present" do
553 dsl.set :filter, roles: "web"
554 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
555 dsl.on("my.server")
556 end
557
558 it "selects using the string when a hosts filter is present" do
559 dsl.set :filter, hosts: "server.local"
560 SSHKit::Coordinator.expects(:new).with(["server.local"]).returns(@coordinator)
561 dsl.on("server.local")
562 end
563
564 it "doesn't select when a hosts filter is present that doesn't match" do
565 dsl.set :filter, hosts: "ruby.local"
566 SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
567 dsl.on("server.local")
568 end
569 end
570 end
571
572 describe "role_properties()" do
525573 before do
526574 dsl.role :redis, %w[example1.com example2.com], redis: { port: 6379, type: :slave }
527 dsl.server 'example1.com', roles: %w{web}, active: true, web: { port: 80 }
528 dsl.server 'example2.com', roles: %w{web redis}, web: { port: 81 }, redis: { type: :master }
529 dsl.server 'example3.com', roles: %w{app}, primary: true
530 end
531
532 it 'retrieves properties for a single role as a set' do
575 dsl.server "example1.com", roles: %w{web}, active: true, web: { port: 80 }
576 dsl.server "example2.com", roles: %w{web redis}, web: { port: 81 }, redis: { type: :master }
577 dsl.server "example3.com", roles: %w{app}, primary: true
578 end
579
580 it "retrieves properties for a single role as a set" do
533581 rps = dsl.role_properties(:app)
534 expect(rps).to eq(Set[{ hostname: 'example3.com', role: :app}])
535 end
536
537 it 'retrieves properties for multiple roles as a set' do
582 expect(rps).to eq(Set[{ hostname: "example3.com", role: :app }])
583 end
584
585 it "retrieves properties for multiple roles as a set" do
538586 rps = dsl.role_properties(:app, :web)
539 expect(rps).to eq(Set[{ hostname: 'example3.com', role: :app},{ hostname: 'example1.com', role: :web, port: 80},{ hostname: 'example2.com', role: :web, port: 81}])
540 end
541
542 it 'yields the properties for a single role' do
543 recipient = mock('recipient')
544 recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
545 recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
587 expect(rps).to eq(Set[{ hostname: "example3.com", role: :app }, { hostname: "example1.com", role: :web, port: 80 }, { hostname: "example2.com", role: :web, port: 81 }])
588 end
589
590 it "yields the properties for a single role" do
591 recipient = mock("recipient")
592 recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
593 recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
546594 dsl.role_properties(:redis) do |host, role, props|
547595 recipient.doit(host, role, props)
548596 end
549597 end
550598
551 it 'yields the properties for multiple roles' do
552 recipient = mock('recipient')
553 recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
554 recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
555 recipient.expects(:doit).with('example3.com', :app, nil)
599 it "yields the properties for multiple roles" do
600 recipient = mock("recipient")
601 recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
602 recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
603 recipient.expects(:doit).with("example3.com", :app, nil)
556604 dsl.role_properties(:redis, :app) do |host, role, props|
557605 recipient.doit(host, role, props)
558606 end
559607 end
560608
561 it 'yields the merged properties for multiple roles' do
562 recipient = mock('recipient')
563 recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
564 recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
565 recipient.expects(:doit).with('example1.com', :web, { port: 80 })
566 recipient.expects(:doit).with('example2.com', :web, { port: 81 })
609 it "yields the merged properties for multiple roles" do
610 recipient = mock("recipient")
611 recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
612 recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
613 recipient.expects(:doit).with("example1.com", :web, port: 80)
614 recipient.expects(:doit).with("example2.com", :web, port: 81)
567615 dsl.role_properties(:redis, :web) do |host, role, props|
568616 recipient.doit(host, role, props)
569617 end
570618 end
571619
572 it 'honours a property filter before yielding' do
573 recipient = mock('recipient')
574 recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
575 recipient.expects(:doit).with('example1.com', :web, { port: 80 })
620 it "honours a property filter before yielding" do
621 recipient = mock("recipient")
622 recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
623 recipient.expects(:doit).with("example1.com", :web, port: 80)
576624 dsl.role_properties(:redis, :web, select: :active) do |host, role, props|
577625 recipient.doit(host, role, props)
578626 end
579627 end
580628 end
581
582629 end
0 require 'spec_helper'
1 require 'support/test_app'
2 require 'support/matchers'
0 require "spec_helper"
1 require "support/test_app"
2 require "support/matchers"
33
44 include TestApp
5
6
0 require 'spec_helper'
0 require "spec_helper"
11
22 describe Capistrano::Application do
3
43 it "provides a --trace option which enables SSHKit/NetSSH trace output"
54
65 it "provides a --format option which enables the choice of output formatting"
76
87 let(:help_output) do
9 out, _ = capture_io do
10 flags '--help', '-h'
8 out, _err = capture_io do
9 flags "--help", "-h"
1110 end
1211 out
1312 end
2322 end
2423
2524 it "overrides the rake method, but still prints the rake version" do
26 out, _ = capture_io do
27 flags '--version', '-V'
25 out, _err = capture_io do
26 flags "--version", "-V"
2827 end
2928 expect(out).to match(/\bCapistrano Version\b/)
3029 expect(out).to match(/\b#{Capistrano::VERSION}\b/)
3130 expect(out).to match(/\bRake Version\b/)
32 expect(out).to match(/\b#{RAKEVERSION}\b/)
31 expect(out).to match(/\b#{Rake::VERSION}\b/)
3332 end
3433
3534 it "overrides the rake method, and sets the sshkit_backend to SSHKit::Backend::Printer" do
36 out, _ = capture_io do
37 flags '--dry-run', '-n'
35 capture_io do
36 flags "--dry-run", "-n"
3837 end
3938 sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend)
4039 expect(sshkit_backend).to eq(SSHKit::Backend::Printer)
40 end
41
42 it "enables printing all config variables on command line parameter" do
43 capture_io do
44 flags "--print-config-variables", "-p"
45 end
46 expect(Capistrano::Configuration.fetch(:print_config_variables)).to be true
4147 end
4248
4349 def flags(*sets)
5056
5157 def command_line(*options)
5258 options.each { |opt| ARGV << opt }
53 def subject.exit(*args)
59 subject.define_singleton_method(:exit) do |*_args|
5460 throw(:system_exit, :exit)
5561 end
5662 subject.run
5864 end
5965
6066 def capture_io
61 require 'stringio'
67 require "stringio"
6268
63 orig_stdout, orig_stderr = $stdout, $stderr
64 captured_stdout, captured_stderr = StringIO.new, StringIO.new
65 $stdout, $stderr = captured_stdout, captured_stderr
69 orig_stdout = $stdout
70 orig_stderr = $stderr
71 captured_stdout = StringIO.new
72 captured_stderr = StringIO.new
73 $stdout = captured_stdout
74 $stderr = captured_stderr
6675
6776 yield
6877
7180 $stdout = orig_stdout
7281 $stderr = orig_stderr
7382 end
74
7583 end
0 require "spec_helper"
1
2 module Capistrano
3 class Configuration
4 describe EmptyFilter do
5 subject(:empty_filter) { EmptyFilter.new }
6
7 describe "#filter" do
8 let(:servers) { mock("servers") }
9
10 it "returns an empty array" do
11 expect(empty_filter.filter(servers)).to eq([])
12 end
13 end
14 end
15 end
16 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
33 class Configuration
4 describe Filter do
5 let(:available) do
6 [
7 Server.new("server1").add_roles(%i(web db)),
8 Server.new("server2").add_role(:web),
9 Server.new("server3").add_role(:redis),
10 Server.new("server4").add_role(:db),
11 Server.new("server5").add_role(:stageweb)
12 ]
13 end
414
5 describe Filter do
6 let(:available) { [ Server.new('server1').add_roles([:web,:db]),
7 Server.new('server2').add_role(:web),
8 Server.new('server3').add_role(:redis),
9 Server.new('server4').add_role(:db),
10 Server.new('server5').add_role(:stageweb) ] }
11
12 describe '#new' do
15 describe "#new" do
1316 it "won't create an invalid type of filter" do
14 expect {
15 f = Filter.new(:zarg)
16 }.to raise_error RuntimeError
17 expect do
18 Filter.new(:zarg)
19 end.to raise_error RuntimeError
1720 end
1821
19 it 'creates an empty host filter' do
20 expect(Filter.new(:host).filter(available)).to be_empty
22 context "with type :host" do
23 context "and no values" do
24 it "creates an EmptyFilter strategy" do
25 expect(Filter.new(:host).instance_variable_get(:@strategy)).to be_a(EmptyFilter)
26 end
27 end
28
29 context "and :all" do
30 it "creates an NullFilter strategy" do
31 expect(Filter.new(:host, :all).instance_variable_get(:@strategy)).to be_a(NullFilter)
32 end
33 end
34
35 context "and [:all]" do
36 it "creates an NullFilter strategy" do
37 expect(Filter.new(:host, [:all]).instance_variable_get(:@strategy)).to be_a(NullFilter)
38 end
39 end
40
41 context "and [:all]" do
42 it "creates an NullFilter strategy" do
43 expect(Filter.new(:host, "all").instance_variable_get(:@strategy)).to be_a(NullFilter)
44 end
45 end
2146 end
2247
23 it 'creates a null host filter' do
24 expect(Filter.new(:host, :all).filter(available)).to eq(available)
25 end
48 context "with type :role" do
49 context "and no values" do
50 it "creates an EmptyFilter strategy" do
51 expect(Filter.new(:role).instance_variable_get(:@strategy)).to be_a(EmptyFilter)
52 end
53 end
2654
27 it 'creates an empty role filter' do
28 expect(Filter.new(:role).filter(available)).to be_empty
29 end
55 context "and :all" do
56 it "creates an NullFilter strategy" do
57 expect(Filter.new(:role, :all).instance_variable_get(:@strategy)).to be_a(NullFilter)
58 end
59 end
3060
31 it 'creates a null role filter' do
32 expect(Filter.new(:role, :all).filter(available)).to eq(available)
33 end
61 context "and [:all]" do
62 it "creates an NullFilter strategy" do
63 expect(Filter.new(:role, [:all]).instance_variable_get(:@strategy)).to be_a(NullFilter)
64 end
65 end
3466
35 end
36
37 describe 'host filter' do
38 it 'works with a single server' do
39 set = Filter.new(:host, 'server1').filter(available.first)
40 expect(set.map(&:hostname)).to eq(%w{server1})
41 end
42 it 'returns all hosts matching a string' do
43 set = Filter.new(:host, 'server1').filter(available)
44 expect(set.map(&:hostname)).to eq(%w{server1})
45 end
46 it 'returns all hosts matching a comma-separated string' do
47 set = Filter.new(:host, 'server1,server3').filter(available)
48 expect(set.map(&:hostname)).to eq(%w{server1 server3})
49 end
50 it 'returns all hosts matching an array of strings' do
51 set = Filter.new(:host, %w{server1 server3}).filter(available)
52 expect(set.map(&:hostname)).to eq(%w{server1 server3})
53 end
54 it 'returns all hosts matching regexp' do
55 set = Filter.new(:host, 'server[13]$').filter(available)
56 expect(set.map(&:hostname)).to eq(%w{server1 server3})
57 end
58 it 'correctly identifies a regex with a comma in' do
59 set = Filter.new(:host, 'server\d{1,3}$').filter(available)
60 expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server4 server5})
67 context "and [:all]" do
68 it "creates an NullFilter strategy" do
69 expect(Filter.new(:role, "all").instance_variable_get(:@strategy)).to be_a(NullFilter)
70 end
71 end
6172 end
6273 end
6374
64 describe 'role filter' do
65 it 'returns all hosts' do
66 set = Filter.new(:role, [:all]).filter(available)
67 expect(set.size).to eq(available.size)
68 expect(set.first.hostname).to eq('server1')
75 describe "#filter" do
76 let(:strategy) { filter.instance_variable_get(:@strategy) }
77 let(:results) { mock("result") }
78
79 shared_examples "it calls #filter on its strategy" do
80 it "calls #filter on its strategy" do
81 strategy.expects(:filter).with(available).returns(results)
82 expect(filter.filter(available)).to eq(results)
83 end
6984 end
70 it 'returns hosts in a single string role' do
71 set = Filter.new(:role, 'web').filter(available)
72 expect(set.size).to eq(2)
73 expect(set.map(&:hostname)).to eq(%w{server1 server2})
85
86 context "for an empty filter" do
87 let(:filter) { Filter.new(:role) }
88 it_behaves_like "it calls #filter on its strategy"
7489 end
75 it 'returns hosts in a single role' do
76 set = Filter.new(:role, [:web]).filter(available)
77 expect(set.size).to eq(2)
78 expect(set.map(&:hostname)).to eq(%w{server1 server2})
90
91 context "for a null filter" do
92 let(:filter) { Filter.new(:role, :all) }
93 it_behaves_like "it calls #filter on its strategy"
7994 end
80 it 'returns hosts in multiple roles specified by a string' do
81 set = Filter.new(:role, 'web,db').filter(available)
82 expect(set.size).to eq(3)
83 expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
95
96 context "for a role filter" do
97 let(:filter) { Filter.new(:role, "web") }
98 it_behaves_like "it calls #filter on its strategy"
8499 end
85 it 'returns hosts in multiple roles' do
86 set = Filter.new(:role, [:web, :db]).filter(available)
87 expect(set.size).to eq(3)
88 expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
89 end
90 it 'returns only hosts for explicit roles' do
91 set = Filter.new(:role, [:web]).filter(available)
92 expect(set.size).to eq(2)
93 expect(set.map(&:hostname)).to eq(%w{server1 server2})
94 end
95 it 'returns hosts with regex role selection' do
96 set = Filter.new(:role, /red/).filter(available)
97 expect(set.map(&:hostname)).to eq(%w{server3})
98 end
99 it 'returns hosts with regex role selection using a string' do
100 set = Filter.new(:role, '/red|web/').filter(available)
101 expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server5})
102 end
103 it 'returns hosts with combination of string role and regex' do
104 set = Filter.new(:role, 'db,/red/').filter(available)
105 expect(set.map(&:hostname)).to eq(%w{server1 server3 server4})
100
101 context "for a host filter" do
102 let(:filter) { Filter.new(:host, "server1") }
103 it_behaves_like "it calls #filter on its strategy"
106104 end
107105 end
108106 end
0 require "spec_helper"
1
2 module Capistrano
3 class Configuration
4 describe HostFilter do
5 subject(:host_filter) { HostFilter.new(values) }
6
7 let(:available) do
8 [Server.new("server1"),
9 Server.new("server2"),
10 Server.new("server3"),
11 Server.new("server4"),
12 Server.new("server5")]
13 end
14
15 shared_examples "it filters hosts correctly" do |expected|
16 it "filters correctly" do
17 set = host_filter.filter(available)
18 expect(set.map(&:hostname)).to eq(expected)
19 end
20 end
21
22 describe "#filter" do
23 context "with a string" do
24 let(:values) { "server1" }
25 it_behaves_like "it filters hosts correctly", %w{server1}
26
27 context "and a single server" do
28 let(:available) { Server.new("server1") }
29 it_behaves_like "it filters hosts correctly", %w{server1}
30 end
31 end
32
33 context "with a comma separated string" do
34 let(:values) { "server1,server3" }
35 it_behaves_like "it filters hosts correctly", %w{server1 server3}
36 end
37
38 context "with an array of strings" do
39 let(:values) { %w{server1 server3} }
40 it_behaves_like "it filters hosts correctly", %w{server1 server3}
41 end
42
43 context "with mixed splittable and unsplittable strings" do
44 let(:values) { %w{server1 server2,server3} }
45 it_behaves_like "it filters hosts correctly", %w{server1 server2 server3}
46 end
47
48 context "with a regexp" do
49 let(:values) { "server[13]$" }
50 it_behaves_like "it filters hosts correctly", %w{server1 server3}
51 end
52
53 context "with a regexp with line boundaries" do
54 let(:values) { "^server" }
55 it_behaves_like "it filters hosts correctly", %w{server1 server2 server3 server4 server5}
56 end
57
58 context "with a regexp with a comma" do
59 let(:values) { 'server\d{1,3}$' }
60 it_behaves_like "it filters hosts correctly", %w{server1 server2 server3 server4 server5}
61 end
62
63 context "without number" do
64 let(:values) { "server" }
65 it_behaves_like "it filters hosts correctly", %w{}
66 end
67 end
68 end
69 end
70 end
0 require "spec_helper"
1
2 module Capistrano
3 class Configuration
4 describe NullFilter do
5 subject(:null_filter) { NullFilter.new }
6
7 describe "#filter" do
8 let(:servers) { mock("servers") }
9
10 it "returns the servers passed in as arguments" do
11 expect(null_filter.filter(servers)).to eq(servers)
12 end
13 end
14 end
15 end
16 end
0 require "spec_helper"
1 require "capistrano/plugin"
2 require "capistrano/scm/plugin"
3
4 module Capistrano
5 class Configuration
6 class ExamplePlugin < Capistrano::Plugin
7 def set_defaults
8 set_if_empty :example_variable, "foo"
9 end
10
11 def define_tasks
12 task :example
13 task :example_prerequisite
14 end
15
16 def register_hooks
17 before :example, :example_prerequisite
18 end
19 end
20
21 class ExampleSCMPlugin < Capistrano::SCM::Plugin
22 end
23
24 describe PluginInstaller do
25 include Capistrano::DSL
26
27 let(:installer) { PluginInstaller.new }
28 let(:options) { {} }
29 let(:plugin) { ExamplePlugin.new }
30
31 before do
32 installer.install(plugin, **options)
33 end
34
35 after do
36 Rake::Task.clear
37 Capistrano::Configuration.reset!
38 end
39
40 context "installing plugin" do
41 it "defines tasks" do
42 expect(Rake::Task[:example]).to_not be_nil
43 expect(Rake::Task[:example_prerequisite]).to_not be_nil
44 end
45
46 it "registers hooks" do
47 task = Rake::Task[:example]
48 expect(task.prerequisites).to eq([:example_prerequisite])
49 end
50
51 it "sets defaults when load:defaults is invoked" do
52 expect(fetch(:example_variable)).to be_nil
53 invoke "load:defaults"
54 expect(fetch(:example_variable)).to eq("foo")
55 end
56
57 it "doesn't say an SCM is installed" do
58 expect(installer.scm_installed?).to be_falsey
59 end
60 end
61
62 context "installing plugin class" do
63 let(:plugin) { ExamplePlugin }
64
65 it "defines tasks" do
66 expect(Rake::Task[:example]).to_not be_nil
67 expect(Rake::Task[:example_prerequisite]).to_not be_nil
68 end
69 end
70
71 context "installing plugin without hooks" do
72 let(:options) { { load_hooks: false } }
73
74 it "doesn't register hooks" do
75 task = Rake::Task[:example]
76 expect(task.prerequisites).to be_empty
77 end
78 end
79
80 context "installing plugin and loading immediately" do
81 let(:options) { { load_immediately: true } }
82
83 it "sets defaults immediately" do
84 expect(fetch(:example_variable)).to eq("foo")
85 end
86 end
87
88 context "installing an SCM plugin" do
89 let(:plugin) { ExampleSCMPlugin }
90
91 it "says an SCM is installed" do
92 expect(installer.scm_installed?).to be_truthy
93 end
94 end
95 end
96 end
97 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
33 class Configuration
4
54 describe Question do
6
75 let(:question) { Question.new(key, default, options) }
86 let(:question_without_echo) { Question.new(key, default, echo: false) }
7 let(:question_without_default) { Question.new(key, nil) }
98 let(:default) { :default }
109 let(:key) { :branch }
1110 let(:options) { nil }
1211
13 describe '.new' do
14 it 'takes a key, default, options' do
12 describe ".new" do
13 it "takes a key, default, options" do
1514 question
1615 end
1716 end
1817
19 describe '#call' do
20 context 'value is entered' do
21 let(:branch) { 'branch' }
18 describe "#call" do
19 context "value is entered" do
20 let(:branch) { "branch" }
2221
23 before do
24 $stdout.expects(:print).with('Please enter branch (default): ')
25 end
26
27 it 'returns the echoed value' do
22 it "returns the echoed value" do
23 $stdout.expects(:print).with("Please enter branch (default): ")
2824 $stdin.expects(:gets).returns(branch)
2925 $stdin.expects(:noecho).never
3026
3127 expect(question.call).to eq(branch)
3228 end
3329
34 it 'returns the value but does not echo it' do
30 it "returns the value but does not echo it" do
31 $stdout.expects(:print).with("Please enter branch (default): ")
3532 $stdin.expects(:noecho).returns(branch)
3633 $stdout.expects(:print).with("\n")
3734
3835 expect(question_without_echo.call).to eq(branch)
3936 end
37
38 it "returns the value but has no default between parenthesis" do
39 $stdout.expects(:print).with("Please enter branch: ")
40 $stdin.expects(:gets).returns(branch)
41 $stdin.expects(:noecho).never
42
43 expect(question_without_default.call).to eq(branch)
44 end
4045 end
4146
42 context 'value is not entered' do
47 context "value is not entered" do
4348 let(:branch) { default }
4449
4550 before do
46 $stdout.expects(:print).with('Please enter branch (default): ')
47 $stdin.expects(:gets).returns('')
51 $stdout.expects(:print).with("Please enter branch (default): ")
52 $stdin.expects(:gets).returns("")
4853 end
4954
50
51 it 'returns the default as the value' do
55 it "returns the default as the value" do
5256 expect(question.call).to eq(branch)
5357 end
5458 end
5559 end
5660 end
57
5861 end
5962 end
0 require "spec_helper"
1
2 module Capistrano
3 class Configuration
4 describe RoleFilter do
5 subject(:role_filter) { RoleFilter.new(values) }
6
7 let(:available) do
8 [
9 Server.new("server1").add_roles(%i(web db)),
10 Server.new("server2").add_role(:web),
11 Server.new("server3").add_role(:redis),
12 Server.new("server4").add_role(:db),
13 Server.new("server5").add_role(:stageweb),
14 Server.new("server6").add_role(:"db.new")
15 ]
16 end
17
18 shared_examples "it filters roles correctly" do |expected_size, expected|
19 it "filters correctly" do
20 set = role_filter.filter(available)
21 expect(set.size).to eq(expected_size)
22 expect(set.map(&:hostname)).to eq(expected)
23 end
24 end
25
26 describe "#filter" do
27 context "with a single role string" do
28 let(:values) { "web" }
29 it_behaves_like "it filters roles correctly", 2, %w{server1 server2}
30 end
31
32 context "with a single role" do
33 let(:values) { [:web] }
34 it_behaves_like "it filters roles correctly", 2, %w{server1 server2}
35 end
36
37 context "with multiple roles in a string" do
38 let(:values) { "web,db" }
39 it_behaves_like "it filters roles correctly", 3, %w{server1 server2 server4}
40 end
41
42 context "with multiple roles" do
43 let(:values) { %i(web db) }
44 it_behaves_like "it filters roles correctly", 3, %w{server1 server2 server4}
45 end
46
47 context "with a regex" do
48 let(:values) { /red/ }
49 it_behaves_like "it filters roles correctly", 1, %w{server3}
50 end
51
52 context "with a regex string" do
53 let(:values) { "/red|web/" }
54 it_behaves_like "it filters roles correctly", 4, %w{server1 server2 server3 server5}
55 end
56
57 context "with both a string and regex" do
58 let(:values) { "db,/red/" }
59 it_behaves_like "it filters roles correctly", 3, %w{server1 server3 server4}
60 end
61
62 context "with a dot wildcard" do
63 let(:values) { "db.*" }
64 it_behaves_like "it filters roles correctly", 0, %w{}
65 end
66
67 context "with a dot" do
68 let(:values) { "db.new" }
69 it_behaves_like "it filters roles correctly", 1, %w{server6}
70 end
71
72 context "with a dot wildcard regex" do
73 let(:values) { "/db.*/" }
74 it_behaves_like "it filters roles correctly", 3, %w{server1 server4 server6}
75 end
76 end
77 end
78 end
79 end
0 require "spec_helper"
1
2 module Capistrano
3 class Configuration
4 describe SCMResolver do
5 include Capistrano::DSL
6
7 let(:resolver) { SCMResolver.new }
8
9 before do
10 Rake::Task.define_task("deploy:check")
11 Rake::Task.define_task("deploy:new_release_path")
12 Rake::Task.define_task("deploy:set_current_revision")
13 set :scm, SCMResolver::DEFAULT_GIT
14 end
15
16 after do
17 Rake::Task.clear
18 Capistrano::Configuration.reset!
19 end
20
21 context "default scm, no plugin installed" do
22 it "emits a warning" do
23 expect { resolver.resolve }.to output(/will not load the git scm/i).to_stderr
24 end
25
26 it "activates the git scm" do
27 resolver.resolve
28 expect(Rake::Task["git:wrapper"]).not_to be_nil
29 end
30
31 it "sets :scm to :git" do
32 resolver.resolve
33 expect(fetch(:scm)).to eq(:git)
34 end
35 end
36
37 context "default scm, git plugin installed" do
38 before do
39 install_plugin Capistrano::SCM::Git
40 end
41
42 it "emits no warning" do
43 expect { resolver.resolve }.not_to output.to_stderr
44 end
45
46 it "deletes :scm" do
47 resolver.resolve
48 expect(fetch(:scm)).to be_nil
49 end
50 end
51 end
52 end
53 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
33 class Configuration
44 describe Server do
5 let(:server) { Server.new('root@hostname:1234') }
6
7 describe 'adding a role' do
5 let(:server) { Server.new("root@hostname:1234") }
6
7 describe "adding a role" do
88 subject { server.add_role(:test) }
9 it 'adds the role' do
10 expect{subject}.to change{server.roles.size}.from(0).to(1)
11 end
12 end
13
14 describe 'adding roles' do
15 subject { server.add_roles([:things, :stuff]) }
16 it 'adds the roles' do
17 expect{subject}.to change{server.roles.size}.from(0).to(2)
18 end
19 end
20
21
22 describe 'checking roles' do
9 it "adds the role" do
10 expect { subject }.to change { server.roles.size }.from(0).to(1)
11 end
12 end
13
14 describe "adding roles" do
15 subject { server.add_roles(%i(things stuff)) }
16 it "adds the roles" do
17 expect { subject }.to change { server.roles.size }.from(0).to(2)
18 end
19 end
20
21 describe "checking roles" do
2322 subject { server.has_role?(:test) }
2423
2524 before do
2625 server.add_role(:test)
2726 end
2827
29 it 'adds the role' do
28 it "adds the role" do
3029 expect(subject).to be_truthy
3130 end
3231 end
3332
34 describe 'comparing identity' do
33 describe "comparing identity" do
3534 subject { server.hostname == Server[hostname].hostname }
3635
37 context 'with the same user, hostname and port' do
38 let(:hostname) { 'root@hostname:1234' }
36 context "with the same user, hostname and port" do
37 let(:hostname) { "root@hostname:1234" }
3938 it { expect(subject).to be_truthy }
4039 end
4140
42 context 'with a different user' do
43 let(:hostname) { 'deployer@hostname:1234' }
41 context "with a different user" do
42 let(:hostname) { "deployer@hostname:1234" }
4443 it { expect(subject).to be_truthy }
4544 end
4645
47 context 'with a different port' do
48 let(:hostname) { 'root@hostname:5678' }
46 context "with a different port" do
47 let(:hostname) { "root@hostname:5678" }
4948 it { expect(subject).to be_truthy }
5049 end
5150
52 context 'with a different hostname' do
53 let(:hostname) { 'root@otherserver:1234' }
51 context "with a different hostname" do
52 let(:hostname) { "root@otherserver:1234" }
5453 it { expect(subject).to be_falsey }
5554 end
5655 end
5756
58 describe 'identifying as primary' do
57 describe "identifying as primary" do
5958 subject { server.primary }
60 context 'server is primary' do
59 context "server is primary" do
6160 before do
6261 server.set(:primary, true)
6362 end
64 it 'returns self' do
63 it "returns self" do
6564 expect(subject).to eq server
6665 end
6766 end
6867
69 context 'server is not primary' do
70 it 'is falesy' do
68 context "server is not primary" do
69 it "is falesy" do
7170 expect(subject).to be_falsey
7271 end
7372 end
7473 end
7574
76 describe 'assigning properties' do
77
75 describe "assigning properties" do
7876 before do
7977 server.with(properties)
8078 end
8179
82 context 'properties contains roles' do
83 let(:properties) { {roles: [:clouds]} }
84
85 it 'adds the roles' do
80 context "properties contains roles" do
81 let(:properties) { { roles: [:clouds] } }
82
83 it "adds the roles" do
8684 expect(server.roles.first).to eq :clouds
8785 end
8886 end
8987
90 context 'properties contains user' do
91 let(:properties) { {user: 'tomc'} }
92
93 it 'sets the user' do
94 expect(server.user).to eq 'tomc'
95 end
96
97 it 'sets the netssh_options user' do
98 expect(server.netssh_options[:user]).to eq 'tomc'
99 end
100 end
101
102 context 'properties contains port' do
103 let(:properties) { {port: 2222} }
104
105 it 'sets the port' do
88 context "properties contains user" do
89 let(:properties) { { user: "tomc" } }
90
91 it "sets the user" do
92 expect(server.user).to eq "tomc"
93 end
94
95 it "sets the netssh_options user" do
96 expect(server.netssh_options[:user]).to eq "tomc"
97 end
98 end
99
100 context "properties contains port" do
101 let(:properties) { { port: 2222 } }
102
103 it "sets the port" do
106104 expect(server.port).to eq 2222
107105 end
108106 end
109107
110 context 'properties contains key' do
111 let(:properties) { {key: '/key'} }
112
113 it 'adds the key' do
114 expect(server.keys).to include '/key'
115 end
116 end
117
118 context 'properties contains password' do
119 let(:properties) { {password: 'supersecret'} }
120
121 it 'adds the key' do
122 expect(server.password).to eq 'supersecret'
123 end
124 end
125
126 context 'new properties' do
108 context "properties contains key" do
109 let(:properties) { { key: "/key" } }
110
111 it "adds the key" do
112 expect(server.keys).to include "/key"
113 end
114 end
115
116 context "properties contains password" do
117 let(:properties) { { password: "supersecret" } }
118
119 it "adds the key" do
120 expect(server.password).to eq "supersecret"
121 end
122 end
123
124 context "new properties" do
127125 let(:properties) { { webscales: 5 } }
128126
129 it 'adds the properties' do
127 it "adds the properties" do
130128 expect(server.properties.webscales).to eq 5
131129 end
132130 end
133131
134 context 'existing properties' do
132 context "existing properties" do
135133 let(:properties) { { webscales: 6 } }
136134
137 it 'keeps the existing properties' do
135 it "keeps the existing properties" do
138136 expect(server.properties.webscales).to eq 6
139137 server.properties.webscales = 5
140138 expect(server.properties.webscales).to eq 5
142140 end
143141 end
144142
145 describe '#include?' do
143 describe "#include?" do
146144 let(:options) { {} }
147145
148146 subject { server.select?(options) }
151149 server.properties.active = true
152150 end
153151
154 context 'options are empty' do
152 context "options are empty" do
155153 it { expect(subject).to be_truthy }
156154 end
157155
158 context 'value is a symbol' do
159 context 'value matches server property' do
160
161 context 'with :filter' do
162 let(:options) { { filter: :active }}
163 it { expect(subject).to be_truthy }
164 end
165
166 context 'with :select' do
167 let(:options) { { select: :active }}
168 it { expect(subject).to be_truthy }
169 end
170
171 context 'with :exclude' do
172 let(:options) { { exclude: :active }}
173 it { expect(subject).to be_falsey }
174 end
175 end
176
177 context 'value does not match server properly' do
178 context 'with :active true' do
179 let(:options) { { active: true }}
180 it { expect(subject).to be_truthy }
181 end
182
183 context 'with :active false' do
184 let(:options) { { active: false }}
185 it { expect(subject).to be_falsey }
186 end
187 end
188
189 context 'value does not match server properly' do
190 context 'with :filter' do
191 let(:options) { { filter: :inactive }}
192 it { expect(subject).to be_falsey }
193 end
194
195 context 'with :select' do
196 let(:options) { { select: :inactive }}
197 it { expect(subject).to be_falsey }
198 end
199
200 context 'with :exclude' do
201 let(:options) { { exclude: :inactive }}
202 it { expect(subject).to be_truthy }
203 end
204 end
205 end
206
207 context 'key is a property' do
208 context 'with :active true' do
209 let(:options) { { active: true }}
156 context "value is a symbol" do
157 context "value matches server property" do
158 context "with :filter" do
159 let(:options) { { filter: :active } }
160 it { expect(subject).to be_truthy }
161 end
162
163 context "with :select" do
164 let(:options) { { select: :active } }
165 it { expect(subject).to be_truthy }
166 end
167
168 context "with :exclude" do
169 let(:options) { { exclude: :active } }
170 it { expect(subject).to be_falsey }
171 end
172 end
173
174 context "value does not match server properly" do
175 context "with :active true" do
176 let(:options) { { active: true } }
177 it { expect(subject).to be_truthy }
178 end
179
180 context "with :active false" do
181 let(:options) { { active: false } }
182 it { expect(subject).to be_falsey }
183 end
184 end
185
186 context "value does not match server properly" do
187 context "with :filter" do
188 let(:options) { { filter: :inactive } }
189 it { expect(subject).to be_falsey }
190 end
191
192 context "with :select" do
193 let(:options) { { select: :inactive } }
194 it { expect(subject).to be_falsey }
195 end
196
197 context "with :exclude" do
198 let(:options) { { exclude: :inactive } }
199 it { expect(subject).to be_truthy }
200 end
201 end
202 end
203
204 context "key is a property" do
205 context "with :active true" do
206 let(:options) { { active: true } }
210207 it { expect(subject).to be_truthy }
211208 end
212209
213 context 'with :active false' do
214 let(:options) { { active: false }}
210 context "with :active false" do
211 let(:options) { { active: false } }
215212 it { expect(subject).to be_falsey }
216213 end
217214 end
218215
219 context 'value is a proc' do
220 context 'value matches server property' do
221
222 context 'with :filter' do
216 context "value is a proc" do
217 context "value matches server property" do
218 context "with :filter" do
223219 let(:options) { { filter: ->(s) { s.properties.active } } }
224220 it { expect(subject).to be_truthy }
225221 end
226222
227 context 'with :select' do
223 context "with :select" do
228224 let(:options) { { select: ->(s) { s.properties.active } } }
229225 it { expect(subject).to be_truthy }
230226 end
231227
232 context 'with :exclude' do
228 context "with :exclude" do
233229 let(:options) { { exclude: ->(s) { s.properties.active } } }
234230 it { expect(subject).to be_falsey }
235231 end
236
237 end
238
239 context 'value does not match server properly' do
240 context 'with :filter' do
232 end
233
234 context "value does not match server properly" do
235 context "with :filter" do
241236 let(:options) { { filter: ->(s) { s.properties.inactive } } }
242237 it { expect(subject).to be_falsey }
243238 end
244239
245 context 'with :select' do
240 context "with :select" do
246241 let(:options) { { select: ->(s) { s.properties.inactive } } }
247242 it { expect(subject).to be_falsey }
248243 end
249244
250 context 'with :exclude' do
245 context "with :exclude" do
251246 let(:options) { { exclude: ->(s) { s.properties.inactive } } }
252247 it { expect(subject).to be_truthy }
253248 end
254
255 end
256 end
257
258 end
259
260 describe 'assign ssh_options' do
261 let(:server) { Server.new('user_name@hostname') }
262
263 context 'defaults' do
264 it 'forward agent' do
249 end
250 end
251 end
252
253 describe "assign ssh_options" do
254 let(:server) { Server.new("user_name@hostname") }
255
256 context "defaults" do
257 it "forward agent" do
265258 expect(server.netssh_options[:forward_agent]).to eq true
266259 end
267 it 'contains user' do
268 expect(server.netssh_options[:user]).to eq 'user_name'
269 end
270 end
271
272 context 'custom' do
260 it "contains user" do
261 expect(server.netssh_options[:user]).to eq "user_name"
262 end
263 end
264
265 context "custom" do
273266 let(:properties) do
274267 { ssh_options: {
275 user: 'another_user',
268 user: "another_user",
276269 keys: %w(/home/another_user/.ssh/id_rsa),
277270 forward_agent: false,
278 auth_methods: %w(publickey password) } }
271 auth_methods: %w(publickey password)
272 } }
279273 end
280274
281275 before do
282276 server.with(properties)
283277 end
284278
285 it 'not forward agent' do
279 it "not forward agent" do
286280 expect(server.netssh_options[:forward_agent]).to eq false
287281 end
288 it 'contains correct user' do
289 expect(server.netssh_options[:user]).to eq 'another_user'
290 end
291 it 'does not affect server user in host' do
292 expect(server.user).to eq 'user_name'
293 end
294 it 'contains keys' do
282 it "contains correct user" do
283 expect(server.netssh_options[:user]).to eq "another_user"
284 end
285 it "does not affect server user in host" do
286 expect(server.user).to eq "user_name"
287 end
288 it "contains keys" do
295289 expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa)
296290 end
297 it 'contains auth_methods' do
291 it "contains auth_methods" do
298292 expect(server.netssh_options[:auth_methods]).to eq %w(publickey password)
299293 end
300294 end
301
302295 end
303296
304297 describe ".[]" do
305 it 'creates a server if its argument is not already a server' do
306 expect(Server['hostname:1234']).to be_a Server
307 end
308
309 it 'returns its argument if it is already a server' do
298 it "creates a server if its argument is not already a server" do
299 expect(Server["hostname:1234"]).to be_a Server
300 end
301
302 it "returns its argument if it is already a server" do
310303 expect(Server[server]).to be server
311304 end
312305 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
33 class Configuration
44 describe Servers do
55 let(:servers) { Servers.new }
66
7 describe 'adding a role' do
8
9 it 'adds two new server instances' do
10 expect{servers.add_role(:app, %w{1 2})}.
11 to change{servers.count}.from(0).to(2)
12 end
13
14 it 'handles de-duplification within roles' do
7 describe "adding a role" do
8 it "adds two new server instances" do
9 expect { servers.add_role(:app, %w{1 2}) }
10 .to change { servers.count }.from(0).to(2)
11 end
12
13 it "handles de-duplification within roles" do
1514 servers.add_role(:app, %w{1})
1615 servers.add_role(:app, %w{1})
1716 expect(servers.count).to eq 1
1817 end
1918
20 it 'handles de-duplification within roles with users' do
21 servers.add_role(:app, %w{1}, user: 'nick')
22 servers.add_role(:app, %w{1}, user: 'fred')
19 it "handles de-duplification within roles with users" do
20 servers.add_role(:app, %w{1}, user: "nick")
21 servers.add_role(:app, %w{1}, user: "fred")
2322 expect(servers.count).to eq 1
2423 end
2524
26 it 'accepts instances of server objects' do
27 servers.add_role(:app, [Capistrano::Configuration::Server.new('example.net'), 'example.com'])
25 it "accepts instances of server objects" do
26 servers.add_role(:app, [Capistrano::Configuration::Server.new("example.net"), "example.com"])
2827 expect(servers.roles_for([:app]).length).to eq 2
2928 end
3029
31 it 'accepts non-enumerable types' do
32 servers.add_role(:app, '1')
30 it "accepts non-enumerable types" do
31 servers.add_role(:app, "1")
3332 expect(servers.roles_for([:app]).count).to eq 1
3433 end
3534
36 it 'creates distinct server properties' do
37 servers.add_role(:db, %w{1 2}, db: { port: 1234 } )
38 servers.add_host('1', db: { master: true })
35 it "creates distinct server properties" do
36 servers.add_role(:db, %w{1 2}, db: { port: 1234 })
37 servers.add_host("1", db: { master: true })
3938 expect(servers.count).to eq(2)
4039 expect(servers.roles_for([:db]).count).to eq 2
41 expect(servers.find(){|s| s.hostname == '1'}.properties.db).to eq({ port: 1234, master: true })
42 expect(servers.find(){|s| s.hostname == '2'}.properties.db).to eq({ port: 1234 })
43 end
44
45 end
46
47 describe 'adding a role to an existing server' do
40 expect(servers.find { |s| s.hostname == "1" }.properties.db).to eq(port: 1234, master: true)
41 expect(servers.find { |s| s.hostname == "2" }.properties.db).to eq(port: 1234)
42 end
43 end
44
45 describe "adding a role to an existing server" do
4846 before do
4947 servers.add_role(:web, %w{1 2})
5048 servers.add_role(:app, %w{1 2})
5149 end
5250
53 it 'adds new roles to existing servers' do
51 it "adds new roles to existing servers" do
5452 expect(servers.count).to eq 2
5553 end
56
57 end
58
59 describe 'collecting server roles' do
54 end
55
56 describe "collecting server roles" do
6057 let(:app) { Set.new([:app]) }
61 let(:web_app) { Set.new([:web, :app]) }
58 let(:web_app) { Set.new(%i(web app)) }
6259 let(:web) { Set.new([:web]) }
6360
6461 before do
6663 servers.add_role(:web, %w{2 3 4})
6764 end
6865
69 it 'returns an array of the roles' do
66 it "returns an array of the roles" do
7067 expect(servers.roles_for([:app]).collect(&:roles)).to eq [app, web_app, web_app]
7168 expect(servers.roles_for([:web]).collect(&:roles)).to eq [web_app, web_app, web]
7269 end
7370 end
7471
75 describe 'finding the primary server' do
72 describe "finding the primary server" do
7673 after do
7774 Configuration.reset!
7875 end
79 it 'takes the first server if none have the primary property' do
80 servers.add_role(:app, %w{1 2})
81 expect(servers.fetch_primary(:app).hostname).to eq('1')
82 end
83
84 it 'takes the first server with the primary have the primary flag' do
85 servers.add_role(:app, %w{1 2})
86 servers.add_host('2', primary: true)
87 expect(servers.fetch_primary(:app).hostname).to eq('2')
88 end
89
90 it 'ignores any on_filters' do
91 Configuration.env.set :filter, { host: '1'}
92 servers.add_role(:app, %w{1 2})
93 servers.add_host('2', primary: true)
94 expect(servers.fetch_primary(:app).hostname).to eq('2')
95 end
96 end
97
98 describe 'fetching servers' do
76 it "takes the first server if none have the primary property" do
77 servers.add_role(:app, %w{1 2})
78 expect(servers.fetch_primary(:app).hostname).to eq("1")
79 end
80
81 it "takes the first server with the primary have the primary flag" do
82 servers.add_role(:app, %w{1 2})
83 servers.add_host("2", primary: true)
84 expect(servers.fetch_primary(:app).hostname).to eq("2")
85 end
86
87 it "ignores any on_filters" do
88 Configuration.env.set :filter, host: "1"
89 servers.add_role(:app, %w{1 2})
90 servers.add_host("2", primary: true)
91 expect(servers.fetch_primary(:app).hostname).to eq("2")
92 end
93 end
94
95 describe "fetching servers" do
9996 before do
10097 servers.add_role(:app, %w{1 2})
10198 servers.add_role(:web, %w{2 3})
10299 end
103100
104 it 'returns the correct app servers' do
101 it "returns the correct app servers" do
105102 expect(servers.roles_for([:app]).map(&:hostname)).to eq %w{1 2}
106103 end
107104
108 it 'returns the correct web servers' do
105 it "returns the correct web servers" do
109106 expect(servers.roles_for([:web]).map(&:hostname)).to eq %w{2 3}
110107 end
111108
112 it 'returns the correct app and web servers' do
113 expect(servers.roles_for([:app, :web]).map(&:hostname)).to eq %w{1 2 3}
114 end
115
116 it 'returns all servers' do
109 it "returns the correct app and web servers" do
110 expect(servers.roles_for(%i(app web)).map(&:hostname)).to eq %w{1 2 3}
111 end
112
113 it "returns all servers" do
117114 expect(servers.roles_for([:all]).map(&:hostname)).to eq %w{1 2 3}
118115 end
119116 end
120117
121 describe 'adding a server' do
122
123 before do
124 servers.add_host('1', roles: [:app, 'web'], test: :value)
125 end
126
127 it 'can create a server with properties' do
128 expect(servers.roles_for([:app]).first.hostname).to eq '1'
129 expect(servers.roles_for([:web]).first.hostname).to eq '1'
118 describe "adding a server" do
119 before do
120 servers.add_host("1", roles: [:app, "web"], test: :value)
121 end
122
123 it "can create a server with properties" do
124 expect(servers.roles_for([:app]).first.hostname).to eq "1"
125 expect(servers.roles_for([:web]).first.hostname).to eq "1"
130126 expect(servers.roles_for([:all]).first.properties.test).to eq :value
131127 expect(servers.roles_for([:all]).first.properties.keys).to eq [:test]
132128 end
133129
134 it 'can accept multiple servers with the same hostname but different ports or users' do
135 servers.add_host('1', roles: [:app, 'web'], test: :value, port: 12)
136 servers.add_host('1', roles: [:app, 'web'], test: :value, port: 34)
137 servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root')
138 servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer')
139 servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root', port: 34)
140 servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 34)
141 servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 56)
142 expect(servers.count).to eq(1)
130 it "can accept multiple servers with the same hostname but different ports or users" do
131 servers.add_host("1", roles: [:app, "web"], test: :value, port: 12)
132 expect(servers.count).to eq(2)
133 servers.add_host("1", roles: [:app, "web"], test: :value, port: 34)
134 servers.add_host("1", roles: [:app, "web"], test: :value, user: "root")
135 servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer")
136 servers.add_host("1", roles: [:app, "web"], test: :value, user: "root", port: 34)
137 servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer", port: 34)
138 servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer", port: 56)
139 expect(servers.count).to eq(4)
143140 end
144141
145142 describe "with a :user property" do
146
147 it 'sets the server ssh username' do
148 servers.add_host('1', roles: [:app, 'web'], user: 'nick')
143 it "sets the server ssh username" do
144 servers.add_host("1", roles: [:app, "web"], user: "nick")
149145 expect(servers.count).to eq(1)
150 expect(servers.roles_for([:all]).first.user).to eq 'nick'
151 end
152
153 it 'overwrites the value of a user specified in the hostname' do
154 servers.add_host('brian@1', roles: [:app, 'web'], user: 'nick')
146 expect(servers.roles_for([:all]).first.user).to eq "nick"
147 end
148
149 it "overwrites the value of a user specified in the hostname" do
150 servers.add_host("brian@1", roles: [:app, "web"], user: "nick")
155151 expect(servers.count).to eq(1)
156 expect(servers.roles_for([:all]).first.user).to eq 'nick'
157 end
158
159 end
160
161 it 'overwrites the value of a previously defined scalar property' do
162 servers.add_host('1', roles: [:app, 'web'], test: :volatile)
152 expect(servers.roles_for([:all]).first.user).to eq "nick"
153 end
154 end
155
156 it "overwrites the value of a previously defined scalar property" do
157 servers.add_host("1", roles: [:app, "web"], test: :volatile)
163158 expect(servers.count).to eq(1)
164159 expect(servers.roles_for([:all]).first.properties.test).to eq :volatile
165160 end
166161
167 it 'merges previously defined hash properties' do
168 servers.add_host('1', roles: [:b], db: { port: 1234 })
169 servers.add_host('1', roles: [:b], db: { master: true })
162 it "merges previously defined hash properties" do
163 servers.add_host("1", roles: [:b], db: { port: 1234 })
164 servers.add_host("1", roles: [:b], db: { master: true })
170165 expect(servers.count).to eq(1)
171 expect(servers.roles_for([:b]).first.properties.db).to eq({ port: 1234, master: true })
172 end
173
174 it 'concatenates previously defined array properties' do
175 servers.add_host('1', roles: [:b], steps: [1,3,5])
176 servers.add_host('1', roles: [:b], steps: [1,9])
166 expect(servers.roles_for([:b]).first.properties.db).to eq(port: 1234, master: true)
167 end
168
169 it "concatenates previously defined array properties" do
170 servers.add_host("1", roles: [:b], steps: [1, 3, 5])
171 servers.add_host("1", roles: [:b], steps: [1, 9])
177172 expect(servers.count).to eq(1)
178 expect(servers.roles_for([:b]).first.properties.steps).to eq([1,3,5,1,9])
179 end
180
181 it 'merges previously defined set properties' do
182 servers.add_host('1', roles: [:b], endpoints: Set[123,333])
183 servers.add_host('1', roles: [:b], endpoints: Set[222,333])
173 expect(servers.roles_for([:b]).first.properties.steps).to eq([1, 3, 5, 1, 9])
174 end
175
176 it "merges previously defined set properties" do
177 servers.add_host("1", roles: [:b], endpoints: Set[123, 333])
178 servers.add_host("1", roles: [:b], endpoints: Set[222, 333])
184179 expect(servers.count).to eq(1)
185 expect(servers.roles_for([:b]).first.properties.endpoints).to eq(Set[123,222,333])
186 end
187
188 it 'adds array property value only ones for a new host' do
189 servers.add_host('2', roles: [:array_test], array_property: [1,2])
190 expect(servers.roles_for([:array_test]).first.properties.array_property).to eq [1,2]
191 end
192
193 it 'updates roles when custom user defined' do
194 servers.add_host('1', roles: ['foo'], user: 'custom')
195 servers.add_host('1', roles: ['bar'], user: 'custom')
196 expect(servers.roles_for([:foo]).first.hostname).to eq '1'
197 expect(servers.roles_for([:bar]).first.hostname).to eq '1'
198 end
199
200 it 'updates roles when custom port defined' do
201 servers.add_host('1', roles: ['foo'], port: 1234)
202 servers.add_host('1', roles: ['bar'], port: 1234)
203 expect(servers.roles_for([:foo]).first.hostname).to eq '1'
204 expect(servers.roles_for([:bar]).first.hostname).to eq '1'
205 end
206
207 end
208
209 describe 'selecting roles' do
210
211 before do
212 servers.add_host('1', roles: :app, active: true)
213 servers.add_host('2', roles: :app)
214 end
215
216 it 'is empty if the filter would remove all matching hosts' do
180 expect(servers.roles_for([:b]).first.properties.endpoints).to eq(Set[123, 222, 333])
181 end
182
183 it "adds array property value only ones for a new host" do
184 servers.add_host("2", roles: [:array_test], array_property: [1, 2])
185 expect(servers.roles_for([:array_test]).first.properties.array_property).to eq [1, 2]
186 end
187
188 it "updates roles when custom user defined" do
189 servers.add_host("1", roles: ["foo"], user: "custom")
190 servers.add_host("1", roles: ["bar"], user: "custom")
191 expect(servers.roles_for([:foo]).first.hostname).to eq "1"
192 expect(servers.roles_for([:bar]).first.hostname).to eq "1"
193 end
194
195 it "updates roles when custom port defined" do
196 servers.add_host("1", roles: ["foo"], port: 1234)
197 servers.add_host("1", roles: ["bar"], port: 1234)
198 expect(servers.roles_for([:foo]).first.hostname).to eq "1"
199 expect(servers.roles_for([:bar]).first.hostname).to eq "1"
200 end
201 end
202
203 describe "selecting roles" do
204 before do
205 servers.add_host("1", roles: :app, active: true)
206 servers.add_host("2", roles: :app)
207 end
208
209 it "is empty if the filter would remove all matching hosts" do
217210 expect(servers.roles_for([:app, select: :inactive])).to be_empty
218211 end
219212
220 it 'can filter hosts by properties on the host object using symbol as shorthand' do
213 it "can filter hosts by properties on the host object using symbol as shorthand" do
221214 expect(servers.roles_for([:app, filter: :active]).length).to eq 1
222215 end
223216
224 it 'can select hosts by properties on the host object using symbol as shorthand' do
217 it "can select hosts by properties on the host object using symbol as shorthand" do
225218 expect(servers.roles_for([:app, select: :active]).length).to eq 1
226219 end
227220
228 it 'can filter hosts by properties on the host using a regular proc' do
221 it "can filter hosts by properties on the host using a regular proc" do
229222 expect(servers.roles_for([:app, filter: ->(h) { h.properties.active }]).length).to eq 1
230223 end
231224
232 it 'can select hosts by properties on the host using a regular proc' do
225 it "can select hosts by properties on the host using a regular proc" do
233226 expect(servers.roles_for([:app, select: ->(h) { h.properties.active }]).length).to eq 1
234227 end
235228
236 it 'is empty if the regular proc filter would remove all matching hosts' do
229 it "is empty if the regular proc filter would remove all matching hosts" do
237230 expect(servers.roles_for([:app, select: ->(h) { h.properties.inactive }])).to be_empty
238231 end
239
240 end
241
242 describe 'excluding by property' do
243
244 before do
245 servers.add_host('1', roles: :app, active: true)
246 servers.add_host('2', roles: :app, active: true, no_release: true)
247 end
248
249 it 'is empty if the filter would remove all matching hosts' do
232 end
233
234 describe "excluding by property" do
235 before do
236 servers.add_host("1", roles: :app, active: true)
237 servers.add_host("2", roles: :app, active: true, no_release: true)
238 end
239
240 it "is empty if the filter would remove all matching hosts" do
250241 hosts = servers.roles_for([:app, exclude: :active])
251242 expect(hosts.map(&:hostname)).to be_empty
252243 end
253244
254 it 'returns the servers without the attributes specified' do
245 it "returns the servers without the attributes specified" do
255246 hosts = servers.roles_for([:app, exclude: :no_release])
256247 expect(hosts.map(&:hostname)).to eq %w{1}
257248 end
258249
259 it 'can exclude hosts by properties on the host using a regular proc' do
250 it "can exclude hosts by properties on the host using a regular proc" do
260251 hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.no_release }])
261252 expect(hosts.map(&:hostname)).to eq %w{1}
262253 end
263254
264 it 'is empty if the regular proc filter would remove all matching hosts' do
255 it "is empty if the regular proc filter would remove all matching hosts" do
265256 hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.active }])
266257 expect(hosts.map(&:hostname)).to be_empty
267258 end
268
269 end
270
271 describe 'filtering roles internally' do
272
273 before do
274 servers.add_host('1', roles: :app, active: true)
275 servers.add_host('2', roles: :app)
276 servers.add_host('3', roles: :web)
277 servers.add_host('4', roles: :web)
278 servers.add_host('5', roles: :db)
259 end
260
261 describe "filtering roles internally" do
262 before do
263 servers.add_host("1", roles: :app, active: true)
264 servers.add_host("2", roles: :app)
265 servers.add_host("3", roles: :web)
266 servers.add_host("4", roles: :web)
267 servers.add_host("5", roles: :db)
279268 end
280269
281270 subject { servers.roles_for(roles).map(&:hostname) }
282271
283 context 'with the ROLES environment variable set' do
284
272 context "with the ROLES environment variable set" do
285273 before do
286 ENV.stubs(:[]).with('ROLES').returns('web,db')
287 ENV.stubs(:[]).with('HOSTS').returns(nil)
288 end
289
290 context 'when selecting all roles' do
274 ENV.stubs(:[]).with("ROLES").returns("web,db")
275 ENV.stubs(:[]).with("HOSTS").returns(nil)
276 end
277
278 context "when selecting all roles" do
291279 let(:roles) { [:all] }
292 it 'ignores it' do
280 it "ignores it" do
293281 expect(subject).to eq %w{1 2 3 4 5}
294282 end
295283 end
296284
297 context 'when selecting specific roles' do
298 let(:roles) { [:app, :web] }
299 it 'ignores it' do
285 context "when selecting specific roles" do
286 let(:roles) { %i(app web) }
287 it "ignores it" do
300288 expect(subject).to eq %w{1 2 3 4}
301289 end
302290 end
303291
304 context 'when selecting roles not included in ROLE' do
292 context "when selecting roles not included in ROLE" do
305293 let(:roles) { [:app] }
306 it 'ignores it' do
294 it "ignores it" do
307295 expect(subject).to eq %w{1 2}
308296 end
309297 end
310
311 end
312
313 context 'with the HOSTS environment variable set' do
314
298 end
299
300 context "with the HOSTS environment variable set" do
315301 before do
316 ENV.stubs(:[]).with('ROLES').returns(nil)
317 ENV.stubs(:[]).with('HOSTS').returns('3,5')
318 end
319
320 context 'when selecting all roles' do
302 ENV.stubs(:[]).with("ROLES").returns(nil)
303 ENV.stubs(:[]).with("HOSTS").returns("3,5")
304 end
305
306 context "when selecting all roles" do
321307 let(:roles) { [:all] }
322 it 'ignores it' do
308 it "ignores it" do
323309 expect(subject).to eq %w{1 2 3 4 5}
324310 end
325311 end
326312
327 context 'when selecting specific roles' do
328 let(:roles) { [:app, :web] }
329 it 'ignores it' do
313 context "when selecting specific roles" do
314 let(:roles) { %i(app web) }
315 it "ignores it" do
330316 expect(subject).to eq %w{1 2 3 4}
331317 end
332318 end
333319
334 context 'when selecting no roles' do
320 context "when selecting no roles" do
335321 let(:roles) { [] }
336 it 'ignores it' do
322 it "ignores it" do
337323 expect(subject).to be_empty
338324 end
339325 end
340
341 end
342
326 end
343327 end
344328 end
345329 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
33 describe Configuration do
44 let(:config) { Configuration.new }
55 let(:servers) { stub }
66
7 describe '.new' do
8 it 'accepts initial hash' do
9 configuration = described_class.new(custom: 'value')
10 expect(configuration.fetch(:custom)).to eq('value')
11 end
12 end
13
14 describe '.env' do
15 it 'is a global accessor to a single instance' do
7 describe ".new" do
8 it "accepts initial hash" do
9 configuration = described_class.new(custom: "value")
10 expect(configuration.fetch(:custom)).to eq("value")
11 end
12 end
13
14 describe ".env" do
15 it "is a global accessor to a single instance" do
1616 Configuration.env.set(:test, true)
1717 expect(Configuration.env.fetch(:test)).to be_truthy
1818 end
1919 end
2020
21 describe '.reset!' do
22 it 'blows away the existing `env` and creates a new one' do
21 describe ".reset!" do
22 it "blows away the existing `env` and creates a new one" do
2323 old_env = Configuration.env
2424 Configuration.reset!
2525 expect(Configuration.env).not_to be old_env
2626 end
2727 end
2828
29 describe 'roles' do
30 context 'adding a role' do
29 describe "roles" do
30 context "adding a role" do
3131 subject { config.role(:app, %w{server1 server2}) }
3232
3333 before do
3535 servers.expects(:add_role).with(:app, %w{server1 server2}, {})
3636 end
3737
38 it 'adds the role' do
38 it "adds the role" do
3939 expect(subject)
4040 end
4141 end
4242 end
4343
44 describe 'setting and fetching' do
44 describe "setting and fetching" do
4545 subject { config.fetch(:key, :default) }
4646
47 context 'value is set' do
48 before do
47 context "set" do
48 it "sets by value" do
4949 config.set(:key, :value)
50 end
51
52 it 'returns the set value' do
53 expect(subject).to eq :value
54 end
55 end
56
57 context 'set_if_empty' do
58 it 'sets the value when none is present' do
50 expect(subject).to eq :value
51 end
52
53 it "sets by block" do
54 config.set(:key) { :value }
55 expect(subject).to eq :value
56 end
57
58 it "raises an exception when given both a value and block" do
59 expect { config.set(:key, :value) { :value } }.to raise_error(Capistrano::ValidationError)
60 end
61 end
62
63 context "set_if_empty" do
64 it "sets by value when none is present" do
5965 config.set_if_empty(:key, :value)
6066 expect(subject).to eq :value
6167 end
6268
63 it 'does not overwrite the value' do
69 it "sets by block when none is present" do
70 config.set_if_empty(:key) { :value }
71 expect(subject).to eq :value
72 end
73
74 it "does not overwrite existing values" do
6475 config.set(:key, :value)
6576 config.set_if_empty(:key, :update)
66 expect(subject).to eq :value
67 end
68 end
69
70 context 'value is not set' do
71 it 'returns the default value' do
77 config.set_if_empty(:key) { :update }
78 expect(subject).to eq :value
79 end
80 end
81
82 context "value is not set" do
83 it "returns the default value" do
7284 expect(subject).to eq :default
7385 end
7486 end
7587
76 context 'value is a proc' do
77 subject { config.fetch(:key, Proc.new { :proc } ) }
78 it 'calls the proc' do
88 context "value is a proc" do
89 subject { config.fetch(:key, proc { :proc }) }
90 it "calls the proc" do
7991 expect(subject).to eq :proc
8092 end
8193 end
8294
83 context 'value is a lambda' do
84 subject { config.fetch(:key, lambda { :lambda } ) }
85 it 'calls the lambda' do
95 context "value is a lambda" do
96 subject { config.fetch(:key, -> { :lambda }) }
97 it "calls the lambda" do
8698 expect(subject).to eq :lambda
8799 end
88100 end
89101
90 context 'value inside proc inside a proc' do
91 subject { config.fetch(:key, Proc.new { Proc.new { "some value" } } ) }
92 it 'calls all procs and lambdas' do
102 context "value inside proc inside a proc" do
103 subject { config.fetch(:key, proc { proc { "some value" } }) }
104 it "calls all procs and lambdas" do
93105 expect(subject).to eq "some value"
94106 end
95107 end
96108
97 context 'value inside lambda inside a lambda' do
98 subject { config.fetch(:key, lambda { lambda { "some value" } } ) }
99 it 'calls all procs and lambdas' do
109 context "value inside lambda inside a lambda" do
110 subject { config.fetch(:key, -> { -> { "some value" } }) }
111 it "calls all procs and lambdas" do
100112 expect(subject).to eq "some value"
101113 end
102114 end
103115
104 context 'value inside lambda inside a proc' do
105 subject { config.fetch(:key, Proc.new { lambda { "some value" } } ) }
106 it 'calls all procs and lambdas' do
116 context "value inside lambda inside a proc" do
117 subject { config.fetch(:key, proc { -> { "some value" } }) }
118 it "calls all procs and lambdas" do
107119 expect(subject).to eq "some value"
108120 end
109121 end
110122
111 context 'value inside proc inside a lambda' do
112 subject { config.fetch(:key, lambda { Proc.new { "some value" } } ) }
113 it 'calls all procs and lambdas' do
123 context "value inside proc inside a lambda" do
124 subject { config.fetch(:key, -> { proc { "some value" } }) }
125 it "calls all procs and lambdas" do
114126 expect(subject).to eq "some value"
115127 end
116128 end
117129
118 context 'lambda with parameters' do
119 subject { config.fetch(:key, lambda { |c| c }).call(42) }
120 it 'is returned as a lambda' do
130 context "lambda with parameters" do
131 subject { config.fetch(:key, ->(c) { c }).call(42) }
132 it "is returned as a lambda" do
121133 expect(subject).to eq 42
122134 end
123135 end
124136
125 context 'block is passed to fetch' do
126 subject { config.fetch(:key, :default) { fail 'we need this!' } }
127
128 it 'returns the block value' do
129 expect { subject }.to raise_error
130 end
131 end
132 end
133
134 describe 'keys' do
137 context "block is passed to fetch" do
138 subject { config.fetch(:key, :default) { raise "we need this!" } }
139
140 it "returns the block value" do
141 expect { subject }.to raise_error(RuntimeError)
142 end
143 end
144
145 context "validations" do
146 before do
147 config.validate :key do |_, value|
148 raise Capistrano::ValidationError unless value.length > 3
149 end
150 end
151
152 it "validates string without error" do
153 config.set(:key, "longer_value")
154 end
155
156 it "validates block without error" do
157 config.set(:key) { "longer_value" }
158 expect(config.fetch(:key)).to eq "longer_value"
159 end
160
161 it "validates lambda without error" do
162 config.set :key, -> { "longer_value" }
163 expect(config.fetch(:key)).to eq "longer_value"
164 end
165
166 it "raises an exception on invalid string" do
167 expect { config.set(:key, "sho") }.to raise_error(Capistrano::ValidationError)
168 end
169
170 it "raises an exception on invalid string provided by block" do
171 config.set(:key) { "sho" }
172 expect { config.fetch(:key) }.to raise_error(Capistrano::ValidationError)
173 end
174
175 it "raises an exception on invalid string provided by lambda" do
176 config.set :key, -> { "sho" }
177 expect { config.fetch(:key) }.to raise_error(Capistrano::ValidationError)
178 end
179 end
180
181 context "appending" do
182 subject { config.append(:linked_dirs, "vendor/bundle", "tmp") }
183
184 it "returns appended value" do
185 expect(subject).to eq ["vendor/bundle", "tmp"]
186 end
187
188 context "on non-array variable" do
189 before { config.set(:linked_dirs, "string") }
190 subject { config.append(:linked_dirs, "vendor/bundle") }
191
192 it "returns appended value" do
193 expect(subject).to eq ["string", "vendor/bundle"]
194 end
195 end
196 end
197
198 context "removing" do
199 before :each do
200 config.set(:linked_dirs, ["vendor/bundle", "tmp"])
201 end
202
203 subject { config.remove(:linked_dirs, "vendor/bundle") }
204
205 it "returns without removed value" do
206 expect(subject).to eq ["tmp"]
207 end
208
209 context "on non-array variable" do
210 before { config.set(:linked_dirs, "string") }
211
212 context "when removing same value" do
213 subject { config.remove(:linked_dirs, "string") }
214
215 it "returns without removed value" do
216 expect(subject).to eq []
217 end
218 end
219
220 context "when removing different value" do
221 subject { config.remove(:linked_dirs, "othervalue") }
222
223 it "returns without removed value" do
224 expect(subject).to eq ["string"]
225 end
226 end
227 end
228 end
229 end
230
231 describe "keys" do
135232 subject { config.keys }
136233
137234 before do
139236 config.set(:key2, :value2)
140237 end
141238
142 it 'returns all set keys' do
143 expect(subject).to match_array [:key1, :key2]
144 end
145 end
146
147 describe 'deleting' do
239 it "returns all set keys" do
240 expect(subject).to match_array %i(key1 key2)
241 end
242 end
243
244 describe "deleting" do
148245 before do
149246 config.set(:key, :value)
150247 end
151248
152 it 'deletes the value' do
249 it "deletes the value" do
153250 config.delete(:key)
154251 expect(config.fetch(:key)).to be_nil
155252 end
156253 end
157254
158 describe 'asking' do
255 describe "asking" do
159256 let(:question) { stub }
160 let(:options) { Hash.new }
257 let(:options) { {} }
161258
162259 before do
163 Configuration::Question.expects(:new).with(:branch, :default, options).
164 returns(question)
165 end
166
167 it 'prompts for the value when fetching' do
260 Configuration::Question.expects(:new).with(:branch, :default, options)
261 .returns(question)
262 end
263
264 it "prompts for the value when fetching" do
168265 config.ask(:branch, :default, options)
169266 expect(config.fetch(:branch)).to eq question
170267 end
171268 end
172269
173 describe 'setting the backend' do
174 it 'by default, is SSHKit' do
270 describe "setting the backend" do
271 it "by default, is SSHKit" do
175272 expect(config.backend).to eq SSHKit
176273 end
177274
178 it 'can be set to another class' do
275 it "can be set to another class" do
179276 config.backend = :test
180277 expect(config.backend).to eq :test
181278 end
182279
183280 describe "ssh_options for Netssh" do
184 it 'merges them with the :ssh_options variable' do
281 it "merges them with the :ssh_options variable" do
185282 config.set :format, :pretty
186283 config.set :log_level, :debug
187 config.set :ssh_options, { user: 'albert' }
188 SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { password: 'einstein' } end
284 config.set :ssh_options, user: "albert"
285 SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = { password: "einstein" } }
189286 config.configure_backend
190 expect(config.backend.config.backend.config.ssh_options).to eq({ user: 'albert', password: 'einstein' })
191 end
287
288 expect(
289 config.backend.config.backend.config.ssh_options
290 ).to include(user: "albert", password: "einstein")
291 end
292 end
293 end
294
295 describe "dry_run?" do
296 it "returns false when using default backend" do
297 expect(config.dry_run?).to eq(false)
298 end
299
300 it "returns true when using printer backend" do
301 config.set :sshkit_backend, SSHKit::Backend::Printer
302
303 expect(config.dry_run?).to eq(true)
304 end
305 end
306
307 describe "custom filtering" do
308 it "accepts a custom filter object" do
309 filter = Object.new
310 def filter.filter(servers)
311 servers
312 end
313 config.add_filter(filter)
314 end
315
316 it "accepts a custom filter as a block" do
317 config.add_filter { |servers| servers }
318 end
319
320 it "raises an error if passed a block and an object" do
321 filter = Object.new
322 def filter.filter(servers)
323 servers
324 end
325
326 expect { config.add_filter(filter) { |servers| servers } }.to raise_error(ArgumentError)
327 end
328
329 it "raises an error if the filter lacks a filter method" do
330 filter = Object.new
331 expect { config.add_filter(filter) }.to raise_error(TypeError)
332 end
333
334 it "calls the filter method of a custom filter" do
335 ENV.delete "ROLES"
336 ENV.delete "HOSTS"
337
338 servers = Configuration::Servers.new
339
340 servers.add_host("test1")
341 servers.add_host("test2")
342 servers.add_host("test3")
343
344 filtered_servers = servers.take(2)
345
346 filter = mock("custom filter")
347 filter.expects(:filter)
348 .with { |subset| subset.is_a? Configuration::Servers }
349 .returns(filtered_servers)
350
351 config.add_filter(filter)
352 expect(config.filter(servers)).to eq(filtered_servers)
192353 end
193354 end
194355 end
0 require "spec_helper"
1 require "capistrano/doctor/environment_doctor"
2
3 module Capistrano
4 module Doctor
5 describe EnvironmentDoctor do
6 let(:doc) { EnvironmentDoctor.new }
7
8 it "prints using 4-space indentation" do
9 expect { doc.call }.to output(/^ {4}/).to_stdout
10 end
11
12 it "prints the Ruby version" do
13 expect { doc.call }.to\
14 output(/#{Regexp.quote(RUBY_DESCRIPTION)}/).to_stdout
15 end
16
17 it "prints the Rubygems version" do
18 expect { doc.call }.to output(/#{Regexp.quote(Gem::VERSION)}/).to_stdout
19 end
20
21 describe "Rake" do
22 before do
23 load File.expand_path("../../../../../lib/capistrano/doctor.rb",
24 __FILE__)
25 end
26
27 after do
28 Rake::Task.clear
29 end
30
31 it "has an doctor:environment task that calls EnvironmentDoctor" do
32 EnvironmentDoctor.any_instance.expects(:call)
33 Rake::Task["doctor:environment"].invoke
34 end
35
36 it "has a doctor task that depends on doctor:environment" do
37 expect(Rake::Task["doctor"].prerequisites).to \
38 include("doctor:environment")
39 end
40 end
41 end
42 end
43 end
0 require "spec_helper"
1 require "capistrano/doctor/gems_doctor"
2 require "airbrussh/version"
3 require "sshkit/version"
4 require "net/ssh/version"
5
6 module Capistrano
7 module Doctor
8 describe GemsDoctor do
9 let(:doc) { GemsDoctor.new }
10
11 it "prints using 4-space indentation" do
12 expect { doc.call }.to output(/^ {4}/).to_stdout
13 end
14
15 it "prints the Capistrano version" do
16 expect { doc.call }.to\
17 output(/capistrano\s+#{Regexp.quote(Capistrano::VERSION)}/).to_stdout
18 end
19
20 it "prints the Rake version" do
21 expect { doc.call }.to\
22 output(/rake\s+#{Regexp.quote(Rake::VERSION)}/).to_stdout
23 end
24
25 it "prints the SSHKit version" do
26 expect { doc.call }.to\
27 output(/sshkit\s+#{Regexp.quote(SSHKit::VERSION)}/).to_stdout
28 end
29
30 it "prints the Airbrussh version" do
31 expect { doc.call }.to\
32 output(/airbrussh\s+#{Regexp.quote(Airbrussh::VERSION)}/).to_stdout
33 end
34
35 it "prints the net-ssh version" do
36 expect { doc.call }.to\
37 output(/net-ssh\s+#{Regexp.quote(Net::SSH::Version::STRING)}/).to_stdout
38 end
39
40 it "warns that new version is available" do
41 Gem.stubs(:latest_version_for).returns(Gem::Version.new("99.0.0"))
42 expect { doc.call }.to output(/\(update available\)/).to_stdout
43 end
44
45 describe "Rake" do
46 before do
47 load File.expand_path("../../../../../lib/capistrano/doctor.rb",
48 __FILE__)
49 end
50
51 after do
52 Rake::Task.clear
53 end
54
55 it "has an doctor:gems task that calls GemsDoctor" do
56 GemsDoctor.any_instance.expects(:call)
57 Rake::Task["doctor:gems"].invoke
58 end
59
60 it "has a doctor task that depends on doctor:gems" do
61 expect(Rake::Task["doctor"].prerequisites).to include("doctor:gems")
62 end
63 end
64 end
65 end
66 end
0 require "spec_helper"
1 require "capistrano/doctor/output_helpers"
2
3 module Capistrano
4 module Doctor
5 describe OutputHelpers do
6 include OutputHelpers
7
8 # Force color for the purpose of these tests
9 before { ENV.stubs(:[]).with("SSHKIT_COLOR").returns("1") }
10
11 it "prints titles in blue with newlines and without indentation" do
12 expect { title("Hello!") }.to\
13 output("\e[0;34;49m\nHello!\n\e[0m\n").to_stdout
14 end
15
16 it "prints warnings in yellow with 4-space indentation" do
17 expect { warning("Yikes!") }.to\
18 output(" \e[0;33;49mYikes!\e[0m\n").to_stdout
19 end
20
21 it "overrides puts to indent 4 spaces per line" do
22 expect { puts("one\ntwo") }.to output(" one\n two\n").to_stdout
23 end
24
25 it "formats tables with indent, aligned columns and per-row color" do
26 data = [
27 ["one", ".", "1"],
28 ["two", "..", "2"],
29 ["three", "...", "3"]
30 ]
31 block = proc do |record, row|
32 row.yellow if record.first == "two"
33 row << record[0]
34 row << record[1]
35 row << record[2]
36 end
37 expected_output = <<-OUT
38 one . 1
39 \e[0;33;49mtwo .. 2\e[0m
40 three ... 3
41 OUT
42 expect { table(data, &block) }.to output(expected_output).to_stdout
43 end
44 end
45 end
46 end
0 require "spec_helper"
1 require "capistrano/doctor/servers_doctor"
2
3 module Capistrano
4 module Doctor
5 describe ServersDoctor do
6 include Capistrano::DSL
7 let(:doc) { ServersDoctor.new }
8
9 before { Capistrano::Configuration.reset! }
10 after { Capistrano::Configuration.reset! }
11
12 it "prints using 4-space indentation" do
13 expect { doc.call }.to output(/^ {4}/).to_stdout
14 end
15
16 it "prints the number of defined servers" do
17 role :app, %w(example.com)
18 server "www@example.com:22"
19
20 expect { doc.call }.to output(/Servers \(2\)/).to_stdout
21 end
22
23 describe "prints the server's details" do
24 it "including username" do
25 server "www@example.com"
26 expect { doc.call }.to output(/www@example.com/).to_stdout
27 end
28
29 it "including port" do
30 server "www@example.com:22"
31 expect { doc.call }.to output(/www@example.com:22/).to_stdout
32 end
33
34 it "including roles" do
35 role :app, %w(example.com)
36 expect { doc.call }.to output(/example.com\s+\[:app\]/).to_stdout
37 end
38
39 it "including empty roles" do
40 server "example.com"
41 expect { doc.call }.to output(/example.com\s+\[\]/).to_stdout
42 end
43
44 it "including properties" do
45 server "example.com", roles: %w(app db), primary: true
46 expect { doc.call }.to \
47 output(/example.com\s+\[:app, :db\]\s+\{ :primary => true \}/).to_stdout
48 end
49
50 it "including misleading role name alert" do
51 server "example.com", roles: ["web app db"]
52 warning_msg = 'Whitespace detected in role(s) :"web app db". ' \
53 'This might be a result of a mistyped "%w()" array literal'
54
55 expect { doc.call }.to output(/#{Regexp.escape(warning_msg)}/).to_stdout
56 end
57 end
58
59 it "doesn't fail for no servers" do
60 expect { doc.call }.to output("\nServers (0)\n \n").to_stdout
61 end
62
63 describe "Rake" do
64 before do
65 load File.expand_path("../../../../../lib/capistrano/doctor.rb",
66 __FILE__)
67 end
68
69 after do
70 Rake::Task.clear
71 end
72
73 it "has an doctor:servers task that calls ServersDoctor" do
74 ServersDoctor.any_instance.expects(:call)
75 Rake::Task["doctor:servers"].invoke
76 end
77
78 it "has a doctor task that depends on doctor:servers" do
79 expect(Rake::Task["doctor"].prerequisites).to \
80 include("doctor:servers")
81 end
82 end
83 end
84 end
85 end
0 require "spec_helper"
1 require "capistrano/doctor/variables_doctor"
2
3 module Capistrano
4 module Doctor
5 describe VariablesDoctor do
6 include Capistrano::DSL
7
8 let(:doc) { VariablesDoctor.new }
9
10 before do
11 set :branch, "master"
12 set :pty, false
13
14 env.variables.untrusted! do
15 set :application, "my_app"
16 set :repo_tree, "public"
17 set :repo_url, ".git"
18 set :copy_strategy, :scp
19 set :custom_setting, "hello"
20 set "string_setting", "hello"
21 ask :secret
22 end
23
24 fetch :custom_setting
25 end
26
27 after { Capistrano::Configuration.reset! }
28
29 it "prints using 4-space indentation" do
30 expect { doc.call }.to output(/^ {4}/).to_stdout
31 end
32
33 it "prints variable names and values" do
34 expect { doc.call }.to output(/:branch\s+"master"$/).to_stdout
35 expect { doc.call }.to output(/:pty\s+false$/).to_stdout
36 expect { doc.call }.to output(/:application\s+"my_app"$/).to_stdout
37 expect { doc.call }.to output(/:repo_url\s+".git"$/).to_stdout
38 expect { doc.call }.to output(/:repo_tree\s+"public"$/).to_stdout
39 expect { doc.call }.to output(/:copy_strategy\s+:scp$/).to_stdout
40 expect { doc.call }.to output(/:custom_setting\s+"hello"$/).to_stdout
41 expect { doc.call }.to output(/"string_setting"\s+"hello"$/).to_stdout
42 end
43
44 it "prints unanswered question variable as <ask>" do
45 expect { doc.call }.to output(/:secret\s+<ask>$/).to_stdout
46 end
47
48 it "prints warning for unrecognized variable" do
49 expect { doc.call }.to \
50 output(/:copy_strategy is not a recognized Capistrano setting/)\
51 .to_stdout
52 end
53
54 it "does not print warning for unrecognized variable that is fetched" do
55 expect { doc.call }.not_to \
56 output(/:custom_setting is not a recognized Capistrano setting/)\
57 .to_stdout
58 end
59
60 it "does not print warning for whitelisted variable" do
61 expect { doc.call }.not_to \
62 output(/:repo_tree is not a recognized Capistrano setting/)\
63 .to_stdout
64 end
65
66 describe "Rake" do
67 before do
68 load File.expand_path("../../../../../lib/capistrano/doctor.rb",
69 __FILE__)
70 end
71
72 after do
73 Rake::Task.clear
74 end
75
76 it "has an doctor:variables task that calls VariablesDoctor" do
77 VariablesDoctor.any_instance.expects(:call)
78 Rake::Task["doctor:variables"].invoke
79 end
80
81 it "has a doctor task that depends on doctor:variables" do
82 expect(Rake::Task["doctor"].prerequisites).to \
83 include("doctor:variables")
84 end
85 end
86 end
87 end
88 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 describe Capistrano::DSL::Paths do
3
43 let(:dsl) { Class.new.extend Capistrano::DSL }
5 let(:parent) { Pathname.new('/var/shared') }
4 let(:parent) { Pathname.new("/var/shared") }
65 let(:paths) { Class.new.extend Capistrano::DSL::Paths }
76
87 let(:linked_dirs) { %w{log public/system} }
9 let(:linked_files) { %w{config/database.yml log/my.log} }
8 let(:linked_files) { %w{config/database.yml log/my.log log/access.log} }
109
1110 before do
12 dsl.set(:deploy_to, '/var/www')
13 end
14
15 describe '#linked_dirs' do
11 dsl.set(:deploy_to, "/var/www")
12 end
13
14 describe "#linked_dirs" do
1615 subject { paths.linked_dirs(parent) }
1716
1817 before do
1918 paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
2019 end
2120
22 it 'returns the full pathnames' do
23 expect(subject).to eq [Pathname.new('/var/shared/log'), Pathname.new('/var/shared/public/system')]
24 end
25 end
26
27
28 describe '#linked_files' do
21 it "returns the full pathnames" do
22 expect(subject).to eq [
23 Pathname.new("/var/shared/log"),
24 Pathname.new("/var/shared/public/system")
25 ]
26 end
27 end
28
29 describe "#linked_files" do
2930 subject { paths.linked_files(parent) }
3031
3132 before do
3233 paths.expects(:fetch).with(:linked_files).returns(linked_files)
3334 end
3435
35 it 'returns the full pathnames' do
36 expect(subject).to eq [Pathname.new('/var/shared/config/database.yml'), Pathname.new('/var/shared/log/my.log')]
37 end
38 end
39
40 describe '#linked_file_dirs' do
36 it "returns the full pathnames" do
37 expect(subject).to eq [
38 Pathname.new("/var/shared/config/database.yml"),
39 Pathname.new("/var/shared/log/my.log"),
40 Pathname.new("/var/shared/log/access.log")
41 ]
42 end
43 end
44
45 describe "#linked_file_dirs" do
4146 subject { paths.linked_file_dirs(parent) }
4247
4348 before do
4449 paths.expects(:fetch).with(:linked_files).returns(linked_files)
4550 end
4651
47 it 'returns the full paths names of the parent dirs' do
48 expect(subject).to eq [Pathname.new('/var/shared/config'), Pathname.new('/var/shared/log')]
49 end
50 end
51
52 describe '#linked_dir_parents' do
52 it "returns the full paths names of the parent dirs" do
53 expect(subject).to eq [
54 Pathname.new("/var/shared/config"),
55 Pathname.new("/var/shared/log")
56 ]
57 end
58 end
59
60 describe "#linked_dir_parents" do
5361 subject { paths.linked_dir_parents(parent) }
5462
5563 before do
5664 paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
5765 end
5866
59 it 'returns the full paths names of the parent dirs' do
60 expect(subject).to eq [Pathname.new('/var/shared'), Pathname.new('/var/shared/public')]
61 end
62 end
63
64 describe '#release path' do
65
67 it "returns the full paths names of the parent dirs" do
68 expect(subject).to eq [
69 Pathname.new("/var/shared"),
70 Pathname.new("/var/shared/public")
71 ]
72 end
73 end
74
75 describe "#release path" do
6676 subject { dsl.release_path }
6777
68 context 'where no release path has been set' do
78 context "where no release path has been set" do
6979 before do
7080 dsl.delete(:release_path)
7181 end
7282
73 it 'returns the `current_path` value' do
74 expect(subject.to_s).to eq '/var/www/current'
75 end
76 end
77
78 context 'where the release path has been set' do
79 before do
80 dsl.set(:release_path,'/var/www/release_path')
81 end
82
83 it 'returns the set `release_path` value' do
84 expect(subject.to_s).to eq '/var/www/release_path'
85 end
86 end
87 end
88
89 describe '#set_release_path' do
83 it "returns the `current_path` value" do
84 expect(subject.to_s).to eq "/var/www/current"
85 end
86 end
87
88 context "where the release path has been set" do
89 before do
90 dsl.set(:release_path, "/var/www/release_path")
91 end
92
93 it "returns the set `release_path` value" do
94 expect(subject.to_s).to eq "/var/www/release_path"
95 end
96 end
97 end
98
99 describe "#set_release_path" do
90100 let(:now) { Time.parse("Oct 21 16:29:00 2015") }
91101 subject { dsl.release_path }
92102
93 context 'without a timestamp' do
103 context "without a timestamp" do
94104 before do
95105 dsl.env.expects(:timestamp).returns(now)
96106 dsl.set_release_path
97107 end
98108
99 it 'returns the release path with the current env timestamp' do
100 expect(subject.to_s).to eq '/var/www/releases/20151021162900'
101 end
102 end
103
104 context 'with a timestamp' do
105 before do
106 dsl.set_release_path('timestamp')
107 end
108
109 it 'returns the release path with the timestamp' do
110 expect(subject.to_s).to eq '/var/www/releases/timestamp'
111 end
112 end
113 end
114
115 describe '#deploy_config_path' do
109 it "returns the release path with the current env timestamp" do
110 expect(subject.to_s).to eq "/var/www/releases/20151021162900"
111 end
112 end
113
114 context "with a timestamp" do
115 before do
116 dsl.set_release_path("timestamp")
117 end
118
119 it "returns the release path with the timestamp" do
120 expect(subject.to_s).to eq "/var/www/releases/timestamp"
121 end
122 end
123 end
124
125 describe "#releases_path" do
126 subject { paths.releases_path }
127
128 context "with custom releases directory" do
129 before do
130 paths.expects(:fetch).with(:releases_directory, "releases").returns("test123")
131 paths.expects(:fetch).with(:deploy_to).returns("/var/www")
132 end
133
134 it "returns the releases path with the custom directory" do
135 expect(subject.to_s).to eq "/var/www/test123"
136 end
137 end
138 end
139
140 describe "#shared_path" do
141 subject { paths.shared_path }
142
143 context "with custom shared directory" do
144 before do
145 paths.expects(:fetch).with(:shared_directory, "shared").returns("test123")
146 paths.expects(:fetch).with(:deploy_to).returns("/var/www")
147 end
148
149 it "returns the shared path with the custom directory" do
150 expect(subject.to_s).to eq "/var/www/test123"
151 end
152 end
153 end
154
155 describe "#deploy_config_path" do
116156 subject { dsl.deploy_config_path.to_s }
117157
118 context 'when not specified' do
158 context "when not specified" do
119159 before do
120160 dsl.delete(:deploy_config_path)
121161 end
122162
123163 it 'returns "config/deploy.rb"' do
124 expect(subject).to eq 'config/deploy.rb'
125 end
126 end
127
128 context 'when the variable :deploy_config_path is set' do
129 before do
130 dsl.set(:deploy_config_path, 'my/custom/path.rb')
131 end
132
133 it 'returns the custom path' do
134 expect(subject).to eq 'my/custom/path.rb'
135 end
136 end
137 end
138
139 describe '#stage_config_path' do
164 expect(subject).to eq "config/deploy.rb"
165 end
166 end
167
168 context "when the variable :deploy_config_path is set" do
169 before do
170 dsl.set(:deploy_config_path, "my/custom/path.rb")
171 end
172
173 it "returns the custom path" do
174 expect(subject).to eq "my/custom/path.rb"
175 end
176 end
177 end
178
179 describe "#stage_config_path" do
140180 subject { dsl.stage_config_path.to_s }
141181
142 context 'when not specified' do
143
182 context "when not specified" do
144183 before do
145184 dsl.delete(:stage_config_path)
146185 end
147186
148187 it 'returns "config/deploy"' do
149 expect(subject).to eq 'config/deploy'
150 end
151 end
152
153 context 'when the variable :stage_config_path is set' do
154 before do
155 dsl.set(:stage_config_path, 'my/custom/path')
156 end
157
158 it 'returns the custom path' do
159 expect(subject).to eq 'my/custom/path'
160 end
161 end
162 end
163
164 describe '#repo_path' do
188 expect(subject).to eq "config/deploy"
189 end
190 end
191
192 context "when the variable :stage_config_path is set" do
193 before do
194 dsl.set(:stage_config_path, "my/custom/path")
195 end
196
197 it "returns the custom path" do
198 expect(subject).to eq "my/custom/path"
199 end
200 end
201 end
202
203 describe "#repo_path" do
165204 subject { dsl.repo_path.to_s }
166205
167 context 'when not specified' do
168
206 context "when not specified" do
169207 before do
170208 dsl.delete(:repo_path)
171209 end
172210
173211 it 'returns the default #{deploy_to}/repo' do
174 dsl.set(:deploy_to, '/var/www')
175 expect(subject).to eq '/var/www/repo'
176 end
177 end
178
179 context 'when the variable :repo_path is set' do
180 before do
181 dsl.set(:repo_path, 'my/custom/path')
182 end
183
184 it 'returns the custom path' do
185 expect(subject).to eq 'my/custom/path'
212 dsl.set(:deploy_to, "/var/www")
213 expect(subject).to eq "/var/www/repo"
214 end
215 end
216
217 context "when the variable :repo_path is set" do
218 before do
219 dsl.set(:repo_path, "my/custom/path")
220 end
221
222 it "returns the custom path" do
223 expect(subject).to eq "my/custom/path"
186224 end
187225 end
188226 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
33 class DummyTaskEnhancements
77 describe TaskEnhancements do
88 let(:task_enhancements) { DummyTaskEnhancements.new }
99
10 describe 'ordering' do
11
10 describe "ordering" do
1211 after do
1312 task.clear
1413 before_task.clear
1817
1918 let(:order) { [] }
2019 let!(:task) do
21 Rake::Task.define_task('task', [:order]) do |t, args|
22 args['order'].push 'task'
20 Rake::Task.define_task("task", [:order]) do |_t, args|
21 args["order"].push "task"
2322 end
2423 end
2524
2625 let!(:before_task) do
27 Rake::Task.define_task('before_task') do
28 order.push 'before_task'
26 Rake::Task.define_task("before_task") do
27 order.push "before_task"
2928 end
3029 end
3130
3231 let!(:after_task) do
33 Rake::Task.define_task('after_task') do
34 order.push 'after_task'
32 Rake::Task.define_task("after_task") do
33 order.push "after_task"
3534 end
3635 end
3736
38 it 'invokes in proper order if define after than before' do
39 task_enhancements.after('task', 'after_task')
40 task_enhancements.before('task', 'before_task')
37 it "invokes in proper order if define after than before" do
38 task_enhancements.after("task", "after_task")
39 task_enhancements.before("task", "before_task")
4140
42 Rake::Task['task'].invoke order
41 Rake::Task["task"].invoke order
4342
44 expect(order).to eq(['before_task', 'task', 'after_task'])
43 expect(order).to eq(%w(before_task task after_task))
4544 end
4645
47 it 'invokes in proper order if define before than after' do
48 task_enhancements.before('task', 'before_task')
49 task_enhancements.after('task', 'after_task')
46 it "invokes in proper order if define before than after" do
47 task_enhancements.before("task", "before_task")
48 task_enhancements.after("task", "after_task")
5049
51 Rake::Task['task'].invoke order
50 Rake::Task["task"].invoke order
5251
53 expect(order).to eq(['before_task', 'task', 'after_task'])
52 expect(order).to eq(%w(before_task task after_task))
5453 end
5554
56 it 'invokes in proper order and with arguments and block' do
57 task_enhancements.after('task', 'after_task_custom', :order) do |t, args|
58 order.push 'after_task'
55 it "invokes in proper order when referring to as-yet undefined tasks" do
56 task_enhancements.after("task", "not_loaded_task")
57
58 Rake::Task.define_task("not_loaded_task") do
59 order.push "not_loaded_task"
5960 end
6061
61 task_enhancements.before('task', 'before_task_custom', :order) do |t, args|
62 order.push 'before_task'
62 Rake::Task["task"].invoke order
63
64 expect(order).to eq(%w(task not_loaded_task))
65 end
66
67 it "invokes in proper order and with arguments and block" do
68 task_enhancements.after("task", "after_task_custom", :order) do |_t, _args|
69 order.push "after_task"
6370 end
6471
65 Rake::Task['task'].invoke(order)
72 task_enhancements.before("task", "before_task_custom", :order) do |_t, _args|
73 order.push "before_task"
74 end
6675
67 expect(order).to eq(['before_task', 'task', 'after_task'])
76 Rake::Task["task"].invoke(order)
77
78 expect(order).to eq(%w(before_task task after_task))
6879 end
6980
70 end
81 it "invokes using the correct namespace when defined within a namespace" do
82 Rake.application.in_namespace("namespace") do
83 Rake::Task.define_task("task") do |t|
84 order.push(t.name)
85 end
86 task_enhancements.before("task", "before_task", :order) do |t|
87 order.push(t.name)
88 end
89 task_enhancements.after("task", "after_task", :order) do |t|
90 order.push(t.name)
91 end
92 end
7193
72 describe 'remote_file' do
73 subject(:remote_file) { task_enhancements.remote_file('source' => 'destination') }
94 Rake::Task["namespace:task"].invoke
7495
75 it { expect(remote_file.name).to eq('source') }
76 it { is_expected.to be_a(Capistrano::UploadTask) }
96 expect(order).to eq(
97 ["namespace:before_task", "namespace:task", "namespace:after_task"]
98 )
99 end
77100
78 describe 'namespaced' do
79 let(:app) { Rake.application }
80 around { |ex| app.in_namespace('namespace', &ex) }
81
82 it { expect(remote_file.name).to eq('source') }
83 it { is_expected.to be_a(Capistrano::UploadTask) }
101 it "raises a sensible error if the task isn't found" do
102 task_enhancements.after("task", "non_existent_task")
103 expect { Rake::Task["task"].invoke order }.to raise_error(ArgumentError, 'Task "non_existent_task" not found')
84104 end
85105 end
86106 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
3
43 class DummyDSL
54 include DSL
65 end
98 describe DSL do
109 let(:dsl) { DummyDSL.new }
1110
12 describe '#t' do
11 describe "#t" do
1312 before do
14 I18n.expects(:t).with(:phrase, {count: 2, scope: :capistrano})
13 I18n.expects(:t).with(:phrase, count: 2, scope: :capistrano)
1514 end
1615
17 it 'delegates to I18n' do
16 it "delegates to I18n" do
1817 dsl.t(:phrase, count: 2)
1918 end
2019 end
2120
22 describe '#stage_set?' do
21 describe "#stage_set?" do
2322 subject { dsl.stage_set? }
2423
25 context 'stage is set' do
24 context "stage is set" do
2625 before do
2726 dsl.set(:stage, :sandbox)
2827 end
2928 it { expect(subject).to be_truthy }
3029 end
3130
32 context 'stage is not set' do
31 context "stage is not set" do
3332 before do
3433 dsl.set(:stage, nil)
3534 end
3736 end
3837 end
3938
40 describe '#sudo' do
41
39 describe "#sudo" do
4240 before do
4341 dsl.expects(:execute).with(:sudo, :my, :command)
4442 end
4543
46 it 'prepends sudo, delegates to execute' do
44 it "prepends sudo, delegates to execute" do
4745 dsl.sudo(:my, :command)
46 end
47 end
48
49 describe "#execute" do
50 context "use outside of on scope" do
51 after do
52 task.clear
53 Rake::Task.clear
54 end
55
56 let(:task) do
57 Rake::Task.define_task("execute_outside_scope") do
58 dsl.execute "whoami"
59 end
60 end
61
62 it "prints helpful message to stderr" do
63 expect do
64 expect do
65 task.invoke
66 end.to output(/^.*Warning: `execute' should be wrapped in an `on' scope/).to_stderr
67 end.to raise_error(NoMethodError)
68 end
69 end
70 end
71
72 describe "#invoke" do
73 context "reinvoking" do
74 it "will not reenable invoking task" do
75 counter = 0
76
77 Rake::Task.define_task("A") do
78 counter += 1
79 end
80
81 expect do
82 dsl.invoke("A")
83 dsl.invoke("A")
84 end.to change { counter }.by(1)
85 end
86
87 it "will print a message on stderr" do
88 Rake::Task.define_task("B")
89
90 expect do
91 dsl.invoke("B")
92 dsl.invoke("B")
93 end.to output(/If you really meant to run this task again, use invoke!/).to_stderr
94 end
95 end
96 end
97
98 describe "#invoke!" do
99 context "reinvoking" do
100 it "will reenable invoking task" do
101 counter = 0
102
103 Rake::Task.define_task("C") do
104 counter += 1
105 end
106
107 expect do
108 dsl.invoke!("C")
109 dsl.invoke!("C")
110 end.to change { counter }.by(2)
111 end
112
113 it "will not print a message on stderr" do
114 Rake::Task.define_task("D")
115
116 expect do
117 dsl.invoke!("D")
118 dsl.invoke!("D")
119 end.to_not output(/If you really meant to run this task again, use invoke!/).to_stderr
120 end
48121 end
49122 end
50123 end
+0
-81
spec/lib/capistrano/git_spec.rb less more
0 require 'spec_helper'
1
2 require 'capistrano/git'
3
4 module Capistrano
5 describe Git do
6 let(:context) { Class.new.new }
7 subject { Capistrano::Git.new(context, Capistrano::Git::DefaultStrategy) }
8
9 describe "#git" do
10 it "should call execute git in the context, with arguments" do
11 context.expects(:execute).with(:git, :init)
12 subject.git(:init)
13 end
14 end
15 end
16
17 describe Git::DefaultStrategy do
18 let(:context) { Class.new.new }
19 subject { Capistrano::Git.new(context, Capistrano::Git::DefaultStrategy) }
20
21 describe "#test" do
22 it "should call test for repo HEAD" do
23 context.expects(:repo_path).returns("/path/to/repo")
24 context.expects(:test).with " [ -f /path/to/repo/HEAD ] "
25
26 subject.test
27 end
28 end
29
30 describe "#check" do
31 it "should test the repo url" do
32 context.expects(:repo_url).returns(:url)
33 context.expects(:execute).with(:git, :'ls-remote --heads', :url).returns(true)
34
35 subject.check
36 end
37 end
38
39 describe "#clone" do
40 it "should run git clone" do
41 context.expects(:repo_url).returns(:url)
42 context.expects(:repo_path).returns(:path)
43
44 context.expects(:execute).with(:git, :clone, '--mirror', :url, :path)
45
46 subject.clone
47 end
48 end
49
50 describe "#update" do
51 it "should run git update" do
52 context.expects(:execute).with(:git, :remote, :update)
53
54 subject.update
55 end
56 end
57
58 describe "#release" do
59 it "should run git archive without a subtree" do
60 context.expects(:fetch).with(:repo_tree).returns(nil)
61 context.expects(:fetch).with(:branch).returns(:branch)
62 context.expects(:release_path).returns(:path)
63
64 context.expects(:execute).with(:git, :archive, :branch, '| tar -x -f - -C', :path)
65
66 subject.release
67 end
68
69 it "should run git archive with a subtree" do
70 context.expects(:fetch).with(:repo_tree).returns('tree')
71 context.expects(:fetch).with(:branch).returns(:branch)
72 context.expects(:release_path).returns(:path)
73
74 context.expects(:execute).with(:git, :archive, :branch, 'tree', '| tar -x --strip-components 1 -f - -C', :path)
75
76 subject.release
77 end
78 end
79 end
80 end
+0
-81
spec/lib/capistrano/hg_spec.rb less more
0 require 'spec_helper'
1
2 require 'capistrano/hg'
3
4 module Capistrano
5 describe Hg do
6 let(:context) { Class.new.new }
7 subject { Capistrano::Hg.new(context, Capistrano::Hg::DefaultStrategy) }
8
9 describe "#hg" do
10 it "should call execute hg in the context, with arguments" do
11 context.expects(:execute).with(:hg, :init)
12 subject.hg(:init)
13 end
14 end
15 end
16
17 describe Hg::DefaultStrategy do
18 let(:context) { Class.new.new }
19 subject { Capistrano::Hg.new(context, Capistrano::Hg::DefaultStrategy) }
20
21 describe "#test" do
22 it "should call test for repo HEAD" do
23 context.expects(:repo_path).returns("/path/to/repo")
24 context.expects(:test).with " [ -d /path/to/repo/.hg ] "
25
26 subject.test
27 end
28 end
29
30 describe "#check" do
31 it "should test the repo url" do
32 context.expects(:repo_url).returns(:url)
33 context.expects(:execute).with(:hg, "id", :url)
34
35 subject.check
36 end
37 end
38
39 describe "#clone" do
40 it "should run hg clone" do
41 context.expects(:repo_url).returns(:url)
42 context.expects(:repo_path).returns(:path)
43
44 context.expects(:execute).with(:hg, "clone", '--noupdate', :url, :path)
45
46 subject.clone
47 end
48 end
49
50 describe "#update" do
51 it "should run hg update" do
52 context.expects(:execute).with(:hg, "pull")
53
54 subject.update
55 end
56 end
57
58 describe "#release" do
59 it "should run hg archive without a subtree" do
60 context.expects(:fetch).with(:repo_tree).returns(nil)
61 context.expects(:fetch).with(:branch).returns(:branch)
62 context.expects(:release_path).returns(:path)
63
64 context.expects(:execute).with(:hg, "archive", :path, "--rev", :branch)
65
66 subject.release
67 end
68
69 it "should run hg archive with a subtree" do
70 context.expects(:fetch).with(:repo_tree).returns('tree')
71 context.expects(:fetch).with(:branch).returns(:branch)
72 context.expects(:release_path).returns(:path)
73
74 context.expects(:execute).with(:hg, "archive --type tgz -p . -I", 'tree', "--rev", :branch, '| tar -x --strip-components 1 -f - -C', :path)
75
76 subject.release
77 end
78 end
79 end
80 end
0 require "spec_helper"
1 require "rake"
2 require "capistrano/immutable_task"
3
4 module Capistrano
5 describe ImmutableTask do
6 after do
7 # Ensure that any tasks we create in these tests don't pollute other tests
8 Rake::Task.clear
9 end
10
11 it "prints warning and raises when task is enhanced" do
12 extend(Rake::DSL)
13
14 load_defaults = Rake::Task.define_task("load:defaults")
15 load_defaults.extend(Capistrano::ImmutableTask)
16
17 $stderr.expects(:puts).with do |message|
18 message =~ /^ERROR: load:defaults has already been invoked/
19 end
20
21 expect do
22 namespace :load do
23 task :defaults do
24 # Never reached since load_defaults is frozen and can't be enhanced
25 end
26 end
27 end.to raise_error(/frozen/i)
28 end
29 end
30 end
0 require "spec_helper"
1 require "capistrano/plugin"
2
3 module Capistrano
4 describe Plugin do
5 include Rake::DSL
6 include Capistrano::DSL
7
8 class DummyPlugin < Capistrano::Plugin
9 def define_tasks
10 task :hello do
11 end
12 end
13
14 def register_hooks
15 before "deploy:published", "hello"
16 end
17 end
18
19 class ExternalTasksPlugin < Capistrano::Plugin
20 def define_tasks
21 eval_rakefile(
22 File.expand_path("../../../support/tasks/plugin.rake", __FILE__)
23 )
24 end
25
26 # Called from plugin.rake to demonstrate that helper methods work
27 def hello
28 set :plugin_result, "hello"
29 end
30 end
31
32 before do
33 # Define an example task to allow testing hooks
34 task "deploy:published"
35 end
36
37 after do
38 # Clean up any tasks or variables we created during the tests
39 Rake::Task.clear
40 Capistrano::Configuration.reset!
41 end
42
43 it "defines tasks when constructed" do
44 install_plugin(DummyPlugin)
45 expect(Rake::Task["hello"]).not_to be_nil
46 end
47
48 it "registers hooks when constructed" do
49 install_plugin(DummyPlugin)
50 expect(Rake::Task["deploy:published"].prerequisites).to include("hello")
51 end
52
53 it "skips registering hooks if load_hooks: false" do
54 install_plugin(DummyPlugin, load_hooks: false)
55 expect(Rake::Task["deploy:published"].prerequisites).to be_empty
56 end
57
58 it "doesn't call set_defaults immediately" do
59 dummy = DummyPlugin.new
60 install_plugin(dummy)
61 dummy.expects(:set_defaults).never
62 end
63
64 it "calls set_defaults during load:defaults" do
65 dummy = DummyPlugin.new
66 dummy.expects(:set_defaults).once
67 install_plugin(dummy)
68 Rake::Task["load:defaults"].invoke
69 end
70
71 it "is able to load tasks from a .rake file" do
72 install_plugin(ExternalTasksPlugin)
73 Rake::Task["plugin_test"].invoke
74 expect(fetch(:plugin_result)).to eq("hello")
75 end
76
77 it "exposes the SSHKit backend to subclasses" do
78 SSHKit::Backend.expects(:current).returns(:backend)
79 plugin = DummyPlugin.new
80 expect(plugin.send(:backend)).to eq(:backend)
81 end
82 end
83 end
0 require "spec_helper"
1
2 require "capistrano/scm/git"
3
4 module Capistrano
5 describe SCM::Git do
6 subject { Capistrano::SCM::Git.new }
7
8 # This allows us to easily use `set`, `fetch`, etc. in the examples.
9 let(:env) { Capistrano::Configuration.env }
10
11 # Stub the SSHKit backend so we can set up expectations without the plugin
12 # actually executing any commands.
13 let(:backend) { stub }
14 before { SSHKit::Backend.stubs(:current).returns(backend) }
15
16 # Mimic the deploy flow tasks so that the plugin can register its hooks.
17 before do
18 Rake::Task.define_task("deploy:new_release_path")
19 Rake::Task.define_task("deploy:check")
20 Rake::Task.define_task("deploy:set_current_revision")
21 end
22
23 # Clean up any tasks or variables that the plugin defined.
24 after do
25 Rake::Task.clear
26 Capistrano::Configuration.reset!
27 end
28
29 describe "#set_defaults" do
30 it "makes git_wrapper_path using application, stage, and local_user" do
31 env.set(:tmp_dir, "/tmp")
32 env.set(:application, "my_app")
33 env.set(:stage, "staging")
34 env.set(:local_user, "(Git Web User) via ShipIt")
35 subject.set_defaults
36 expect(env.fetch(:git_wrapper_path)).to eq("/tmp/git-ssh-my_app-staging-(Git Web User) via ShipIt.sh")
37 end
38 end
39
40 describe "#git" do
41 it "should call execute git in the context, with arguments" do
42 backend.expects(:execute).with(:git, :init)
43 subject.git(:init)
44 end
45 end
46
47 describe "#repo_mirror_exists?" do
48 it "should call test for repo HEAD" do
49 env.set(:repo_path, "/path/to/repo")
50 backend.expects(:test).with " [ -f /path/to/repo/HEAD ] "
51
52 subject.repo_mirror_exists?
53 end
54 end
55
56 describe "#check_repo_is_reachable" do
57 it "should test the repo url" do
58 env.set(:repo_url, "url")
59 backend.expects(:execute).with(:git, :'ls-remote', "url", "HEAD").returns(true)
60
61 subject.check_repo_is_reachable
62 end
63 end
64
65 describe "#clone_repo" do
66 it "should run git clone" do
67 env.set(:repo_url, "url")
68 env.set(:repo_path, "path")
69 backend.expects(:execute).with(:git, :clone, "--mirror", "url", "path")
70
71 subject.clone_repo
72 end
73
74 it "should run git clone in shallow mode" do
75 env.set(:git_shallow_clone, "1")
76 env.set(:repo_url, "url")
77 env.set(:repo_path, "path")
78
79 backend.expects(:execute).with(:git, :clone, "--mirror", "--depth", "1", "--no-single-branch", "url", "path")
80
81 subject.clone_repo
82 end
83
84 context "with username and password specified" do
85 before do
86 env.set(:git_http_username, "hello")
87 env.set(:git_http_password, "topsecret")
88 env.set(:repo_url, "https://example.com/repo.git")
89 env.set(:repo_path, "path")
90 end
91
92 it "should include the credentials in the url" do
93 backend.expects(:execute).with(:git, :clone, "--mirror", "https://hello:topsecret@example.com/repo.git", "path")
94 subject.clone_repo
95 end
96 end
97 end
98
99 describe "#update_mirror" do
100 it "should run git update" do
101 env.set(:repo_url, "url")
102
103 backend.expects(:execute).with(:git, :remote, "set-url", "origin", "url")
104 backend.expects(:execute).with(:git, :remote, :update, "--prune")
105
106 subject.update_mirror
107 end
108
109 it "should run git update in shallow mode" do
110 env.set(:git_shallow_clone, "1")
111 env.set(:branch, "branch")
112 env.set(:repo_url, "url")
113
114 backend.expects(:execute).with(:git, :remote, "set-url", "origin", "url")
115 backend.expects(:execute).with(:git, :fetch, "--depth", "1", "origin", "branch")
116
117 subject.update_mirror
118 end
119 end
120
121 describe "#archive_to_release_path" do
122 it "should run git archive without a subtree" do
123 env.set(:branch, "branch")
124 env.set(:release_path, "path")
125
126 backend.expects(:execute).with(:git, :archive, "branch", "| /usr/bin/env tar -x -f - -C", "path")
127
128 subject.archive_to_release_path
129 end
130
131 it "should run git archive with a subtree" do
132 env.set(:repo_tree, "tree")
133 env.set(:branch, "branch")
134 env.set(:release_path, "path")
135
136 backend.expects(:execute).with(:git, :archive, "branch", "tree", "| /usr/bin/env tar -x --strip-components 1 -f - -C", "path")
137
138 subject.archive_to_release_path
139 end
140
141 it "should run tar with an overridden name" do
142 env.set(:branch, "branch")
143 env.set(:release_path, "path")
144 SSHKit.config.command_map.expects(:[]).with(:tar).returns("/usr/bin/env gtar")
145
146 backend.expects(:execute).with(:git, :archive, "branch", "| /usr/bin/env gtar -x -f - -C", "path")
147
148 subject.archive_to_release_path
149 end
150 end
151
152 describe "#fetch_revision" do
153 it "should capture git rev-list" do
154 env.set(:branch, "branch")
155 backend.expects(:capture).with(:git, "rev-list --max-count=1 branch").returns("81cec13b777ff46348693d327fc8e7832f79bf43")
156 revision = subject.fetch_revision
157 expect(revision).to eq("81cec13b777ff46348693d327fc8e7832f79bf43")
158 end
159 end
160 end
161 end
0 require "spec_helper"
1
2 require "capistrano/scm/hg"
3
4 module Capistrano
5 describe SCM::Hg do
6 subject { Capistrano::SCM::Hg.new }
7
8 # This allows us to easily use `set`, `fetch`, etc. in the examples.
9 let(:env) { Capistrano::Configuration.env }
10
11 # Stub the SSHKit backend so we can set up expectations without the plugin
12 # actually executing any commands.
13 let(:backend) { stub }
14 before { SSHKit::Backend.stubs(:current).returns(backend) }
15
16 # Mimic the deploy flow tasks so that the plugin can register its hooks.
17 before do
18 Rake::Task.define_task("deploy:new_release_path")
19 Rake::Task.define_task("deploy:check")
20 Rake::Task.define_task("deploy:set_current_revision")
21 end
22
23 # Clean up any tasks or variables that the plugin defined.
24 after do
25 Rake::Task.clear
26 Capistrano::Configuration.reset!
27 end
28
29 describe "#hg" do
30 it "should call execute hg in the context, with arguments" do
31 backend.expects(:execute).with(:hg, :init)
32 subject.hg(:init)
33 end
34 end
35
36 describe "#repo_mirror_exists?" do
37 it "should call test for repo HEAD" do
38 env.set(:repo_path, "/path/to/repo")
39 backend.expects(:test).with " [ -d /path/to/repo/.hg ] "
40
41 subject.repo_mirror_exists?
42 end
43 end
44
45 describe "#check_repo_is_reachable" do
46 it "should test the repo url" do
47 env.set(:repo_url, :url)
48 backend.expects(:execute).with(:hg, "id", :url)
49
50 subject.check_repo_is_reachable
51 end
52 end
53
54 describe "#clone_repo" do
55 it "should run hg clone" do
56 env.set(:repo_url, :url)
57 env.set(:repo_path, "path")
58
59 backend.expects(:execute).with(:hg, "clone", "--noupdate", :url, "path")
60
61 subject.clone_repo
62 end
63 end
64
65 describe "#update_mirror" do
66 it "should run hg update" do
67 backend.expects(:execute).with(:hg, "pull")
68
69 subject.update_mirror
70 end
71 end
72
73 describe "#archive_to_release_path" do
74 it "should run hg archive without a subtree" do
75 env.set(:branch, :branch)
76 env.set(:release_path, "path")
77
78 backend.expects(:execute).with(:hg, "archive", "path", "--rev", :branch)
79
80 subject.archive_to_release_path
81 end
82
83 it "should run hg archive with a subtree" do
84 env.set(:repo_tree, "tree")
85 env.set(:branch, :branch)
86 env.set(:release_path, "path")
87 env.set(:tmp_dir, "/tmp")
88
89 SecureRandom.stubs(:hex).with(10).returns("random")
90 backend.expects(:execute).with(:hg, "archive -p . -I", "tree", "--rev", :branch, "/tmp/random.tar")
91 backend.expects(:execute).with(:mkdir, "-p", "path")
92 backend.expects(:execute).with(:tar, "-x --strip-components 1 -f", "/tmp/random.tar", "-C", "path")
93 backend.expects(:execute).with(:rm, "/tmp/random.tar")
94
95 subject.archive_to_release_path
96 end
97 end
98
99 describe "#fetch_revision" do
100 it "should capture hg log" do
101 env.set(:branch, :branch)
102 backend.expects(:capture).with(:hg, "log --rev branch --template \"{node}\n\"").returns("01abcde")
103 revision = subject.fetch_revision
104 expect(revision).to eq("01abcde")
105 end
106 end
107 end
108 end
0 require "spec_helper"
1
2 require "capistrano/scm/svn"
3
4 module Capistrano
5 describe SCM::Svn do
6 subject { Capistrano::SCM::Svn.new }
7
8 # This allows us to easily use `set`, `fetch`, etc. in the examples.
9 let(:env) { Capistrano::Configuration.env }
10
11 # Stub the SSHKit backend so we can set up expectations without the plugin
12 # actually executing any commands.
13 let(:backend) { stub }
14 before { SSHKit::Backend.stubs(:current).returns(backend) }
15
16 # Mimic the deploy flow tasks so that the plugin can register its hooks.
17 before do
18 Rake::Task.define_task("deploy:new_release_path")
19 Rake::Task.define_task("deploy:check")
20 Rake::Task.define_task("deploy:set_current_revision")
21 end
22
23 # Clean up any tasks or variables that the plugin defined.
24 after do
25 Rake::Task.clear
26 Capistrano::Configuration.reset!
27 end
28
29 describe "#svn" do
30 it "should call execute svn in the context, with arguments" do
31 env.set(:svn_username, "someuser")
32 env.set(:svn_password, "somepassword")
33 backend.expects(:execute).with(:svn, :init, "--username someuser", "--password somepassword")
34 subject.svn(:init)
35 end
36 end
37
38 describe "#repo_mirror_exists?" do
39 it "should call test for repo HEAD" do
40 env.set(:repo_path, "/path/to/repo")
41 backend.expects(:test).with " [ -d /path/to/repo/.svn ] "
42
43 subject.repo_mirror_exists?
44 end
45 end
46
47 describe "#check_repo_is_reachable" do
48 it "should test the repo url" do
49 env.set(:repo_url, :url)
50 env.set(:svn_username, "someuser")
51 env.set(:svn_password, "somepassword")
52 backend.expects(:test).with(:svn, :info, :url, "--username someuser", "--password somepassword").returns(true)
53
54 subject.check_repo_is_reachable
55 end
56 end
57
58 describe "#clone_repo" do
59 it "should run svn checkout" do
60 env.set(:repo_url, :url)
61 env.set(:repo_path, "path")
62 env.set(:svn_username, "someuser")
63 env.set(:svn_password, "somepassword")
64
65 backend.expects(:execute).with(:svn, :checkout, :url, "path", "--username someuser", "--password somepassword")
66
67 subject.clone_repo
68 end
69 end
70
71 describe "#update_mirror" do
72 it "should run svn update" do
73 env.set(:repo_url, "url")
74 env.set(:repo_path, "path")
75 backend.expects(:capture).with(:svn, :info, "path").returns("URL: url\n")
76
77 env.set(:svn_username, "someuser")
78 env.set(:svn_password, "somepassword")
79 backend.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword")
80
81 subject.update_mirror
82 end
83
84 context "for specific revision" do
85 it "should run svn update" do
86 env.set(:repo_url, "url")
87 env.set(:repo_path, "path")
88 backend.expects(:capture).with(:svn, :info, "path").returns("URL: url\n")
89
90 env.set(:svn_username, "someuser")
91 env.set(:svn_password, "somepassword")
92 env.set(:svn_revision, "12345")
93 backend.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword", "--revision 12345")
94
95 subject.update_mirror
96 end
97 end
98
99 it "should run svn switch if repo_url is changed" do
100 env.set(:repo_url, "url")
101 env.set(:repo_path, "path")
102 backend.expects(:capture).with(:svn, :info, "path").returns("URL: old_url\n")
103
104 env.set(:svn_username, "someuser")
105 env.set(:svn_password, "somepassword")
106 backend.expects(:execute).with(:svn, :switch, "url", "--username someuser", "--password somepassword")
107 backend.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword")
108
109 subject.update_mirror
110 end
111 end
112
113 describe "#archive_to_release_path" do
114 it "should run svn export" do
115 env.set(:release_path, "path")
116 env.set(:svn_username, "someuser")
117 env.set(:svn_password, "somepassword")
118
119 backend.expects(:execute).with(:svn, :export, "--force", ".", "path", "--username someuser", "--password somepassword")
120
121 subject.archive_to_release_path
122 end
123 end
124
125 describe "#fetch_revision" do
126 it "should capture svn version" do
127 env.set(:repo_path, "path")
128
129 backend.expects(:capture).with(:svnversion, "path").returns("12345")
130
131 revision = subject.fetch_revision
132 expect(revision).to eq("12345")
133 end
134 end
135 end
136 end
0 require 'spec_helper'
0 require "spec_helper"
11
2 require 'capistrano/scm'
2 require "capistrano/scm"
33
44 module RaiseNotImplementedMacro
55 def raise_not_implemented_on(method)
66 it "should raise NotImplemented on #{method}" do
7 expect {
7 expect do
88 subject.send(method)
9 }.to raise_error(NotImplementedError)
9 end.to raise_error(NotImplementedError)
1010 end
1111 end
1212 end
2525
2626 module Capistrano
2727 describe SCM do
28 let(:context) { Class.new.new }
28 let(:context) { mock }
2929
3030 describe "#initialize" do
3131 subject { Capistrano::SCM.new(context, DummyStrategy) }
6262
6363 describe "#release_path" do
6464 it "should return the release path according to the context" do
65 context.expects(:release_path).returns('/path/to/nowhere')
66 expect(subject.release_path).to eq('/path/to/nowhere')
65 context.expects(:release_path).returns("/path/to/nowhere")
66 expect(subject.release_path).to eq("/path/to/nowhere")
6767 end
6868 end
6969
100100 end
101101 end
102102 end
103
+0
-79
spec/lib/capistrano/svn_spec.rb less more
0 require 'spec_helper'
1
2 require 'capistrano/svn'
3
4 module Capistrano
5 describe Svn do
6 let(:context) { Class.new.new }
7 subject { Capistrano::Svn.new(context, Capistrano::Svn::DefaultStrategy) }
8
9 describe "#svn" do
10 it "should call execute svn in the context, with arguments" do
11 context.expects(:execute).with(:svn, :init)
12 subject.svn(:init)
13 end
14 end
15 end
16
17 describe Svn::DefaultStrategy do
18 let(:context) { Class.new.new }
19 subject { Capistrano::Svn.new(context, Capistrano::Svn::DefaultStrategy) }
20
21 describe "#test" do
22 it "should call test for repo HEAD" do
23 context.expects(:repo_path).returns("/path/to/repo")
24 context.expects(:test).with " [ -d /path/to/repo/.svn ] "
25
26 subject.test
27 end
28 end
29
30 describe "#check" do
31 it "should test the repo url" do
32 context.expects(:repo_url).returns(:url)
33 context.expects(:test).with(:svn, :info, :url).returns(true)
34
35 subject.check
36 end
37 end
38
39 describe "#clone" do
40 it "should run svn checkout" do
41 context.expects(:repo_url).returns(:url)
42 context.expects(:repo_path).returns(:path)
43
44 context.expects(:execute).with(:svn, :checkout, :url, :path)
45
46 subject.clone
47 end
48 end
49
50 describe "#update" do
51 it "should run svn update" do
52 context.expects(:execute).with(:svn, :update)
53
54 subject.update
55 end
56 end
57
58 describe "#release" do
59 it "should run svn export" do
60 context.expects(:release_path).returns(:path)
61
62 context.expects(:execute).with(:svn, :export, '--force', '.', :path)
63
64 subject.release
65 end
66 end
67
68 describe "#fetch_revision" do
69 it "should run fetch revision" do
70 context.expects(:repo_path).returns(:path)
71
72 context.expects(:capture).with(:svnversion, :path)
73
74 subject.fetch_revision
75 end
76 end
77 end
78 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 describe Capistrano::UploadTask do
33 let(:app) { Rake.application = Rake::Application.new }
44
5 subject(:upload_task) { described_class.define_task('path/file.yml') }
5 subject(:upload_task) { described_class.define_task("path/file.yml") }
66
77 it { is_expected.to be_a(Rake::FileCreationTask) }
88 it { is_expected.to be_needed }
99
10 context 'inside namespace' do
11 let(:normal_task) { Rake::Task.define_task('path/other_file.yml') }
10 context "inside namespace" do
11 let(:normal_task) { Rake::Task.define_task("path/other_file.yml") }
1212
13 around { |ex| app.in_namespace('namespace', &ex) }
13 around { |ex| app.in_namespace("namespace", &ex) }
1414
15 it { expect(upload_task.name).to eq('path/file.yml') }
16 it { expect(upload_task.scope.path).to eq('namespace') }
15 it { expect(upload_task.name).to eq("path/file.yml") }
16 it { expect(upload_task.scope.path).to eq("namespace") }
1717 end
1818 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
3
43 describe VersionValidator do
54 let(:validator) { VersionValidator.new(version) }
65 let(:version) { stub }
76
8 describe '#new' do
9 it 'takes a version' do
7 describe "#new" do
8 it "takes a version" do
109 expect(validator)
1110 end
1211 end
1312
14 describe '#verify' do
15 let(:current_version) { '3.0.1' }
13 describe "#verify" do
14 let(:current_version) { "3.0.1" }
1615
1716 subject { validator.verify }
1817
2019 validator.stubs(:current_version).returns(current_version)
2120 end
2221
23 context 'with exact version' do
24 context 'valid' do
25 let(:version) { '3.0.1' }
22 context "with exact version" do
23 context "valid" do
24 let(:version) { "3.0.1" }
2625 it { expect(subject).to be_truthy }
2726 end
2827
29 context 'invalid - lower' do
30 let(:version) { '3.0.0' }
28 context "invalid - lower" do
29 let(:version) { "3.0.0" }
3130
32 it 'fails' do
33 expect { subject }.to raise_error
31 it "fails" do
32 expect { subject }.to raise_error(RuntimeError)
3433 end
3534 end
3635
37 context 'invalid - higher' do
38 let(:version) { '3.0.2' }
36 context "invalid - higher" do
37 let(:version) { "3.0.2" }
3938
40 it 'fails' do
41 expect { subject }.to raise_error
42 end
43 end
44
45 end
46
47 context 'with optimistic versioning' do
48 context 'valid' do
49 let(:version) { '>= 3.0.0' }
50 it { expect(subject).to be_truthy }
51 end
52
53 context 'invalid - lower' do
54 let(:version) { '<= 2.0.0' }
55
56 it 'fails' do
57 expect { subject }.to raise_error
39 it "fails" do
40 expect { subject }.to raise_error(RuntimeError)
5841 end
5942 end
6043 end
6144
45 context "with optimistic versioning" do
46 context "valid" do
47 let(:version) { ">= 3.0.0" }
48 it { expect(subject).to be_truthy }
49 end
6250
51 context "invalid - lower" do
52 let(:version) { "<= 2.0.0" }
6353
64 context 'with pessimistic versioning' do
65 context '2 decimal places' do
66 context 'valid' do
67 let(:version) { '~> 3.0.0' }
54 it "fails" do
55 expect { subject }.to raise_error(RuntimeError)
56 end
57 end
58 end
59
60 context "with pessimistic versioning" do
61 context "2 decimal places" do
62 context "valid" do
63 let(:version) { "~> 3.0.0" }
6864 it { expect(subject).to be_truthy }
6965 end
7066
71 context 'invalid' do
72 let(:version) { '~> 3.1.0' }
67 context "invalid" do
68 let(:version) { "~> 3.1.0" }
7369
74 it 'fails' do
75 expect { subject }.to raise_error
70 it "fails" do
71 expect { subject }.to raise_error(RuntimeError)
7672 end
7773 end
7874 end
7975
80 context '1 decimal place' do
81 let(:current_version) { '3.5.0' }
76 context "1 decimal place" do
77 let(:current_version) { "3.5.0" }
8278
83 context 'valid' do
84 let(:version) { '~> 3.1' }
79 context "valid" do
80 let(:version) { "~> 3.1" }
8581 it { expect(subject).to be_truthy }
8682 end
8783
88 context 'invalid' do
89 let(:version) { '~> 3.6' }
90 it 'fails' do
91 expect { subject }.to raise_error
84 context "invalid" do
85 let(:version) { "~> 3.6" }
86 it "fails" do
87 expect { subject }.to raise_error(RuntimeError)
9288 end
9389 end
9490 end
9591
92 context "with multiple versions" do
93 let(:current_version) { "3.5.9" }
94
95 context "valid" do
96 let(:version) { [">= 3.5.0", "< 3.5.10"] }
97 it { is_expected.to be_truthy }
98 end
99
100 context "invalid" do
101 let(:version) { [">= 3.5.0", "< 3.5.8"] }
102 it "fails" do
103 expect { subject }.to raise_error(RuntimeError)
104 end
105 end
106
107 context "invalid" do
108 let(:version) { ["> 3.5.9", "< 3.5.13"] }
109 it "fails" do
110 expect { subject }.to raise_error(RuntimeError)
111 end
112 end
113 end
96114 end
97
98115 end
99
100116 end
101
102117 end
0 require 'spec_helper'
0 require "spec_helper"
11
22 module Capistrano
3
43 describe Application do
5 let(:app) { Application.new }
4 let(:app) { Application.new }
65 end
76 end
0 $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
0 $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
11 $LOAD_PATH.unshift(File.dirname(__FILE__))
2 require 'capistrano/all'
3 require 'rspec'
4 require 'mocha/api'
5 require 'time'
2 require "capistrano/all"
3 require "rspec"
4 require "mocha/api"
5 require "time"
66
77 # Requires supporting files with custom matchers and macros, etc,
88 # in ./support/ and its subdirectories.
9 Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each {|f| require f}
9 Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each { |f| require f }
1010
1111 RSpec.configure do |config|
12 config.raise_errors_for_deprecations!
12 config.raise_errors_for_deprecations!
1313 config.mock_framework = :mocha
14 config.order = 'random'
14 config.order = "random"
1515 end
0 require 'open-uri'
0 require "open-uri"
11
22 Vagrant.configure("2") do |config|
3
43 config.ssh.insert_key = false
54
65 [:app].each_with_index do |role, i|
7 config.vm.define(role, primary: true) do |config|
8 config.vm.define role
9 config.vm.box = 'hashicorp/precise64'
10 config.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i
11 config.vm.provision :shell, inline: 'sudo apt-get -y install git-core'
6 config.vm.define(role, primary: true) do |primary|
7 primary.vm.define role
8 primary.vm.box = "hashicorp/precise64"
9 primary.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i
10 primary.vm.provision :shell, inline: "sudo apt-get -y install git-core"
1211
13 vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r",&:read)
12 vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r", &:read)
1413
15 config.vm.provision :shell,
16 inline: <<-INLINE
14 primary.vm.provision :shell,
15 inline: <<-INLINE
1716 install -d -m 700 /root/.ssh
1817 echo -e "#{vagrantkey}" > /root/.ssh/authorized_keys
1918 chmod 0600 /root/.ssh/authorized_keys
00 namespace :deploy do
11 namespace :check do
2 task :linked_files => 'config/database.yml'
2 task linked_files: "config/database.yml"
33 end
44 end
55
6 remote_file 'config/database.yml' => '/tmp/database.yml', roles: :all
6 remote_file "config/database.yml" => "/tmp/database.yml", :roles => :all
77
8 file '/tmp/database.yml' do |t|
8 file "/tmp/database.yml" do |t|
99 sh "touch #{t.name}"
1010 end
0 set :fail, proc { fail }
1 before 'deploy:starting', :fail do
0 set :fail, proc { raise }
1 before "deploy:starting", :fail do
22 on roles :all do
3 execute :touch, shared_path.join('fail')
3 execute :mkdir, "-p", shared_path
4 execute :touch, shared_path.join("fail")
45 end
56 fetch(:fail)
67 end
0 after 'deploy:failed', :failed do
0 after "deploy:failed", :custom_failed do
11 on roles :all do
2 execute :touch, shared_path.join('failed')
2 execute :touch, shared_path.join("failed")
33 end
44 end
0 # This rake file is used by plugin_spec.rb.
1
2 task :plugin_test do
3 # Example of invoking a helper method provided by the plugin
4 hello
5 end
00 task :am_i_root do
11 on roles(:all) do |host|
2 host.user = 'root'
3 ident = capture :id, '-a'
2 host.user = "root"
3 ident = capture :id, "-a"
44 info "I am #{ident}"
55 end
6 on roles(:all) do |host|
7 ident = capture :id, '-a'
6 on roles(:all) do |_host|
7 ident = capture :id, "-a"
88 info "I am #{ident}"
99 end
1010 end
0 require 'fileutils'
1 require 'pathname'
0 require "English"
1 require "fileutils"
2 require "pathname"
23
34 module TestApp
45 extend self
89 end
910
1011 def default_config
11 %{
12 <<-CONFIG
1213 set :deploy_to, '#{deploy_to}'
1314 set :repo_url, 'git://github.com/capistrano/capistrano.git'
1415 set :branch, 'master'
1617 server 'vagrant@localhost:2220', roles: %w{web app}
1718 set :linked_files, #{linked_files}
1819 set :linked_dirs, #{linked_dirs}
19 }
20 set :format_options, log_file: nil
21 set :local_user, #{current_user.inspect}
22 CONFIG
2023 end
2124
2225 def linked_files
2831 end
2932
3033 def linked_dirs
31 %w{bin log public/system vendor/bundle}
34 %w{bin log public/system}
3235 end
3336
3437 def create_test_app
3538 FileUtils.rm_rf(test_app_path)
3639 FileUtils.mkdir(test_app_path)
3740
38 File.open(gemfile, 'w+') do |file|
41 File.open(gemfile, "w+") do |file|
3942 file.write "gem 'capistrano', path: '#{path_to_cap}'"
4043 end
4144
4245 Dir.chdir(test_app_path) do
43 %x[bundle]
46 run "bundle"
4447 end
4548 end
4649
4750 def install_test_app_with(config)
4851 create_test_app
4952 Dir.chdir(test_app_path) do
50 %x[bundle exec cap install STAGES=#{stage}]
53 run "cap install STAGES=#{stage}"
5154 end
5255 write_local_deploy_file(config)
5356 end
5457
5558 def write_local_deploy_file(config)
56 File.open(test_stage_path, 'w') do |file|
59 File.open(test_stage_path, "w") do |file|
5760 file.write config
5861 end
5962 end
6063
64 def write_local_stage_file(filename, config=nil)
65 File.open(test_app_path.join("config/deploy/#{filename}"), "w") do |file|
66 file.write(config) if config
67 end
68 end
69
6170 def append_to_deploy_file(config)
62 File.open(test_stage_path, 'a') do |file|
71 File.open(test_stage_path, "a") do |file|
6372 file.write config + "\n"
6473 end
6574 end
6675
6776 def prepend_to_capfile(config)
6877 current_capfile = File.read(capfile)
69 File.open(capfile, 'w') do |file|
78 File.open(capfile, "w") do |file|
7079 file.write config
7180 file.write current_capfile
7281 end
7786 end
7887
7988 def create_shared_file(path)
80 File.open(shared_path.join(path), 'w')
89 File.open(shared_path.join(path), "w")
8190 end
8291
83 def cap(task)
84 run "bundle exec cap #{stage} #{task}"
92 def cap(task, subdirectory=nil)
93 run "cap #{stage} #{task} --trace", subdirectory
8594 end
8695
87 def run(command)
96 def run(command, subdirectory=nil)
8897 output = nil
89 Dir.chdir(test_app_path) do
90 output = %x[#{command}]
98 command = "bundle exec #{command}" unless command =~ /^bundle\b/
99 dir = subdirectory ? test_app_path.join(subdirectory) : test_app_path
100 Dir.chdir(dir) do
101 output = with_clean_bundler_env { `#{command}` }
91102 end
92 [$?.success?, output]
103 [$CHILD_STATUS.success?, output]
93104 end
94105
95106 def stage
96 'test'
107 "test"
97108 end
98109
99110 def test_stage_path
100 test_app_path.join('config/deploy/test.rb')
111 test_app_path.join("config/deploy/test.rb")
101112 end
102113
103114 def test_app_path
104 Pathname.new('/tmp/test_app')
115 Pathname.new("/tmp/test_app")
105116 end
106117
107118 def deploy_to
108 Pathname.new('/home/vagrant/var/www/deploy')
119 Pathname.new("/home/vagrant/var/www/deploy")
109120 end
110121
111122 def shared_path
112 deploy_to.join('shared')
123 deploy_to.join("shared")
113124 end
114125
115126 def current_path
116 deploy_to.join('current')
127 deploy_to.join("current")
117128 end
118129
119130 def releases_path
120 deploy_to.join('releases')
131 deploy_to.join("releases")
121132 end
122133
123 def release_path
124 releases_path.join(timestamp)
134 def release_path(t=timestamp)
135 releases_path.join(t)
125136 end
126137
127 def timestamp
128 Time.now.utc.strftime("%Y%m%d%H%M%S")
138 def timestamp(offset=0)
139 (Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
129140 end
130141
131142 def repo_path
132 deploy_to.join('repo')
143 deploy_to.join("repo")
133144 end
134145
135146 def path_to_cap
136 File.expand_path('.')
147 File.expand_path(".")
137148 end
138149
139150 def gemfile
140 test_app_path.join('Gemfile')
151 test_app_path.join("Gemfile")
141152 end
142153
143154 def capfile
144 test_app_path.join('Capfile')
155 test_app_path.join("Capfile")
145156 end
146157
147158 def current_user
148 `whoami`.chomp
159 "(GitHub Web Flow) via ShipIt"
149160 end
150161
151162 def task_dir
152 test_app_path.join('lib/capistrano/tasks')
163 test_app_path.join("lib/capistrano/tasks")
153164 end
154165
155166 def copy_task_to_test_app(source)
157168 end
158169
159170 def config_path
160 test_app_path.join('config')
171 test_app_path.join("config")
161172 end
162173
163174 def move_configuration_to_custom_location(location)
164175 prepend_to_capfile(
165 %{
176 <<-CONFIG
166177 set :stage_config_path, "app/config/deploy"
167178 set :deploy_config_path, "app/config/deploy.rb"
168 }
179 CONFIG
169180 )
170181
171182 location = test_app_path.join(location)
173184 FileUtils.mv(config_path, location)
174185 end
175186
187 def git_wrapper_path
188 "/tmp/git-ssh-my_app_name-#{stage}-#{current_user}.sh"
189 end
190
191 def with_clean_bundler_env(&block)
192 return yield unless defined?(Bundler)
193 Bundler.with_clean_env(&block)
194 end
176195 end