New upstream version 3.10.0
Samuel Henrique
6 years ago
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! |
0 | 0 | *.gem |
1 | 1 | *.rbc |
2 | *.swp | |
2 | 3 | .bundle |
3 | 4 | .config |
5 | .rspec | |
6 | .rspec-local | |
7 | .ruby-version | |
4 | 8 | .yardoc |
5 | 9 | Gemfile.lock |
6 | 10 | InstalledFiles |
11 | _site | |
7 | 12 | _yardoc |
8 | 13 | coverage |
9 | 14 | doc/ |
11 | 16 | pkg |
12 | 17 | rdoc |
13 | 18 | spec/reports |
19 | tags | |
14 | 20 | test/tmp |
15 | 21 | test/version_tmp |
16 | 22 | 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 |
0 | 0 | language: ruby |
1 | 1 | 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 | |
7 | 21 | install: bundle install --jobs=1 |
8 | 22 | cache: bundler |
9 | 23 | branches: |
0 | 0 | # Capistrano 3.x Changelog |
1 | 1 | |
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 | |
11 | 325 | |
12 | 326 | * Fixed fetch revision for annotated git tags. (@igorsokolov) |
13 | 327 | * Fixed updating roles when custom user or port is specified. (@ayastreb) |
24 | 338 | * Breaking Changes |
25 | 339 | * Hosts with the same name are now consolidated into one irrespective of the |
26 | 340 | 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 | |
28 | 342 | for details. |
29 | 343 | * Inside the on() block the host variable is now a copy of the host, so changes can be |
30 | 344 | made within the block (such as dynamically overriding the user) that will not persist. |
39 | 353 | * Allow specification of repo_path using stage variable |
40 | 354 | default is as before (@townsen) |
41 | 355 | |
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 | |
45 | 359 | |
46 | 360 | * Fixed setting properties twice when creating new server. See [issue |
47 | 361 | #1214](https://github.com/capistrano/capistrano/issues/1214) (@ayastreb) |
48 | 362 | |
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 | |
52 | 366 | |
53 | 367 | * Minor changes: |
54 | 368 | * Rely on a newer version of capistrano-stats with better privacy (@leehambley) |
57 | 371 | * Spec improvements (@dimitrid, @sponomarev) |
58 | 372 | * Fix to CLI flags for git-ls-remote (@dimitrid) |
59 | 373 | |
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 | |
63 | 377 | |
64 | 378 | * Enhancement (@townsen) |
65 | 379 | * Added the variable `:repo_tree` which allows the specification of a sub-tree that |
143 | 457 | * Updated svn fetch_revision method to use `svnversion` |
144 | 458 | * `cap install` no longer overwrites existing files. (@dmarkow) |
145 | 459 | |
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 | |
149 | 463 | |
150 | 464 | * Bug Fixes: |
151 | 465 | * 3.2.0 introduced some behaviour to modify the way before/after hooks were called, to allow the optional |
160 | 474 | * Added `keys` method to Configuration to allow introspection of configuration options. (@juanibiapina) |
161 | 475 | * Improve error message when git:check fails (raise instead of silently `exit 1`) (@mbrictson) |
162 | 476 | |
163 | ## `3.2.0` | |
477 | ## [`3.2.0`] | |
164 | 478 | |
165 | 479 | The changelog entries here are incomplete, because many authors choose not to |
166 | 480 | be credited for their work, check the tag comparison link for Github. |
167 | 481 | |
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 | |
169 | 483 | |
170 | 484 | * Minor changes: |
171 | 485 | * Added `keys` method to Server properties to allow introspection of automatically added |
173 | 487 | * Compatibility with Rake 10.2.0 - `ensure_task` is now added to `@top_level_tasks` as a string. (@dmarkow) |
174 | 488 | * Amended the git check command, "ls-remote", to use "-h", limiting the list to refs/heads |
175 | 489 | |
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 | |
179 | 493 | |
180 | 494 | Breaking changes: |
181 | 495 | |
209 | 523 | |
210 | 524 | Big thanks to @Kriechi for his help. |
211 | 525 | |
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 | |
215 | 529 | |
216 | 530 | * `capify` not listed as executable (@leehambley) |
217 | 531 | * Confirm license as MIT (@leehambley) |
218 | 532 | * Move the git ssh helper to application path (@mpapis) |
219 | 533 | |
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 | |
223 | 537 | |
224 | 538 | If you are coming here to wonder why your Capfile doesn't work anymore, please |
225 | 539 | 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. | |
1 | 1 | |
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) | |
4 | 6 | |
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 | |
10 | 8 | |
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.) | |
12 | 10 | |
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. | |
19 | 12 | |
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: | |
23 | 14 | |
24 | ## When Submitting An Issue: | |
15 | * Capistrano version | |
16 | * Plugins and versions (capistrano-rvm, capistrano-bundler, etc.) | |
17 | * Logs and backtraces | |
25 | 18 | |
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… | |
31 | 20 | |
32 | Please make sure you have reviewed the FAQs at http://www.capistranorb.com/. | |
21 | ## Reporting bugs | |
33 | 22 | |
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! | |
39 | 24 | |
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).** | |
44 | 26 | |
45 | ## When Requesting a Feature: | |
27 | When opening a bug report, please include the output of the `cap <stage> doctor` task, e.g.: | |
46 | 28 | |
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 | ``` | |
50 | 32 | |
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: | |
58 | 34 | |
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 | |
62 | 40 | |
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!* | |
64 | 42 | |
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>. | |
70 | 44 | |
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" | |
1 | 1 | |
2 | 2 | # Specify your gem's dependencies in capistrano.gemspec |
3 | 3 | gemspec |
4 | 4 | |
5 | 5 | 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" | |
8 | 9 | end |
0 | 0 | MIT License (MIT) |
1 | 1 | |
2 | Copyright (c) 2012-2013 Tom Clements, Lee Hambley | |
2 | Copyright (c) 2012-2016 Tom Clements, Lee Hambley | |
3 | 3 | |
4 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | 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: | |
27 | 106 | |
28 | 107 | ``` 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: | |
33 | 114 | |
34 | 115 | ``` sh |
35 | 116 | $ bundle install |
36 | 117 | ``` |
37 | 118 | |
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 | ||
40 | 123 | ``` sh |
41 | 124 | $ bundle exec cap install |
42 | 125 | ``` |
43 | 126 | |
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`: | |
45 | 128 | |
46 | 129 | ``` |
47 | 130 | ├── Capfile |
55 | 138 | └── tasks |
56 | 139 | ``` |
57 | 140 | |
58 | To create different stages: | |
141 | To customize the stages that are created, use: | |
59 | 142 | |
60 | 143 | ``` sh |
61 | 144 | $ bundle exec cap install STAGES=local,sandbox,qa,production |
62 | 145 | ``` |
63 | 146 | |
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 | |
65 | 150 | |
66 | 151 | ``` sh |
67 | 152 | # list all available tasks |
82 | 167 | |
83 | 168 | # trace through task invocations |
84 | 169 | $ 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. | |
114 | 197 | |
115 | 198 | ## License |
116 | 199 |
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. |
0 | 0 | require "bundler/gem_tasks" |
1 | 1 | require "cucumber/rake/task" |
2 | 2 | require "rspec/core/rake_task" |
3 | require "rubocop/rake_task" | |
3 | 4 | |
4 | task :default => :spec | |
5 | task default: %i(spec rubocop) | |
5 | 6 | RSpec::Core::RakeTask.new |
6 | 7 | |
7 | 8 | Cucumber::Rake::Task.new(:features) |
8 | 9 | |
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. |
0 | 0 | #!/usr/bin/env ruby |
1 | require 'capistrano/all' | |
1 | require "capistrano/all" | |
2 | 2 | Capistrano::Application.new.run |
0 | 0 | # -*- encoding: utf-8 -*- |
1 | lib = File.expand_path('../lib', __FILE__) | |
1 | ||
2 | lib = File.expand_path("../lib", __FILE__) | |
2 | 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) |
3 | require 'capistrano/version' | |
4 | require "capistrano/version" | |
4 | 5 | |
5 | 6 | Gem::Specification.new do |gem| |
6 | 7 | gem.name = "capistrano" |
7 | 8 | gem.version = Capistrano::VERSION |
8 | 9 | gem.authors = ["Tom Clements", "Lee Hambley"] |
9 | 10 | 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" | |
12 | 13 | gem.homepage = "http://capistranorb.com/" |
13 | 14 | |
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) | |
16 | 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) |
17 | 18 | gem.require_paths = ["lib"] |
18 | 19 | |
19 | gem.licenses = ['MIT'] | |
20 | gem.licenses = ["MIT"] | |
20 | 21 | |
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" | |
23 | 27 | |
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" | |
36 | 32 | end |
6 | 6 | Scenario: Creating the repo |
7 | 7 | When I run cap "git:check" |
8 | 8 | Then the task is successful |
9 | And references in the remote repo are listed | |
9 | And git wrapper permissions are 0700 | |
10 | 10 | |
11 | 11 | Scenario: Creating the directory structure |
12 | 12 | When I run cap "deploy:check:directories" |
51 | 51 | When I run cap "deploy:symlink:release" |
52 | 52 | Then the current directory will be a symlink to the release |
53 | 53 | |
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 |
0 | 0 | Feature: Installation |
1 | 1 | |
2 | 2 | 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 | |
4 | 9 | |
5 | 10 | Scenario: With default stages |
6 | When I run cap "install" | |
11 | When I run "cap install" | |
7 | 12 | Then the deploy.rb file is created |
8 | 13 | And the default stage files are created |
9 | 14 | And the tasks folder is created |
10 | 15 | |
11 | 16 | Scenario: With specified stages |
12 | When I run cap "install STAGES=qa,production" | |
17 | When I run "cap install STAGES=qa,production" | |
13 | 18 | Then the deploy.rb file is created |
14 | 19 | And the specified stage files are created |
15 | 20 | And the tasks folder is created |
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 | ||
0 | 2 | 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 | |
2 | 11 | end |
3 | 12 | |
4 | 13 | Then(/^the shared path is created$/) do |
9 | 18 | run_vagrant_command(test_dir_exists(TestApp.releases_path)) |
10 | 19 | end |
11 | 20 | |
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 | ||
12 | 33 | Then(/^directories in :linked_dirs are created in shared$/) do |
13 | 34 | TestApp.linked_dirs.each do |dir| |
14 | 35 | run_vagrant_command(test_dir_exists(TestApp.shared_path.join(dir))) |
17 | 38 | |
18 | 39 | Then(/^directories referenced in :linked_files are created in shared$/) do |
19 | 40 | dirs = TestApp.linked_files.map { |path| TestApp.shared_path.join(path).dirname } |
20 | dirs.each do | dir| | |
41 | dirs.each do |dir| | |
21 | 42 | run_vagrant_command(test_dir_exists(dir)) |
22 | 43 | end |
23 | 44 | end |
44 | 65 | end |
45 | 66 | |
46 | 67 | 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)) | |
48 | 69 | end |
49 | 70 | |
50 | 71 | 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 | |
53 | 74 | end |
54 | 75 | |
55 | 76 | 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 | |
60 | 81 | end |
61 | 82 | |
62 | 83 | 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 | |
65 | 86 | end |
66 | 87 | |
67 | 88 | 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 | |
72 | 93 | end |
73 | 94 | |
74 | 95 | Then(/^it creates the file with the remote_task prerequisite$/) do |
90 | 111 | end |
91 | 112 | |
92 | 113 | Then(/^the failure task will run$/) do |
93 | failed = TestApp.shared_path.join('failed') | |
114 | failed = TestApp.shared_path.join("failed") | |
94 | 115 | run_vagrant_command(test_file_exists(failed)) |
95 | 116 | end |
96 | 117 | |
97 | 118 | Then(/^the failure task will not run$/) do |
98 | failed = TestApp.shared_path.join('failed') | |
119 | failed = TestApp.shared_path.join("failed") | |
99 | 120 | expect { run_vagrant_command(test_file_exists(failed)) } |
100 | 121 | .to raise_error(VagrantHelpers::VagrantSSHCommandError) |
101 | 122 | end |
102 | 123 | |
103 | 124 | When(/^an error is raised$/) do |
104 | error = TestApp.shared_path.join('fail') | |
125 | error = TestApp.shared_path.join("fail") | |
105 | 126 | run_vagrant_command(test_file_exists(error)) |
106 | 127 | end |
107 | 128 | |
116 | 137 | Then(/doesn't contain "([^"]*)" in the output/) do |expected| |
117 | 138 | expect(@output).not_to include(expected) |
118 | 139 | 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 |
0 | 0 | When(/^I run cap "(.*?)"$/) do |task| |
1 | 1 | @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) | |
2 | 6 | end |
3 | 7 | |
4 | 8 | When(/^I run cap "(.*?)" as part of a release$/) do |task| |
9 | 13 | @success, @output = TestApp.run(command) |
10 | 14 | end |
11 | 15 | |
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 |
1 | 1 | TestApp.install |
2 | 2 | end |
3 | 3 | |
4 | Given(/^a test app without any configuration$/) do | |
5 | TestApp.create_test_app | |
6 | end | |
7 | ||
4 | 8 | 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 | |
6 | 14 | end |
7 | 15 | |
8 | 16 | Given(/^a linked file "(.*?)"$/) do |file| |
12 | 20 | |
13 | 21 | Given(/^file "(.*?)" exists in shared path$/) do |file| |
14 | 22 | 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}") | |
16 | 24 | 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 | |
17 | 31 | end |
18 | 32 | |
19 | 33 | Given(/^file "(.*?)" does not exist in shared path$/) do |file| |
23 | 37 | end |
24 | 38 | |
25 | 39 | 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") | |
27 | 41 | end |
28 | 42 | |
29 | 43 | 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") | |
31 | 45 | end |
32 | 46 | |
33 | 47 | Given(/config stage file has line "(.*?)"/) do |line| |
35 | 49 | end |
36 | 50 | |
37 | 51 | 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") | |
39 | 53 | end |
40 | 54 | |
41 | 55 | 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") | |
44 | 58 | end |
45 | 59 | |
46 | 60 | 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") | |
49 | 63 | 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" | |
3 | 3 | |
4 | 4 | at_exit do |
5 | if ENV['KEEP_RUNNING'] | |
5 | if ENV["KEEP_RUNNING"] | |
6 | 6 | VagrantHelpers.run_vagrant_command("rm -rf /home/vagrant/var") |
7 | 7 | end |
8 | 8 | end |
9 | 9 | |
10 | require_relative '../../spec/support/test_app' | |
10 | require_relative "../../spec/support/test_app" |
0 | 0 | module RemoteCommandHelpers |
1 | 1 | def test_dir_exists(path) |
2 | exists?('d', path) | |
2 | exists?("d", path) | |
3 | 3 | end |
4 | 4 | |
5 | 5 | def test_symlink_exists(path) |
6 | exists?('L', path) | |
6 | exists?("L", path) | |
7 | 7 | end |
8 | 8 | |
9 | 9 | def test_file_exists(path) |
10 | exists?('f', path) | |
10 | exists?("f", path) | |
11 | 11 | end |
12 | 12 | |
13 | 13 | def exists?(type, path) |
14 | %{[ -#{type} "#{path}" ]} | |
14 | %Q{[ -#{type} "#{path}" ]} | |
15 | 15 | end |
16 | 16 | |
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 | |
19 | 25 | end |
20 | 26 | end |
21 | 27 |
0 | require "open3" | |
1 | ||
0 | 2 | module VagrantHelpers |
1 | 3 | extend self |
2 | 4 | |
3 | 5 | class VagrantSSHCommandError < RuntimeError; end |
4 | 6 | |
5 | 7 | at_exit do |
6 | if ENV['KEEP_RUNNING'] | |
8 | if ENV["KEEP_RUNNING"] | |
7 | 9 | puts "Vagrant vm will be left up because KEEP_RUNNING is set." |
8 | 10 | puts "Rerun without KEEP_RUNNING set to cleanup the vm." |
9 | 11 | else |
13 | 15 | |
14 | 16 | def vagrant_cli_command(command) |
15 | 17 | 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}") | |
20 | 20 | end |
21 | $? | |
21 | ||
22 | (stdout + stderr).each_line { |line| puts "[vagrant] #{line}" } | |
23 | ||
24 | [stdout, stderr, status] | |
22 | 25 | end |
23 | 26 | |
24 | 27 | 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 | |
30 | 31 | end |
31 | ||
32 | 32 | end |
33 | 33 | |
34 | 34 | World(VagrantHelpers) |
0 | 0 | #!/usr/bin/env cap |
1 | 1 | include Capistrano::DSL |
2 | require 'capistrano/install' | |
2 | require "capistrano/install" |
0 | require 'rake' | |
1 | require 'sshkit' | |
0 | require "rake" | |
1 | require "sshkit" | |
2 | 2 | |
3 | require 'io/console' | |
3 | require "io/console" | |
4 | 4 | |
5 | 5 | Rake.application.options.trace = true |
6 | 6 | |
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" | |
13 | 14 | |
14 | 15 | module Capistrano |
15 | ||
16 | 16 | end |
0 | 0 | module Capistrano |
1 | 1 | class Application < Rake::Application |
2 | ||
3 | 2 | def initialize |
4 | 3 | super |
5 | @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} << capfile | |
4 | @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} | |
6 | 5 | end |
7 | 6 | |
8 | 7 | def name |
20 | 19 | switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/ |
21 | 20 | end |
22 | 21 | |
23 | super.push(version, dry_run, roles, hostfilter) | |
22 | super.push(version, dry_run, roles, hostfilter, print_config_variables) | |
24 | 23 | end |
25 | 24 | |
26 | 25 | def handle_options |
27 | options.rakelib = ['rakelib'] | |
26 | options.rakelib = ["rakelib"] | |
28 | 27 | options.trace_output = $stderr |
29 | 28 | |
30 | 29 | OptionParser.new do |opts| |
47 | 46 | end |
48 | 47 | |
49 | 48 | standard_rake_options.each { |args| opts.on(*args) } |
50 | opts.environment('RAKEOPT') | |
49 | opts.environment("RAKEOPT") | |
51 | 50 | end.parse! |
52 | 51 | end |
53 | ||
54 | 52 | |
55 | 53 | def top_level_tasks |
56 | 54 | if tasks_without_stage_dependency.include?(@top_level_tasks.first) |
62 | 60 | |
63 | 61 | def display_error_message(ex) |
64 | 62 | 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 | |
70 | 64 | trace "(Backtrace restricted to imported tasks)" |
71 | 65 | end |
66 | ||
72 | 67 | super |
73 | 68 | end |
74 | 69 | |
80 | 75 | end |
81 | 76 | end |
82 | 77 | |
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 | ||
83 | 87 | private |
84 | 88 | |
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 | ||
85 | 97 | 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, "") | |
89 | 101 | Dir[deploy_config_path].each { |f| add_import f } |
90 | 102 | end |
91 | 103 | |
92 | 104 | super |
93 | 105 | end |
94 | 106 | |
95 | # allows the `cap install` task to load without a capfile | |
96 | 107 | def capfile |
97 | File.expand_path(File.join(File.dirname(__FILE__),'..','Capfile')) | |
108 | File.expand_path(File.join(File.dirname(__FILE__), "..", "Capfile")) | |
98 | 109 | end |
99 | 110 | |
100 | 111 | def version |
101 | ['--version', '-V', | |
112 | ["--version", "-V", | |
102 | 113 | "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})" | |
105 | 116 | exit |
106 | } | |
107 | ] | |
117 | end] | |
108 | 118 | end |
109 | 119 | |
110 | 120 | def dry_run |
111 | ['--dry-run', '-n', | |
121 | ["--dry-run", "-n", | |
112 | 122 | "Do a dry run without executing actions", |
113 | lambda { |value| | |
123 | lambda do |_value| | |
114 | 124 | Configuration.env.set(:sshkit_backend, SSHKit::Backend::Printer) |
115 | } | |
116 | ] | |
125 | end] | |
117 | 126 | end |
118 | 127 | |
119 | 128 | def roles |
120 | ['--roles ROLES', '-r', | |
129 | ["--roles ROLES", "-r", | |
121 | 130 | "Run SSH commands only on hosts matching these roles", |
122 | lambda { |value| | |
131 | lambda do |value| | |
123 | 132 | Configuration.env.add_cmdline_filter(:role, value) |
124 | } | |
125 | ] | |
133 | end] | |
126 | 134 | end |
127 | 135 | |
128 | 136 | def hostfilter |
129 | ['--hosts HOSTS', '-z', | |
137 | ["--hosts HOSTS", "-z", | |
130 | 138 | "Run SSH commands only on matching hosts", |
131 | lambda { |value| | |
139 | lambda do |value| | |
132 | 140 | Configuration.env.add_cmdline_filter(:host, value) |
133 | } | |
134 | ] | |
141 | end] | |
135 | 142 | end |
136 | 143 | |
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 | |
137 | 151 | end |
138 | ||
139 | 152 | 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" | |
1 | 5 | |
2 | 6 | module Capistrano |
3 | 7 | class Configuration |
4 | 8 | 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 | |
41 | 18 | 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 | |
52 | 22 | end |
53 | 23 | end |
54 | 24 | 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 |
0 | 0 | module Capistrano |
1 | 1 | class Configuration |
2 | 2 | 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 | |
6 | 7 | end |
7 | 8 | |
8 | 9 | def call |
11 | 12 | end |
12 | 13 | |
13 | 14 | private |
15 | ||
14 | 16 | attr_reader :key, :default, :options |
15 | 17 | |
16 | 18 | def ask_question |
17 | 19 | $stdout.print question |
20 | $stdout.flush | |
18 | 21 | end |
19 | 22 | |
20 | 23 | def value_or_default |
27 | 30 | |
28 | 31 | def response |
29 | 32 | return @response if defined? @response |
30 | ||
33 | ||
31 | 34 | @response = (gets || "").chomp |
32 | 35 | end |
33 | ||
36 | ||
34 | 37 | def gets |
35 | 38 | if echo? |
36 | 39 | $stdin.gets |
37 | 40 | else |
38 | $stdin.noecho(&:gets).tap{ $stdout.print "\n" } | |
41 | $stdin.noecho(&:gets).tap { $stdout.print "\n" } | |
39 | 42 | end |
40 | 43 | rescue Errno::EIO |
41 | 44 | # when stdio gets closed |
45 | return | |
42 | 46 | end |
43 | ||
47 | ||
44 | 48 | 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 | |
46 | 54 | end |
47 | 55 | |
48 | 56 | 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" | |
1 | 1 | module Capistrano |
2 | 2 | class Configuration |
3 | 3 | class Server < SSHKit::Host |
24 | 24 | end |
25 | 25 | |
26 | 26 | 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 | |
37 | 38 | return false unless result |
38 | 39 | end |
39 | return true | |
40 | ||
41 | true | |
40 | 42 | end |
41 | 43 | |
42 | 44 | def primary |
53 | 55 | end |
54 | 56 | |
55 | 57 | def netssh_options |
56 | @netssh_options ||= super.merge( fetch(:ssh_options) || {} ) | |
58 | @netssh_options ||= super.merge(fetch(:ssh_options) || {}) | |
57 | 59 | end |
58 | 60 | |
59 | 61 | def roles_array |
61 | 63 | end |
62 | 64 | |
63 | 65 | 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 | |
65 | 68 | end |
66 | 69 | |
67 | 70 | private |
75 | 78 | end |
76 | 79 | |
77 | 80 | class Properties |
78 | ||
79 | 81 | def initialize |
80 | 82 | @properties = {} |
81 | 83 | end |
82 | 84 | |
83 | 85 | def set(key, value) |
84 | 86 | pval = @properties[key] |
85 | if pval.is_a? Hash and value.is_a? Hash | |
87 | if pval.is_a?(Hash) && value.is_a?(Hash) | |
86 | 88 | pval.merge!(value) |
87 | elsif pval.is_a? Set and value.is_a? Set | |
89 | elsif pval.is_a?(Set) && value.is_a?(Set) | |
88 | 90 | pval.merge(value) |
89 | elsif pval.is_a? Array and value.is_a? Array | |
91 | elsif pval.is_a?(Array) && value.is_a?(Array) | |
90 | 92 | pval.concat value |
91 | 93 | else |
92 | 94 | @properties[key] = value |
97 | 99 | @properties[key] |
98 | 100 | end |
99 | 101 | |
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 | |
102 | 104 | end |
103 | 105 | |
104 | 106 | def roles |
109 | 111 | @properties.keys |
110 | 112 | end |
111 | 113 | |
114 | # rubocop:disable Style/MethodMissing | |
112 | 115 | def method_missing(key, value=nil) |
113 | 116 | if value |
114 | 117 | set(lvalue(key), value) |
116 | 119 | fetch(key) |
117 | 120 | end |
118 | 121 | end |
122 | # rubocop:enable Style/MethodMissing | |
123 | ||
124 | def to_h | |
125 | @properties | |
126 | end | |
119 | 127 | |
120 | 128 | private |
121 | 129 | |
122 | 130 | def lvalue(key) |
123 | key.to_s.chomp('=').to_sym | |
131 | key.to_s.chomp("=").to_sym | |
124 | 132 | end |
125 | ||
126 | 133 | end |
127 | ||
128 | 134 | end |
129 | 135 | end |
130 | 136 | 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" | |
3 | 3 | |
4 | 4 | module Capistrano |
5 | 5 | class Configuration |
8 | 8 | |
9 | 9 | def add_host(host, properties={}) |
10 | 10 | 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) | |
15 | 18 | else |
16 | servers << new_host.with(properties) | |
19 | servers_by_key[key] = new_host.with(properties) | |
17 | 20 | end |
18 | 21 | end |
19 | 22 | |
23 | # rubocop:disable Security/MarshalLoad | |
20 | 24 | def add_role(role, hosts, options={}) |
21 | 25 | options_deepcopy = Marshal.dump(options.merge(roles: role)) |
22 | 26 | Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) } |
23 | 27 | end |
28 | # rubocop:enable Security/MarshalLoad | |
24 | 29 | |
25 | 30 | def roles_for(names) |
26 | 31 | options = extract_options(names) |
27 | s = Filter.new(:role, names).filter(servers) | |
32 | s = Filter.new(:role, names).filter(servers_by_key.values) | |
28 | 33 | s.select { |server| server.select?(options) } |
29 | 34 | end |
30 | 35 | |
37 | 42 | if block_given? |
38 | 43 | yield host, role, props |
39 | 44 | else |
40 | rps << (props || {}).merge( role: role, hostname: host.hostname ) | |
45 | rps << (props || {}).merge(role: role, hostname: host.hostname) | |
41 | 46 | end |
42 | 47 | end |
43 | 48 | end |
44 | 49 | end |
45 | block_given? ? nil: rps | |
50 | block_given? ? nil : rps | |
46 | 51 | end |
47 | 52 | |
48 | 53 | def fetch_primary(role) |
51 | 56 | end |
52 | 57 | |
53 | 58 | def each |
54 | servers.each { |server| yield server } | |
59 | servers_by_key.values.each { |server| yield server } | |
55 | 60 | end |
56 | 61 | |
57 | 62 | private |
58 | 63 | |
59 | def servers | |
60 | @servers ||= [] | |
64 | ServerKey = Struct.new(:hostname, :port) | |
65 | ||
66 | def servers_by_key | |
67 | @servers_by_key ||= {} | |
61 | 68 | end |
62 | 69 | |
63 | 70 | 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" | |
4 | 7 | |
5 | 8 | module Capistrano |
9 | class ValidationError < RuntimeError; end | |
10 | ||
6 | 11 | class Configuration |
7 | ||
8 | def initialize(config = nil) | |
9 | @config ||= config | |
10 | end | |
11 | ||
12 | 12 | def self.env |
13 | 13 | @env ||= new |
14 | 14 | end |
17 | 17 | @env = new |
18 | 18 | end |
19 | 19 | |
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 | ||
20 | 29 | def ask(key, default=nil, options={}) |
21 | 30 | question = Question.new(key, default, options) |
22 | 31 | set(key, question) |
23 | 32 | end |
24 | 33 | |
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) | |
27 | 36 | end |
28 | 37 | |
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)) | |
31 | 40 | end |
32 | 41 | |
33 | def delete(key) | |
34 | config.delete(key) | |
42 | def remove(key, *values) | |
43 | set(key, Array(fetch(key)) - values) | |
35 | 44 | end |
36 | 45 | |
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? | |
41 | 52 | end |
42 | return value | |
43 | 53 | end |
44 | 54 | |
45 | def keys | |
46 | config.keys | |
55 | def is_question?(key) | |
56 | value = fetch_for(key, nil) | |
57 | !value.nil? && value.is_a?(Question) | |
47 | 58 | end |
48 | 59 | |
49 | 60 | def role(name, hosts, options={}) |
50 | 61 | 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" | |
52 | 63 | end |
53 | 64 | |
54 | 65 | servers.add_role(name, hosts, options) |
78 | 89 | |
79 | 90 | def configure_backend |
80 | 91 | backend.configure do |sshkit| |
81 | sshkit.format = fetch(:format) | |
92 | configure_sshkit_output(sshkit) | |
82 | 93 | sshkit.output_verbosity = fetch(:log_level) |
83 | 94 | sshkit.default_env = fetch(:default_env) |
84 | 95 | sshkit.backend = fetch(:sshkit_backend, SSHKit::Backend::Netssh) |
85 | 96 | sshkit.backend.configure do |backend| |
86 | 97 | backend.pty = fetch(:pty) |
87 | 98 | 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, {})) | |
89 | 100 | end |
90 | 101 | end |
102 | end | |
103 | ||
104 | def configure_scm | |
105 | Capistrano::Configuration::SCMResolver.new.resolve | |
91 | 106 | end |
92 | 107 | |
93 | 108 | def timestamp |
94 | 109 | @timestamp ||= Time.now.utc |
95 | 110 | end |
96 | 111 | |
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 | ||
97 | 128 | 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] | |
102 | 136 | @filters << Filter.new(:host, fh[:host]) if fh[:host] |
103 | 137 | @filters << Filter.new(:role, fh[:role]) if fh[:role] |
104 | 138 | end |
107 | 141 | cmdline_filters << Filter.new(type, values) |
108 | 142 | end |
109 | 143 | |
110 | def filter list | |
144 | def filter(list) | |
111 | 145 | 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 | |
113 | 165 | end |
114 | 166 | |
115 | 167 | private |
118 | 170 | @cmdline_filters ||= [] |
119 | 171 | end |
120 | 172 | |
121 | def servers | |
122 | @servers ||= Servers.new | |
173 | def installer | |
174 | @installer ||= PluginInstaller.new | |
123 | 175 | end |
124 | 176 | |
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) | |
128 | 180 | |
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) | |
139 | 182 | end |
140 | 183 | end |
141 | 184 | 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" | |
2 | 24 | set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" } |
3 | 25 | set_if_empty :tmp_dir, "/tmp" |
4 | 26 | |
5 | 27 | set_if_empty :default_env, {} |
6 | 28 | set_if_empty :keep_releases, 5 |
7 | 29 | |
8 | set_if_empty :format, :pretty | |
30 | set_if_empty :format, :airbrussh | |
9 | 31 | set_if_empty :log_level, :debug |
10 | 32 | |
11 | 33 | set_if_empty :pty, false |
12 | 34 | |
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" | |
1 | 1 | |
2 | 2 | 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")) | |
1 | 1 | load dotfile if dotfile.file? |
2 |
0 | require "forwardable" | |
1 | ||
0 | 2 | module Capistrano |
1 | 3 | module DSL |
2 | 4 | 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? | |
44 | 11 | |
45 | 12 | def roles(*names) |
46 | 13 | env.roles_for(names.flatten) |
52 | 19 | |
53 | 20 | def release_roles(*names) |
54 | 21 | if names.last.is_a? Hash |
55 | names.last.merge!({ :exclude => :no_release }) | |
22 | names.last[:exclude] = :no_release | |
56 | 23 | else |
57 | 24 | names << { exclude: :no_release } |
58 | 25 | end |
59 | 26 | roles(*names) |
60 | end | |
61 | ||
62 | def primary(role) | |
63 | env.primary(role) | |
64 | 27 | end |
65 | 28 | |
66 | 29 | def env |
74 | 37 | def asset_timestamp |
75 | 38 | env.timestamp.strftime("%Y%m%d%H%M.%S") |
76 | 39 | end |
77 | ||
78 | 40 | end |
79 | 41 | end |
80 | 42 | end |
0 | require 'pathname' | |
0 | require "pathname" | |
1 | 1 | module Capistrano |
2 | 2 | module DSL |
3 | 3 | module Paths |
4 | ||
5 | 4 | def deploy_to |
6 | 5 | fetch(:deploy_to) |
7 | 6 | end |
11 | 10 | end |
12 | 11 | |
13 | 12 | def current_path |
14 | deploy_path.join('current') | |
13 | deploy_path.join(fetch(:current_directory, "current")) | |
15 | 14 | end |
16 | 15 | |
17 | 16 | def releases_path |
18 | deploy_path.join('releases') | |
17 | deploy_path.join(fetch(:releases_directory, "releases")) | |
19 | 18 | end |
20 | 19 | |
21 | 20 | def release_path |
22 | fetch(:release_path, current_path) | |
21 | fetch(:release_path) { current_path } | |
23 | 22 | end |
24 | 23 | |
25 | 24 | def set_release_path(timestamp=now) |
28 | 27 | end |
29 | 28 | |
30 | 29 | def stage_config_path |
31 | Pathname.new fetch(:stage_config_path, 'config/deploy') | |
30 | Pathname.new fetch(:stage_config_path, "config/deploy") | |
32 | 31 | end |
33 | 32 | |
34 | 33 | def deploy_config_path |
35 | Pathname.new fetch(:deploy_config_path, 'config/deploy.rb') | |
34 | Pathname.new fetch(:deploy_config_path, "config/deploy.rb") | |
36 | 35 | end |
37 | 36 | |
38 | 37 | 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) | |
53 | 39 | end |
54 | 40 | |
55 | 41 | def repo_path |
56 | Pathname.new(fetch(:repo_path, ->(){deploy_path.join('repo')})) | |
42 | Pathname.new(fetch(:repo_path, ->() { deploy_path.join("repo") })) | |
57 | 43 | end |
58 | 44 | |
59 | 45 | def shared_path |
60 | deploy_path.join('shared') | |
46 | deploy_path.join(fetch(:shared_directory, "shared")) | |
61 | 47 | end |
62 | 48 | |
63 | 49 | def revision_log |
64 | deploy_path.join('revisions.log') | |
50 | deploy_path.join("revisions.log") | |
65 | 51 | end |
66 | 52 | |
67 | 53 | def now |
95 | 81 | end |
96 | 82 | |
97 | 83 | def map_dirnames(paths) |
98 | paths.map { |path| path.dirname } | |
84 | paths.map(&:dirname).uniq | |
99 | 85 | end |
100 | 86 | end |
101 | 87 | end |
0 | 0 | module Capistrano |
1 | 1 | module DSL |
2 | 2 | module Stages |
3 | RESERVED_NAMES = %w(deploy doctor install).freeze | |
4 | private_constant :RESERVED_NAMES | |
3 | 5 | |
4 | 6 | 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 | |
6 | 10 | end |
7 | 11 | |
8 | 12 | def stage_definitions |
9 | stage_config_path.join('*.rb') | |
13 | stage_config_path.join("*.rb") | |
10 | 14 | end |
11 | 15 | |
12 | 16 | def stage_set? |
13 | 17 | !!fetch(:stage, false) |
14 | 18 | end |
15 | 19 | |
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 | |
16 | 28 | end |
17 | 29 | end |
18 | 30 | end |
0 | require 'capistrano/upload_task' | |
0 | require "capistrano/upload_task" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | module TaskEnhancements |
8 | 8 | |
9 | 9 | def after(task, post_task, *args, &block) |
10 | 10 | 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 | |
14 | 16 | end |
15 | end | |
16 | ||
17 | def remote_file(task) | |
18 | target_roles = task.delete(:roles) { :all } | |
19 | define_remote_file_task(task, target_roles) | |
20 | 17 | end |
21 | 18 | |
22 | 19 | def define_remote_file_task(task, target_roles) |
30 | 27 | upload! File.open(prerequisite_file), file |
31 | 28 | end |
32 | 29 | end |
33 | ||
34 | 30 | end |
35 | 31 | end |
36 | 32 | |
53 | 49 | |
54 | 50 | def exit_deploy_because_of_exception(ex) |
55 | 51 | warn t(:deploy_failed, ex: ex.message) |
56 | invoke 'deploy:failed' | |
52 | invoke "deploy:failed" | |
57 | 53 | exit(false) |
58 | 54 | end |
59 | 55 | |
60 | 56 | def deploying? |
61 | 57 | fetch(:deploying, false) |
62 | 58 | end |
63 | ||
64 | 59 | end |
65 | 60 | 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" | |
6 | 5 | |
7 | 6 | module Capistrano |
8 | 7 | module DSL |
11 | 10 | include Paths |
12 | 11 | include Stages |
13 | 12 | |
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) | |
16 | 32 | end |
17 | 33 | |
18 | 34 | def t(key, options={}) |
29 | 45 | |
30 | 46 | def revision_log_message |
31 | 47 | 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))) | |
38 | 53 | end |
39 | 54 | |
40 | 55 | def rollback_log_message |
49 | 64 | VersionValidator.new(locked_version).verify |
50 | 65 | end |
51 | 66 | |
67 | # rubocop:disable Security/MarshalLoad | |
52 | 68 | def on(hosts, options={}, &block) |
53 | 69 | subset_copy = Marshal.dump(Configuration.env.filter(hosts)) |
54 | 70 | SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block) |
55 | 71 | end |
72 | # rubocop:enable Security/MarshalLoad | |
56 | 73 | |
57 | 74 | def run_locally(&block) |
58 | 75 | SSHKit::Backend::Local.new(&block).run |
59 | 76 | end |
60 | 77 | |
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 | |
61 | 92 | end |
62 | 93 | end |
63 | self.extend Capistrano::DSL | |
94 | extend Capistrano::DSL |
0 | 0 | load File.expand_path("../tasks/framework.rake", __FILE__) |
1 | require 'capistrano/install' | |
1 | require "capistrano/install" |
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 | 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" | |
1 | 1 | |
2 | 2 | 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}", | |
21 | 26 | 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" | |
24 | 29 | }, |
25 | 30 | error: { |
31 | invalid_stage_name: '"%{name}" is a reserved word and cannot be used as a stage. Rename "%{path}" to something else.', | |
26 | 32 | 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}" | |
29 | 35 | } |
30 | 36 | } |
31 | 37 | } |
32 | 38 | |
33 | I18n.backend.store_translations(:en, { capistrano: en }) | |
39 | I18n.backend.store_translations(:en, capistrano: en) | |
34 | 40 | |
35 | 41 | if I18n.respond_to?(:enforce_available_locales=) |
36 | 42 | 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 |
0 | 0 | module Capistrano |
1 | ||
2 | 1 | # Base class for SCM strategy providers. |
3 | 2 | # |
4 | 3 | # @abstract |
24 | 23 | |
25 | 24 | # Call test in context |
26 | 25 | def test!(*args) |
27 | context.test *args | |
26 | context.test(*args) | |
28 | 27 | end |
29 | 28 | |
30 | 29 | # The repository URL according to the context |
58 | 57 | # @return [Boolean] |
59 | 58 | # |
60 | 59 | 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" | |
64 | 61 | end |
65 | 62 | |
66 | 63 | # @abstract |
71 | 68 | # @return [Boolean] |
72 | 69 | # |
73 | 70 | 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" | |
77 | 72 | end |
78 | 73 | |
79 | 74 | # @abstract |
83 | 78 | # @return void |
84 | 79 | # |
85 | 80 | 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" | |
89 | 82 | end |
90 | 83 | |
91 | 84 | # @abstract |
95 | 88 | # @return void |
96 | 89 | # |
97 | 90 | 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" | |
101 | 92 | end |
102 | 93 | |
103 | 94 | # @abstract |
107 | 98 | # @return void |
108 | 99 | # |
109 | 100 | 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" | |
113 | 102 | end |
114 | 103 | |
115 | 104 | # @abstract |
119 | 108 | # @return void |
120 | 109 | # |
121 | 110 | 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" | |
125 | 112 | end |
126 | 113 | end |
127 | 114 | end |
0 | require "capistrano/doctor" | |
1 | require "capistrano/immutable_task" | |
0 | 2 | include Capistrano::DSL |
1 | 3 | |
2 | 4 | namespace :load do |
3 | 5 | task :defaults do |
4 | load 'capistrano/defaults.rb' | |
6 | load "capistrano/defaults.rb" | |
5 | 7 | 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 | |
6 | 17 | end |
7 | 18 | |
8 | 19 | stages.each do |stage| |
9 | 20 | Rake::Task.define_task(stage) do |
10 | 21 | set(:stage, stage.to_sym) |
11 | 22 | |
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 | |
16 | 30 | I18n.locale = fetch(:locale, :en) |
17 | 31 | configure_backend |
18 | 32 | end |
19 | 33 | end |
20 | 34 | |
21 | require 'capistrano/dotfile' | |
35 | require "capistrano/dotfile" |
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" | |
1 | 1 | task :console do |
2 | 2 | stage = fetch(:stage) |
3 | puts I18n.t('console.welcome', scope: :capistrano, stage: stage) | |
3 | puts I18n.t("console.welcome", scope: :capistrano, stage: stage) | |
4 | 4 | loop do |
5 | 5 | print "#{stage}> " |
6 | 6 | |
7 | if input = $stdin.gets | |
8 | command = input.chomp | |
9 | else | |
10 | command = 'exit' | |
11 | end | |
7 | command = (input = $stdin.gets) ? input.chomp : "exit" | |
12 | 8 | |
13 | 9 | next if command.empty? |
14 | 10 | |
15 | 11 | if %w{quit exit q}.include? command |
16 | puts t('console.bye') | |
12 | puts t("console.bye") | |
17 | 13 | break |
18 | 14 | else |
19 | 15 | begin |
0 | 0 | namespace :deploy do |
1 | ||
2 | 1 | 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 | |
9 | 28 | invoke "deploy:set_current_revision" |
10 | invoke 'deploy:symlink:shared' | |
29 | invoke "deploy:symlink:shared" | |
11 | 30 | end |
12 | 31 | |
13 | 32 | task :reverting do |
14 | invoke 'deploy:revert_release' | |
33 | invoke "deploy:revert_release" | |
15 | 34 | end |
16 | 35 | |
17 | 36 | task :publishing do |
18 | invoke 'deploy:symlink:release' | |
37 | invoke "deploy:symlink:release" | |
19 | 38 | end |
20 | 39 | |
21 | 40 | task :finishing do |
22 | invoke 'deploy:cleanup' | |
41 | invoke "deploy:cleanup" | |
23 | 42 | end |
24 | 43 | |
25 | 44 | task :finishing_rollback do |
26 | invoke 'deploy:cleanup_rollback' | |
45 | invoke "deploy:cleanup_rollback" | |
27 | 46 | end |
28 | 47 | |
29 | 48 | 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" | |
34 | 53 | 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" | |
40 | 58 | end |
41 | 59 | |
42 | 60 | namespace :check do |
43 | desc 'Check shared and release directories exist' | |
61 | desc "Check shared and release directories exist" | |
44 | 62 | task :directories do |
45 | 63 | 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" | |
51 | 69 | task :linked_dirs do |
52 | 70 | next unless any? :linked_dirs |
53 | 71 | 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" | |
59 | 77 | task :make_linked_dirs do |
60 | 78 | 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" | |
67 | 85 | task :linked_files do |
68 | 86 | next unless any? :linked_files |
69 | 87 | on release_roles :all do |host| |
78 | 96 | end |
79 | 97 | |
80 | 98 | namespace :symlink do |
81 | desc 'Symlink release to current' | |
99 | desc "Symlink release to current" | |
82 | 100 | task :release do |
83 | 101 | on release_roles :all do |
84 | 102 | 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 | |
86 | 104 | execute :mv, tmp_current_path, current_path.parent |
87 | 105 | end |
88 | 106 | end |
89 | 107 | |
90 | desc 'Symlink files and directories from shared to release' | |
108 | desc "Symlink files and directories from shared to release" | |
91 | 109 | 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" | |
97 | 115 | task :linked_dirs do |
98 | 116 | next unless any? :linked_dirs |
99 | 117 | on release_roles :all do |
100 | execute :mkdir, '-p', linked_dir_parents(release_path) | |
118 | execute :mkdir, "-p", linked_dir_parents(release_path) | |
101 | 119 | |
102 | 120 | fetch(:linked_dirs).each do |dir| |
103 | 121 | target = release_path.join(dir) |
104 | 122 | 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" | |
116 | 131 | task :linked_files do |
117 | 132 | next unless any? :linked_files |
118 | 133 | on release_roles :all do |
119 | execute :mkdir, '-p', linked_file_dirs(release_path) | |
134 | execute :mkdir, "-p", linked_file_dirs(release_path) | |
120 | 135 | |
121 | 136 | fetch(:linked_files).each do |file| |
122 | 137 | target = release_path.join(file) |
123 | 138 | 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" | |
136 | 148 | task :cleanup do |
137 | 149 | 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 | |
142 | 169 | 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 | |
147 | 172 | else |
148 | 173 | info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases)) |
149 | 174 | end |
151 | 176 | end |
152 | 177 | end |
153 | 178 | |
154 | desc 'Remove and archive rolled-back release.' | |
179 | desc "Remove and archive rolled-back release." | |
155 | 180 | task :cleanup_rollback do |
156 | 181 | 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 | |
158 | 183 | last_release_path = releases_path.join(last_release) |
159 | 184 | 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 | |
164 | 189 | 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" | |
171 | 196 | task :log_revision do |
172 | 197 | on release_roles(:all) do |
173 | 198 | 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 | |
181 | 206 | on release_roles(:all) do |
182 | 207 | set(:revision_log_message, rollback_log_message) |
183 | 208 | end |
189 | 214 | |
190 | 215 | task :rollback_release_path do |
191 | 216 | on release_roles(:all) do |
192 | releases = capture(:ls, '-xt', releases_path).split | |
217 | releases = capture(:ls, "-xt", releases_path).split | |
193 | 218 | if releases.count < 2 |
194 | 219 | error t(:cannot_rollback) |
195 | 220 | exit 1 |
196 | 221 | 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] | |
198 | 231 | set_release_path(last_release) |
199 | 232 | set(:rollback_timestamp, last_release) |
200 | 233 | end |
202 | 235 | |
203 | 236 | desc "Place a REVISION file with the current revision SHA in the current release path" |
204 | 237 | task :set_current_revision do |
205 | invoke "#{scm}:set_current_revision" | |
206 | 238 | on release_roles(:all) do |
207 | 239 | within release_path do |
208 | 240 | execute :echo, "\"#{fetch(:current_revision)}\" >> REVISION" |
212 | 244 | |
213 | 245 | task :set_previous_revision do |
214 | 246 | on release_roles(:all) do |
215 | target = release_path.join('REVISION') | |
247 | target = release_path.join("REVISION") | |
216 | 248 | if test "[ -f #{target} ]" |
217 | set(:previous_revision, capture(:cat, target, '2>/dev/null')) | |
249 | set(:previous_revision, capture(:cat, target, "2>/dev/null")) | |
218 | 250 | end |
219 | 251 | end |
220 | 252 | end |
221 | 253 | |
222 | 254 | task :restart |
223 | 255 | task :failed |
224 | ||
225 | 256 | 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 |
0 | 0 | namespace :deploy do |
1 | ||
2 | desc 'Start a deployment, make sure server(s) ready.' | |
1 | desc "Start a deployment, make sure server(s) ready." | |
3 | 2 | task :starting do |
4 | 3 | end |
5 | 4 | |
6 | desc 'Started' | |
5 | desc "Started" | |
7 | 6 | task :started do |
8 | 7 | end |
9 | 8 | |
10 | desc 'Update server(s) by setting up a new release.' | |
9 | desc "Update server(s) by setting up a new release." | |
11 | 10 | task :updating do |
12 | 11 | end |
13 | 12 | |
14 | desc 'Updated' | |
13 | desc "Updated" | |
15 | 14 | task :updated do |
16 | 15 | end |
17 | 16 | |
18 | desc 'Revert server(s) to previous release.' | |
17 | desc "Revert server(s) to previous release." | |
19 | 18 | task :reverting do |
20 | 19 | end |
21 | 20 | |
22 | desc 'Reverted' | |
21 | desc "Reverted" | |
23 | 22 | task :reverted do |
24 | 23 | end |
25 | 24 | |
26 | desc 'Publish the release.' | |
25 | desc "Publish the release." | |
27 | 26 | task :publishing do |
28 | 27 | end |
29 | 28 | |
30 | desc 'Published' | |
29 | desc "Published" | |
31 | 30 | task :published do |
32 | 31 | end |
33 | 32 | |
34 | desc 'Finish the deployment, clean up server(s).' | |
33 | desc "Finish the deployment, clean up server(s)." | |
35 | 34 | task :finishing do |
36 | 35 | end |
37 | 36 | |
38 | desc 'Finish the rollback, clean up server(s).' | |
37 | desc "Finish the rollback, clean up server(s)." | |
39 | 38 | task :finishing_rollback do |
40 | 39 | end |
41 | 40 | |
42 | desc 'Finished' | |
41 | desc "Finished" | |
43 | 42 | task :finished do |
44 | 43 | end |
45 | 44 | |
46 | desc 'Rollback to previous release.' | |
45 | desc "Rollback to previous release." | |
47 | 46 | task :rollback do |
48 | 47 | %w{ starting started |
49 | 48 | reverting reverted |
54 | 53 | end |
55 | 54 | end |
56 | 55 | |
57 | desc 'Deploy a new release.' | |
56 | desc "Deploy a new release." | |
58 | 57 | task :deploy do |
59 | 58 | set(:deploying, true) |
60 | 59 | %w{ starting started |
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 | 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" | |
3 | 3 | task :install do |
4 | envs = ENV['STAGES'] || 'staging,production' | |
4 | envs = ENV["STAGES"] || "staging,production" | |
5 | 5 | |
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") | |
9 | 9 | |
10 | 10 | deploy_rb = File.expand_path("../../templates/deploy.rb.erb", __FILE__) |
11 | 11 | stage_rb = File.expand_path("../../templates/stage.rb.erb", __FILE__) |
13 | 13 | |
14 | 14 | mkdir_p deploy_dir |
15 | 15 | |
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") } } | |
18 | 18 | |
19 | 19 | entries.each do |entry| |
20 | if File.exists?(entry[:file]) | |
20 | if File.exist?(entry[:file]) | |
21 | 21 | warn "[skip] #{entry[:file]} already exists" |
22 | 22 | else |
23 | File.open(entry[:file], 'w+') do |f| | |
23 | File.open(entry[:file], "w+") do |f| | |
24 | 24 | f.write(ERB.new(File.read(entry[:template])).result(binding)) |
25 | 25 | puts I18n.t(:written_file, scope: :capistrano, file: entry[:file]) |
26 | 26 | end |
29 | 29 | |
30 | 30 | mkdir_p tasks_dir |
31 | 31 | |
32 | if File.exists?('Capfile') | |
32 | if File.exist?("Capfile") | |
33 | 33 | warn "[skip] Capfile already exists" |
34 | 34 | 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") | |
37 | 37 | end |
38 | ||
39 | 38 | |
40 | 39 | puts I18n.t :capified, scope: :capistrano |
41 | 40 | end |
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 |
0 | 0 | # Load DSL and set up stages |
1 | require 'capistrano/setup' | |
1 | require "capistrano/setup" | |
2 | 2 | |
3 | 3 | # 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 | |
5 | 16 | |
6 | 17 | # Include tasks from other gems included in your Gemfile |
7 | 18 | # |
14 | 25 | # https://github.com/capistrano/rails |
15 | 26 | # https://github.com/capistrano/passenger |
16 | 27 | # |
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" | |
24 | 35 | |
25 | 36 | # 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 %>" | |
2 | 2 | |
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" | |
5 | 5 | |
6 | 6 | # Default branch is :master |
7 | 7 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp |
8 | 8 | |
9 | 9 | # 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" | |
11 | 11 | |
12 | # Default value for :scm is :git | |
13 | # set :scm, :git | |
12 | # Default value for :format is :airbrussh. | |
13 | # set :format, :airbrussh | |
14 | 14 | |
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 | |
20 | 18 | |
21 | 19 | # Default value for :pty is false |
22 | 20 | # set :pty, true |
23 | 21 | |
24 | 22 | # 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" | |
26 | 24 | |
27 | 25 | # 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" | |
29 | 27 | |
30 | 28 | # Default value for default_env is {} |
31 | 29 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } |
32 | 30 | |
31 | # Default value for local_user is ENV['USER'] | |
32 | # set :local_user, -> { `git config user.name`.chomp } | |
33 | ||
33 | 34 | # Default value for keep_releases is 5 |
34 | 35 | # set :keep_releases, 5 |
35 | 36 | |
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 |
2 | 2 | # Defines a single server with a list of roles and multiple properties. |
3 | 3 | # You can define all roles on a single server, or split them: |
4 | 4 | |
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} | |
8 | 8 | |
9 | 9 | |
10 | 10 | |
12 | 12 | # ================== |
13 | 13 | |
14 | 14 | # 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 | |
16 | 16 | # property set. Specify the username and a domain or IP for the server. |
17 | 17 | # Don't use `:all`, it's a meta role. |
18 | 18 | |
48 | 48 | # |
49 | 49 | # The server-based syntax can be used to override options: |
50 | 50 | # ------------------------------------ |
51 | # server 'example.com', | |
52 | # user: 'user_name', | |
51 | # server "example.com", | |
52 | # user: "user_name", | |
53 | 53 | # roles: %w{web app}, |
54 | 54 | # ssh_options: { |
55 | # user: 'user_name', # overrides user setting above | |
55 | # user: "user_name", # overrides user setting above | |
56 | 56 | # keys: %w(/home/user_name/.ssh/id_rsa), |
57 | 57 | # forward_agent: false, |
58 | 58 | # auth_methods: %w(publickey password) |
59 | # # password: 'please use keys' | |
59 | # # password: "please use keys" | |
60 | 60 | # } |
0 | require 'rake/file_creation_task' | |
0 | require "rake/file_creation_task" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | class UploadTask < Rake::FileCreationTask |
0 | 0 | module Capistrano |
1 | 1 | class VersionValidator |
2 | ||
3 | 2 | def initialize(version) |
4 | 3 | @version = version |
5 | 4 | end |
6 | 5 | |
7 | 6 | 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" | |
13 | 9 | end |
14 | 10 | |
15 | 11 | private |
12 | ||
16 | 13 | attr_reader :version |
17 | ||
18 | 14 | |
19 | 15 | def match? |
20 | 16 | available =~ requested |
25 | 21 | end |
26 | 22 | |
27 | 23 | def available |
28 | Gem::Dependency.new('cap', version) | |
24 | Gem::Dependency.new("cap", version) | |
29 | 25 | end |
30 | 26 | |
31 | 27 | def requested |
32 | Gem::Dependency.new('cap', current_version) | |
28 | Gem::Dependency.new("cap", current_version) | |
33 | 29 | end |
34 | ||
35 | 30 | end |
36 | 31 | end |
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" | |
1 | 1 | |
2 | 2 | describe Capistrano::DSL do |
3 | ||
4 | 3 | let(:dsl) { Class.new.extend Capistrano::DSL } |
5 | 4 | |
6 | 5 | before do |
7 | 6 | Capistrano::Configuration.reset! |
8 | 7 | end |
9 | 8 | |
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 | |
12 | 11 | 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 | |
21 | 20 | subject { dsl.roles(:all) } |
22 | 21 | |
23 | it 'returns all servers' do | |
22 | it "returns all servers" do | |
24 | 23 | expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com} |
25 | 24 | end |
26 | 25 | end |
27 | 26 | |
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 | |
31 | 29 | subject { dsl.release_roles(:all) } |
32 | 30 | |
33 | it 'returns all release servers' do | |
31 | it "returns all release servers" do | |
34 | 32 | expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com} |
35 | 33 | end |
36 | 34 | end |
37 | 35 | |
38 | context 'with property filter options' do | |
36 | context "with property filter options" do | |
39 | 37 | subject { dsl.release_roles(:all, filter: :active) } |
40 | 38 | |
41 | it 'returns all release servers that match the property filter' do | |
39 | it "returns all release servers that match the property filter" do | |
42 | 40 | expect(subject.map(&:hostname)).to eq %w{example1.com example3.com} |
43 | 41 | end |
44 | 42 | end |
45 | 43 | end |
46 | 44 | |
47 | describe 'fetching servers by multiple roles' do | |
45 | describe "fetching servers by multiple roles" do | |
48 | 46 | it "does not confuse the last role with options" do |
49 | 47 | expect(dsl.roles(:app, :web).count).to eq 4 |
50 | 48 | expect(dsl.roles(:app, :web, filter: :active).count).to eq 2 |
51 | 49 | end |
52 | 50 | end |
53 | 51 | |
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 | |
58 | 56 | expect(subject.map(&:hostname)).to eq %w{example3.com example4.com} |
59 | 57 | end |
60 | 58 | end |
61 | 59 | |
62 | describe 'fetching servers by an array of roles' do | |
60 | describe "fetching servers by an array of roles" do | |
63 | 61 | subject { dsl.roles([:app]) } |
64 | 62 | |
65 | it 'returns the servers' do | |
63 | it "returns the servers" do | |
66 | 64 | expect(subject.map(&:hostname)).to eq %w{example3.com example4.com} |
67 | 65 | end |
68 | 66 | end |
69 | 67 | |
70 | describe 'fetching filtered servers by role' do | |
68 | describe "fetching filtered servers by role" do | |
71 | 69 | subject { dsl.roles(:app, filter: :active) } |
72 | 70 | |
73 | it 'returns the servers' do | |
71 | it "returns the servers" do | |
74 | 72 | expect(subject.map(&:hostname)).to eq %w{example3.com} |
75 | 73 | end |
76 | 74 | end |
77 | 75 | |
78 | describe 'fetching selected servers by role' do | |
76 | describe "fetching selected servers by role" do | |
79 | 77 | subject { dsl.roles(:app, select: :active) } |
80 | 78 | |
81 | it 'returns the servers' do | |
79 | it "returns the servers" do | |
82 | 80 | expect(subject.map(&:hostname)).to eq %w{example3.com} |
83 | 81 | end |
84 | 82 | end |
85 | 83 | |
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 | |
88 | 86 | 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 | |
95 | 93 | 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 | |
127 | 125 | 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/ | |
130 | 128 | expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com}) |
131 | 129 | end |
132 | 130 | end |
133 | 131 | |
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 | |
139 | 168 | 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 | |
145 | 174 | before do |
146 | 175 | dsl.role :web, %w{example1.com example2.com example3.com} |
147 | 176 | dsl.role :web, %w{example1.com}, active: true |
151 | 180 | dsl.role :db, %w{example5.com}, no_release: true |
152 | 181 | end |
153 | 182 | |
154 | describe 'fetching all servers' do | |
183 | describe "fetching all servers" do | |
155 | 184 | subject { dsl.roles(:all) } |
156 | 185 | |
157 | it 'returns all servers' do | |
186 | it "returns all servers" do | |
158 | 187 | expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com} |
159 | 188 | end |
160 | 189 | end |
161 | 190 | |
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 | |
165 | 193 | subject { dsl.release_roles(:all) } |
166 | 194 | |
167 | it 'returns all release servers' do | |
195 | it "returns all release servers" do | |
168 | 196 | expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com} |
169 | 197 | end |
170 | 198 | end |
171 | 199 | |
172 | context 'with filter options' do | |
200 | context "with filter options" do | |
173 | 201 | subject { dsl.release_roles(:all, filter: :active) } |
174 | 202 | |
175 | it 'returns all release servers that match the filter' do | |
203 | it "returns all release servers that match the filter" do | |
176 | 204 | expect(subject.map(&:hostname)).to eq %w{example1.com example3.com} |
177 | 205 | end |
178 | 206 | end |
179 | 207 | end |
180 | 208 | |
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 | |
186 | 213 | expect(subject.map(&:hostname)).to eq %w{example3.com example4.com} |
187 | 214 | end |
188 | 215 | end |
189 | 216 | |
190 | describe 'fetching servers by an array of roles' do | |
217 | describe "fetching servers by an array of roles" do | |
191 | 218 | subject { dsl.roles([:app]) } |
192 | 219 | |
193 | it 'returns the servers' do | |
220 | it "returns the servers" do | |
194 | 221 | expect(subject.map(&:hostname)).to eq %w{example3.com example4.com} |
195 | 222 | end |
196 | 223 | end |
197 | 224 | |
198 | describe 'fetching filtered servers by role' do | |
225 | describe "fetching filtered servers by role" do | |
199 | 226 | subject { dsl.roles(:app, filter: :active) } |
200 | 227 | |
201 | it 'returns the servers' do | |
228 | it "returns the servers" do | |
202 | 229 | expect(subject.map(&:hostname)).to eq %w{example3.com} |
203 | 230 | end |
204 | 231 | end |
205 | 232 | |
206 | describe 'fetching selected servers by role' do | |
233 | describe "fetching selected servers by role" do | |
207 | 234 | subject { dsl.roles(:app, select: :active) } |
208 | 235 | |
209 | it 'returns the servers' do | |
236 | it "returns the servers" do | |
210 | 237 | expect(subject.map(&:hostname)).to eq %w{example3.com} |
211 | 238 | end |
212 | 239 | end |
213 | 240 | |
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 | |
216 | 243 | 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 | |
223 | 250 | 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 | |
234 | 259 | 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 | |
238 | 263 | dsl.role :app, %w{deployer@example1.com:1234} |
239 | 264 | dsl.role :app, %w{example1.com:5678} |
240 | 265 | end |
241 | 266 | |
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 | |
250 | 275 | 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 | |
256 | 282 | 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 | |
265 | 291 | subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first } |
266 | 292 | |
267 | 293 | describe "using the :user property" do |
268 | 294 | 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" | |
270 | 296 | expect(subject).to eq("brian@example1.com:1234") |
271 | 297 | end |
272 | 298 | end |
273 | 299 | |
274 | 300 | describe "using the :port property" do |
275 | 301 | 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 | |
277 | 303 | expect(subject).to eq("db@example1.com:1234") |
278 | 304 | end |
279 | 305 | end |
280 | 306 | end |
281 | ||
282 | end | |
283 | ||
284 | describe 'setting and fetching variables' do | |
285 | ||
307 | end | |
308 | ||
309 | describe "setting and fetching variables" do | |
286 | 310 | before do |
287 | 311 | dsl.set :scm, :git |
288 | 312 | end |
289 | 313 | |
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 | |
293 | 317 | expect(dsl.fetch(:scm)).to eq :git |
294 | 318 | end |
295 | 319 | end |
296 | 320 | |
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 | |
299 | 323 | expect(dsl.fetch(:source_control)).to be_nil |
300 | 324 | end |
301 | 325 | end |
302 | 326 | end |
303 | 327 | |
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 | |
307 | 331 | expect(dsl.fetch(:scm, :svn)).to eq :git |
308 | 332 | end |
309 | 333 | end |
310 | 334 | |
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 | |
313 | 337 | expect(dsl.fetch(:source_control, :svn)).to eq :svn |
314 | 338 | end |
315 | 339 | end |
316 | 340 | end |
317 | 341 | |
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 | |
321 | 345 | expect(dsl.fetch(:scm) { :svn }).to eq :git |
322 | 346 | end |
323 | 347 | end |
324 | 348 | |
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 | |
327 | 351 | expect(dsl.fetch(:source_control) { :svn }).to eq :svn |
328 | 352 | end |
329 | 353 | end |
330 | 354 | end |
331 | ||
332 | end | |
333 | ||
334 | describe 'asking for a variable' do | |
355 | end | |
356 | ||
357 | describe "asking for a variable" do | |
335 | 358 | before do |
336 | 359 | dsl.ask(:scm, :svn) |
337 | 360 | $stdout.stubs(:print) |
338 | 361 | end |
339 | 362 | |
340 | context 'variable is provided' do | |
363 | context "variable is provided" do | |
341 | 364 | 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 | |
351 | 374 | 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 | |
356 | 379 | expect(dsl.fetch(:scm)).to eq :svn |
357 | 380 | end |
358 | 381 | end |
359 | 382 | end |
360 | 383 | |
361 | describe 'checking for presence' do | |
384 | describe "checking for presence" do | |
362 | 385 | subject { dsl.any? :linked_files } |
363 | 386 | |
364 | 387 | before do |
365 | 388 | dsl.set(:linked_files, linked_files) |
366 | 389 | end |
367 | 390 | |
368 | context 'variable is an non-empty array' do | |
391 | context "variable is an non-empty array" do | |
369 | 392 | let(:linked_files) { %w{1} } |
370 | 393 | |
371 | 394 | it { expect(subject).to be_truthy } |
372 | 395 | end |
373 | 396 | |
374 | context 'variable is an empty array' do | |
397 | context "variable is an empty array" do | |
375 | 398 | let(:linked_files) { [] } |
376 | 399 | it { expect(subject).to be_falsey } |
377 | 400 | end |
378 | 401 | |
379 | context 'variable exists, is not an array' do | |
402 | context "variable exists, is not an array" do | |
380 | 403 | let(:linked_files) { stub } |
381 | 404 | it { expect(subject).to be_truthy } |
382 | 405 | end |
383 | 406 | |
384 | context 'variable is nil' do | |
407 | context "variable is nil" do | |
385 | 408 | let(:linked_files) { nil } |
386 | 409 | it { expect(subject).to be_falsey } |
387 | 410 | end |
388 | 411 | end |
389 | 412 | |
390 | describe 'configuration SSHKit' do | |
413 | describe "configuration SSHKit" do | |
391 | 414 | let(:config) { SSHKit.config } |
392 | 415 | let(:backend) { SSHKit.config.backend.config } |
393 | 416 | let(:default_env) { { rails_env: :production } } |
398 | 421 | dsl.set(:default_env, default_env) |
399 | 422 | dsl.set(:pty, true) |
400 | 423 | 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)) | |
406 | 427 | dsl.configure_backend |
407 | 428 | end |
408 | 429 | |
409 | it 'sets the output' do | |
430 | it "sets the output" do | |
410 | 431 | expect(config.output).to be_a SSHKit::Formatter::Dot |
411 | 432 | end |
412 | 433 | |
413 | it 'sets the output verbosity' do | |
434 | it "sets the output verbosity" do | |
414 | 435 | expect(config.output_verbosity).to eq 0 |
415 | 436 | end |
416 | 437 | |
417 | it 'sets the default env' do | |
438 | it "sets the default env" do | |
418 | 439 | expect(config.default_env).to eq default_env |
419 | 440 | end |
420 | 441 | |
421 | it 'sets the backend pty' do | |
442 | it "sets the backend pty" do | |
422 | 443 | expect(backend.pty).to be_truthy |
423 | 444 | end |
424 | 445 | |
425 | it 'sets the backend connection timeout' do | |
446 | it "sets the backend connection timeout" do | |
426 | 447 | expect(backend.connection_timeout).to eq 10 |
427 | 448 | end |
428 | 449 | |
429 | it 'sets the backend ssh_options' do | |
450 | it "sets the backend ssh_options" do | |
430 | 451 | expect(backend.ssh_options[:keys]).to eq %w(/home/user/.ssh/id_rsa) |
431 | 452 | expect(backend.ssh_options[:forward_agent]).to eq false |
432 | 453 | expect(backend.ssh_options[:auth_methods]).to eq %w(publickey password) |
433 | 454 | 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 | |
525 | 573 | before do |
526 | 574 | 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 | |
533 | 581 | 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 | |
538 | 586 | 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) | |
546 | 594 | dsl.role_properties(:redis) do |host, role, props| |
547 | 595 | recipient.doit(host, role, props) |
548 | 596 | end |
549 | 597 | end |
550 | 598 | |
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) | |
556 | 604 | dsl.role_properties(:redis, :app) do |host, role, props| |
557 | 605 | recipient.doit(host, role, props) |
558 | 606 | end |
559 | 607 | end |
560 | 608 | |
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) | |
567 | 615 | dsl.role_properties(:redis, :web) do |host, role, props| |
568 | 616 | recipient.doit(host, role, props) |
569 | 617 | end |
570 | 618 | end |
571 | 619 | |
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) | |
576 | 624 | dsl.role_properties(:redis, :web, select: :active) do |host, role, props| |
577 | 625 | recipient.doit(host, role, props) |
578 | 626 | end |
579 | 627 | end |
580 | 628 | end |
581 | ||
582 | 629 | 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" | |
3 | 3 | |
4 | 4 | include TestApp |
5 | ||
6 |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | describe Capistrano::Application do |
3 | ||
4 | 3 | it "provides a --trace option which enables SSHKit/NetSSH trace output" |
5 | 4 | |
6 | 5 | it "provides a --format option which enables the choice of output formatting" |
7 | 6 | |
8 | 7 | let(:help_output) do |
9 | out, _ = capture_io do | |
10 | flags '--help', '-h' | |
8 | out, _err = capture_io do | |
9 | flags "--help", "-h" | |
11 | 10 | end |
12 | 11 | out |
13 | 12 | end |
23 | 22 | end |
24 | 23 | |
25 | 24 | 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" | |
28 | 27 | end |
29 | 28 | expect(out).to match(/\bCapistrano Version\b/) |
30 | 29 | expect(out).to match(/\b#{Capistrano::VERSION}\b/) |
31 | 30 | expect(out).to match(/\bRake Version\b/) |
32 | expect(out).to match(/\b#{RAKEVERSION}\b/) | |
31 | expect(out).to match(/\b#{Rake::VERSION}\b/) | |
33 | 32 | end |
34 | 33 | |
35 | 34 | 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" | |
38 | 37 | end |
39 | 38 | sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend) |
40 | 39 | 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 | |
41 | 47 | end |
42 | 48 | |
43 | 49 | def flags(*sets) |
50 | 56 | |
51 | 57 | def command_line(*options) |
52 | 58 | options.each { |opt| ARGV << opt } |
53 | def subject.exit(*args) | |
59 | subject.define_singleton_method(:exit) do |*_args| | |
54 | 60 | throw(:system_exit, :exit) |
55 | 61 | end |
56 | 62 | subject.run |
58 | 64 | end |
59 | 65 | |
60 | 66 | def capture_io |
61 | require 'stringio' | |
67 | require "stringio" | |
62 | 68 | |
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 | |
66 | 75 | |
67 | 76 | yield |
68 | 77 | |
71 | 80 | $stdout = orig_stdout |
72 | 81 | $stderr = orig_stderr |
73 | 82 | end |
74 | ||
75 | 83 | 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" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | 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 | |
4 | 14 | |
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 | |
13 | 16 | 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 | |
17 | 20 | end |
18 | 21 | |
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 | |
21 | 46 | end |
22 | 47 | |
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 | |
26 | 54 | |
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 | |
30 | 60 | |
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 | |
34 | 66 | |
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 | |
61 | 72 | end |
62 | 73 | end |
63 | 74 | |
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 | |
69 | 84 | 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" | |
74 | 89 | 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" | |
79 | 94 | 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" | |
84 | 99 | 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" | |
106 | 104 | end |
107 | 105 | end |
108 | 106 | 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" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | class Configuration |
4 | ||
5 | 4 | describe Question do |
6 | ||
7 | 5 | let(:question) { Question.new(key, default, options) } |
8 | 6 | let(:question_without_echo) { Question.new(key, default, echo: false) } |
7 | let(:question_without_default) { Question.new(key, nil) } | |
9 | 8 | let(:default) { :default } |
10 | 9 | let(:key) { :branch } |
11 | 10 | let(:options) { nil } |
12 | 11 | |
13 | describe '.new' do | |
14 | it 'takes a key, default, options' do | |
12 | describe ".new" do | |
13 | it "takes a key, default, options" do | |
15 | 14 | question |
16 | 15 | end |
17 | 16 | end |
18 | 17 | |
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" } | |
22 | 21 | |
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): ") | |
28 | 24 | $stdin.expects(:gets).returns(branch) |
29 | 25 | $stdin.expects(:noecho).never |
30 | 26 | |
31 | 27 | expect(question.call).to eq(branch) |
32 | 28 | end |
33 | 29 | |
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): ") | |
35 | 32 | $stdin.expects(:noecho).returns(branch) |
36 | 33 | $stdout.expects(:print).with("\n") |
37 | 34 | |
38 | 35 | expect(question_without_echo.call).to eq(branch) |
39 | 36 | 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 | |
40 | 45 | end |
41 | 46 | |
42 | context 'value is not entered' do | |
47 | context "value is not entered" do | |
43 | 48 | let(:branch) { default } |
44 | 49 | |
45 | 50 | 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("") | |
48 | 53 | end |
49 | 54 | |
50 | ||
51 | it 'returns the default as the value' do | |
55 | it "returns the default as the value" do | |
52 | 56 | expect(question.call).to eq(branch) |
53 | 57 | end |
54 | 58 | end |
55 | 59 | end |
56 | 60 | end |
57 | ||
58 | 61 | end |
59 | 62 | 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" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | class Configuration |
4 | 4 | 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 | |
8 | 8 | 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 | |
23 | 22 | subject { server.has_role?(:test) } |
24 | 23 | |
25 | 24 | before do |
26 | 25 | server.add_role(:test) |
27 | 26 | end |
28 | 27 | |
29 | it 'adds the role' do | |
28 | it "adds the role" do | |
30 | 29 | expect(subject).to be_truthy |
31 | 30 | end |
32 | 31 | end |
33 | 32 | |
34 | describe 'comparing identity' do | |
33 | describe "comparing identity" do | |
35 | 34 | subject { server.hostname == Server[hostname].hostname } |
36 | 35 | |
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" } | |
39 | 38 | it { expect(subject).to be_truthy } |
40 | 39 | end |
41 | 40 | |
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" } | |
44 | 43 | it { expect(subject).to be_truthy } |
45 | 44 | end |
46 | 45 | |
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" } | |
49 | 48 | it { expect(subject).to be_truthy } |
50 | 49 | end |
51 | 50 | |
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" } | |
54 | 53 | it { expect(subject).to be_falsey } |
55 | 54 | end |
56 | 55 | end |
57 | 56 | |
58 | describe 'identifying as primary' do | |
57 | describe "identifying as primary" do | |
59 | 58 | subject { server.primary } |
60 | context 'server is primary' do | |
59 | context "server is primary" do | |
61 | 60 | before do |
62 | 61 | server.set(:primary, true) |
63 | 62 | end |
64 | it 'returns self' do | |
63 | it "returns self" do | |
65 | 64 | expect(subject).to eq server |
66 | 65 | end |
67 | 66 | end |
68 | 67 | |
69 | context 'server is not primary' do | |
70 | it 'is falesy' do | |
68 | context "server is not primary" do | |
69 | it "is falesy" do | |
71 | 70 | expect(subject).to be_falsey |
72 | 71 | end |
73 | 72 | end |
74 | 73 | end |
75 | 74 | |
76 | describe 'assigning properties' do | |
77 | ||
75 | describe "assigning properties" do | |
78 | 76 | before do |
79 | 77 | server.with(properties) |
80 | 78 | end |
81 | 79 | |
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 | |
86 | 84 | expect(server.roles.first).to eq :clouds |
87 | 85 | end |
88 | 86 | end |
89 | 87 | |
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 | |
106 | 104 | expect(server.port).to eq 2222 |
107 | 105 | end |
108 | 106 | end |
109 | 107 | |
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 | |
127 | 125 | let(:properties) { { webscales: 5 } } |
128 | 126 | |
129 | it 'adds the properties' do | |
127 | it "adds the properties" do | |
130 | 128 | expect(server.properties.webscales).to eq 5 |
131 | 129 | end |
132 | 130 | end |
133 | 131 | |
134 | context 'existing properties' do | |
132 | context "existing properties" do | |
135 | 133 | let(:properties) { { webscales: 6 } } |
136 | 134 | |
137 | it 'keeps the existing properties' do | |
135 | it "keeps the existing properties" do | |
138 | 136 | expect(server.properties.webscales).to eq 6 |
139 | 137 | server.properties.webscales = 5 |
140 | 138 | expect(server.properties.webscales).to eq 5 |
142 | 140 | end |
143 | 141 | end |
144 | 142 | |
145 | describe '#include?' do | |
143 | describe "#include?" do | |
146 | 144 | let(:options) { {} } |
147 | 145 | |
148 | 146 | subject { server.select?(options) } |
151 | 149 | server.properties.active = true |
152 | 150 | end |
153 | 151 | |
154 | context 'options are empty' do | |
152 | context "options are empty" do | |
155 | 153 | it { expect(subject).to be_truthy } |
156 | 154 | end |
157 | 155 | |
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 } } | |
210 | 207 | it { expect(subject).to be_truthy } |
211 | 208 | end |
212 | 209 | |
213 | context 'with :active false' do | |
214 | let(:options) { { active: false }} | |
210 | context "with :active false" do | |
211 | let(:options) { { active: false } } | |
215 | 212 | it { expect(subject).to be_falsey } |
216 | 213 | end |
217 | 214 | end |
218 | 215 | |
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 | |
223 | 219 | let(:options) { { filter: ->(s) { s.properties.active } } } |
224 | 220 | it { expect(subject).to be_truthy } |
225 | 221 | end |
226 | 222 | |
227 | context 'with :select' do | |
223 | context "with :select" do | |
228 | 224 | let(:options) { { select: ->(s) { s.properties.active } } } |
229 | 225 | it { expect(subject).to be_truthy } |
230 | 226 | end |
231 | 227 | |
232 | context 'with :exclude' do | |
228 | context "with :exclude" do | |
233 | 229 | let(:options) { { exclude: ->(s) { s.properties.active } } } |
234 | 230 | it { expect(subject).to be_falsey } |
235 | 231 | 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 | |
241 | 236 | let(:options) { { filter: ->(s) { s.properties.inactive } } } |
242 | 237 | it { expect(subject).to be_falsey } |
243 | 238 | end |
244 | 239 | |
245 | context 'with :select' do | |
240 | context "with :select" do | |
246 | 241 | let(:options) { { select: ->(s) { s.properties.inactive } } } |
247 | 242 | it { expect(subject).to be_falsey } |
248 | 243 | end |
249 | 244 | |
250 | context 'with :exclude' do | |
245 | context "with :exclude" do | |
251 | 246 | let(:options) { { exclude: ->(s) { s.properties.inactive } } } |
252 | 247 | it { expect(subject).to be_truthy } |
253 | 248 | 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 | |
265 | 258 | expect(server.netssh_options[:forward_agent]).to eq true |
266 | 259 | 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 | |
273 | 266 | let(:properties) do |
274 | 267 | { ssh_options: { |
275 | user: 'another_user', | |
268 | user: "another_user", | |
276 | 269 | keys: %w(/home/another_user/.ssh/id_rsa), |
277 | 270 | forward_agent: false, |
278 | auth_methods: %w(publickey password) } } | |
271 | auth_methods: %w(publickey password) | |
272 | } } | |
279 | 273 | end |
280 | 274 | |
281 | 275 | before do |
282 | 276 | server.with(properties) |
283 | 277 | end |
284 | 278 | |
285 | it 'not forward agent' do | |
279 | it "not forward agent" do | |
286 | 280 | expect(server.netssh_options[:forward_agent]).to eq false |
287 | 281 | 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 | |
295 | 289 | expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa) |
296 | 290 | end |
297 | it 'contains auth_methods' do | |
291 | it "contains auth_methods" do | |
298 | 292 | expect(server.netssh_options[:auth_methods]).to eq %w(publickey password) |
299 | 293 | end |
300 | 294 | end |
301 | ||
302 | 295 | end |
303 | 296 | |
304 | 297 | 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 | |
310 | 303 | expect(Server[server]).to be server |
311 | 304 | end |
312 | 305 | end |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | class Configuration |
4 | 4 | describe Servers do |
5 | 5 | let(:servers) { Servers.new } |
6 | 6 | |
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 | |
15 | 14 | servers.add_role(:app, %w{1}) |
16 | 15 | servers.add_role(:app, %w{1}) |
17 | 16 | expect(servers.count).to eq 1 |
18 | 17 | end |
19 | 18 | |
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") | |
23 | 22 | expect(servers.count).to eq 1 |
24 | 23 | end |
25 | 24 | |
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"]) | |
28 | 27 | expect(servers.roles_for([:app]).length).to eq 2 |
29 | 28 | end |
30 | 29 | |
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") | |
33 | 32 | expect(servers.roles_for([:app]).count).to eq 1 |
34 | 33 | end |
35 | 34 | |
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 }) | |
39 | 38 | expect(servers.count).to eq(2) |
40 | 39 | 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 | |
48 | 46 | before do |
49 | 47 | servers.add_role(:web, %w{1 2}) |
50 | 48 | servers.add_role(:app, %w{1 2}) |
51 | 49 | end |
52 | 50 | |
53 | it 'adds new roles to existing servers' do | |
51 | it "adds new roles to existing servers" do | |
54 | 52 | expect(servers.count).to eq 2 |
55 | 53 | end |
56 | ||
57 | end | |
58 | ||
59 | describe 'collecting server roles' do | |
54 | end | |
55 | ||
56 | describe "collecting server roles" do | |
60 | 57 | let(:app) { Set.new([:app]) } |
61 | let(:web_app) { Set.new([:web, :app]) } | |
58 | let(:web_app) { Set.new(%i(web app)) } | |
62 | 59 | let(:web) { Set.new([:web]) } |
63 | 60 | |
64 | 61 | before do |
66 | 63 | servers.add_role(:web, %w{2 3 4}) |
67 | 64 | end |
68 | 65 | |
69 | it 'returns an array of the roles' do | |
66 | it "returns an array of the roles" do | |
70 | 67 | expect(servers.roles_for([:app]).collect(&:roles)).to eq [app, web_app, web_app] |
71 | 68 | expect(servers.roles_for([:web]).collect(&:roles)).to eq [web_app, web_app, web] |
72 | 69 | end |
73 | 70 | end |
74 | 71 | |
75 | describe 'finding the primary server' do | |
72 | describe "finding the primary server" do | |
76 | 73 | after do |
77 | 74 | Configuration.reset! |
78 | 75 | 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 | |
99 | 96 | before do |
100 | 97 | servers.add_role(:app, %w{1 2}) |
101 | 98 | servers.add_role(:web, %w{2 3}) |
102 | 99 | end |
103 | 100 | |
104 | it 'returns the correct app servers' do | |
101 | it "returns the correct app servers" do | |
105 | 102 | expect(servers.roles_for([:app]).map(&:hostname)).to eq %w{1 2} |
106 | 103 | end |
107 | 104 | |
108 | it 'returns the correct web servers' do | |
105 | it "returns the correct web servers" do | |
109 | 106 | expect(servers.roles_for([:web]).map(&:hostname)).to eq %w{2 3} |
110 | 107 | end |
111 | 108 | |
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 | |
117 | 114 | expect(servers.roles_for([:all]).map(&:hostname)).to eq %w{1 2 3} |
118 | 115 | end |
119 | 116 | end |
120 | 117 | |
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" | |
130 | 126 | expect(servers.roles_for([:all]).first.properties.test).to eq :value |
131 | 127 | expect(servers.roles_for([:all]).first.properties.keys).to eq [:test] |
132 | 128 | end |
133 | 129 | |
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) | |
143 | 140 | end |
144 | 141 | |
145 | 142 | 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") | |
149 | 145 | 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") | |
155 | 151 | 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) | |
163 | 158 | expect(servers.count).to eq(1) |
164 | 159 | expect(servers.roles_for([:all]).first.properties.test).to eq :volatile |
165 | 160 | end |
166 | 161 | |
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 }) | |
170 | 165 | 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]) | |
177 | 172 | 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]) | |
184 | 179 | 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 | |
217 | 210 | expect(servers.roles_for([:app, select: :inactive])).to be_empty |
218 | 211 | end |
219 | 212 | |
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 | |
221 | 214 | expect(servers.roles_for([:app, filter: :active]).length).to eq 1 |
222 | 215 | end |
223 | 216 | |
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 | |
225 | 218 | expect(servers.roles_for([:app, select: :active]).length).to eq 1 |
226 | 219 | end |
227 | 220 | |
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 | |
229 | 222 | expect(servers.roles_for([:app, filter: ->(h) { h.properties.active }]).length).to eq 1 |
230 | 223 | end |
231 | 224 | |
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 | |
233 | 226 | expect(servers.roles_for([:app, select: ->(h) { h.properties.active }]).length).to eq 1 |
234 | 227 | end |
235 | 228 | |
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 | |
237 | 230 | expect(servers.roles_for([:app, select: ->(h) { h.properties.inactive }])).to be_empty |
238 | 231 | 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 | |
250 | 241 | hosts = servers.roles_for([:app, exclude: :active]) |
251 | 242 | expect(hosts.map(&:hostname)).to be_empty |
252 | 243 | end |
253 | 244 | |
254 | it 'returns the servers without the attributes specified' do | |
245 | it "returns the servers without the attributes specified" do | |
255 | 246 | hosts = servers.roles_for([:app, exclude: :no_release]) |
256 | 247 | expect(hosts.map(&:hostname)).to eq %w{1} |
257 | 248 | end |
258 | 249 | |
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 | |
260 | 251 | hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.no_release }]) |
261 | 252 | expect(hosts.map(&:hostname)).to eq %w{1} |
262 | 253 | end |
263 | 254 | |
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 | |
265 | 256 | hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.active }]) |
266 | 257 | expect(hosts.map(&:hostname)).to be_empty |
267 | 258 | 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) | |
279 | 268 | end |
280 | 269 | |
281 | 270 | subject { servers.roles_for(roles).map(&:hostname) } |
282 | 271 | |
283 | context 'with the ROLES environment variable set' do | |
284 | ||
272 | context "with the ROLES environment variable set" do | |
285 | 273 | 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 | |
291 | 279 | let(:roles) { [:all] } |
292 | it 'ignores it' do | |
280 | it "ignores it" do | |
293 | 281 | expect(subject).to eq %w{1 2 3 4 5} |
294 | 282 | end |
295 | 283 | end |
296 | 284 | |
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 | |
300 | 288 | expect(subject).to eq %w{1 2 3 4} |
301 | 289 | end |
302 | 290 | end |
303 | 291 | |
304 | context 'when selecting roles not included in ROLE' do | |
292 | context "when selecting roles not included in ROLE" do | |
305 | 293 | let(:roles) { [:app] } |
306 | it 'ignores it' do | |
294 | it "ignores it" do | |
307 | 295 | expect(subject).to eq %w{1 2} |
308 | 296 | end |
309 | 297 | 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 | |
315 | 301 | 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 | |
321 | 307 | let(:roles) { [:all] } |
322 | it 'ignores it' do | |
308 | it "ignores it" do | |
323 | 309 | expect(subject).to eq %w{1 2 3 4 5} |
324 | 310 | end |
325 | 311 | end |
326 | 312 | |
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 | |
330 | 316 | expect(subject).to eq %w{1 2 3 4} |
331 | 317 | end |
332 | 318 | end |
333 | 319 | |
334 | context 'when selecting no roles' do | |
320 | context "when selecting no roles" do | |
335 | 321 | let(:roles) { [] } |
336 | it 'ignores it' do | |
322 | it "ignores it" do | |
337 | 323 | expect(subject).to be_empty |
338 | 324 | end |
339 | 325 | end |
340 | ||
341 | end | |
342 | ||
326 | end | |
343 | 327 | end |
344 | 328 | end |
345 | 329 | end |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | describe Configuration do |
4 | 4 | let(:config) { Configuration.new } |
5 | 5 | let(:servers) { stub } |
6 | 6 | |
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 | |
16 | 16 | Configuration.env.set(:test, true) |
17 | 17 | expect(Configuration.env.fetch(:test)).to be_truthy |
18 | 18 | end |
19 | 19 | end |
20 | 20 | |
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 | |
23 | 23 | old_env = Configuration.env |
24 | 24 | Configuration.reset! |
25 | 25 | expect(Configuration.env).not_to be old_env |
26 | 26 | end |
27 | 27 | end |
28 | 28 | |
29 | describe 'roles' do | |
30 | context 'adding a role' do | |
29 | describe "roles" do | |
30 | context "adding a role" do | |
31 | 31 | subject { config.role(:app, %w{server1 server2}) } |
32 | 32 | |
33 | 33 | before do |
35 | 35 | servers.expects(:add_role).with(:app, %w{server1 server2}, {}) |
36 | 36 | end |
37 | 37 | |
38 | it 'adds the role' do | |
38 | it "adds the role" do | |
39 | 39 | expect(subject) |
40 | 40 | end |
41 | 41 | end |
42 | 42 | end |
43 | 43 | |
44 | describe 'setting and fetching' do | |
44 | describe "setting and fetching" do | |
45 | 45 | subject { config.fetch(:key, :default) } |
46 | 46 | |
47 | context 'value is set' do | |
48 | before do | |
47 | context "set" do | |
48 | it "sets by value" do | |
49 | 49 | 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 | |
59 | 65 | config.set_if_empty(:key, :value) |
60 | 66 | expect(subject).to eq :value |
61 | 67 | end |
62 | 68 | |
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 | |
64 | 75 | config.set(:key, :value) |
65 | 76 | 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 | |
72 | 84 | expect(subject).to eq :default |
73 | 85 | end |
74 | 86 | end |
75 | 87 | |
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 | |
79 | 91 | expect(subject).to eq :proc |
80 | 92 | end |
81 | 93 | end |
82 | 94 | |
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 | |
86 | 98 | expect(subject).to eq :lambda |
87 | 99 | end |
88 | 100 | end |
89 | 101 | |
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 | |
93 | 105 | expect(subject).to eq "some value" |
94 | 106 | end |
95 | 107 | end |
96 | 108 | |
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 | |
100 | 112 | expect(subject).to eq "some value" |
101 | 113 | end |
102 | 114 | end |
103 | 115 | |
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 | |
107 | 119 | expect(subject).to eq "some value" |
108 | 120 | end |
109 | 121 | end |
110 | 122 | |
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 | |
114 | 126 | expect(subject).to eq "some value" |
115 | 127 | end |
116 | 128 | end |
117 | 129 | |
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 | |
121 | 133 | expect(subject).to eq 42 |
122 | 134 | end |
123 | 135 | end |
124 | 136 | |
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 | |
135 | 232 | subject { config.keys } |
136 | 233 | |
137 | 234 | before do |
139 | 236 | config.set(:key2, :value2) |
140 | 237 | end |
141 | 238 | |
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 | |
148 | 245 | before do |
149 | 246 | config.set(:key, :value) |
150 | 247 | end |
151 | 248 | |
152 | it 'deletes the value' do | |
249 | it "deletes the value" do | |
153 | 250 | config.delete(:key) |
154 | 251 | expect(config.fetch(:key)).to be_nil |
155 | 252 | end |
156 | 253 | end |
157 | 254 | |
158 | describe 'asking' do | |
255 | describe "asking" do | |
159 | 256 | let(:question) { stub } |
160 | let(:options) { Hash.new } | |
257 | let(:options) { {} } | |
161 | 258 | |
162 | 259 | 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 | |
168 | 265 | config.ask(:branch, :default, options) |
169 | 266 | expect(config.fetch(:branch)).to eq question |
170 | 267 | end |
171 | 268 | end |
172 | 269 | |
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 | |
175 | 272 | expect(config.backend).to eq SSHKit |
176 | 273 | end |
177 | 274 | |
178 | it 'can be set to another class' do | |
275 | it "can be set to another class" do | |
179 | 276 | config.backend = :test |
180 | 277 | expect(config.backend).to eq :test |
181 | 278 | end |
182 | 279 | |
183 | 280 | 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 | |
185 | 282 | config.set :format, :pretty |
186 | 283 | 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" } } | |
189 | 286 | 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) | |
192 | 353 | end |
193 | 354 | end |
194 | 355 | 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" | |
1 | 1 | |
2 | 2 | describe Capistrano::DSL::Paths do |
3 | ||
4 | 3 | let(:dsl) { Class.new.extend Capistrano::DSL } |
5 | let(:parent) { Pathname.new('/var/shared') } | |
4 | let(:parent) { Pathname.new("/var/shared") } | |
6 | 5 | let(:paths) { Class.new.extend Capistrano::DSL::Paths } |
7 | 6 | |
8 | 7 | 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} } | |
10 | 9 | |
11 | 10 | 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 | |
16 | 15 | subject { paths.linked_dirs(parent) } |
17 | 16 | |
18 | 17 | before do |
19 | 18 | paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs) |
20 | 19 | end |
21 | 20 | |
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 | |
29 | 30 | subject { paths.linked_files(parent) } |
30 | 31 | |
31 | 32 | before do |
32 | 33 | paths.expects(:fetch).with(:linked_files).returns(linked_files) |
33 | 34 | end |
34 | 35 | |
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 | |
41 | 46 | subject { paths.linked_file_dirs(parent) } |
42 | 47 | |
43 | 48 | before do |
44 | 49 | paths.expects(:fetch).with(:linked_files).returns(linked_files) |
45 | 50 | end |
46 | 51 | |
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 | |
53 | 61 | subject { paths.linked_dir_parents(parent) } |
54 | 62 | |
55 | 63 | before do |
56 | 64 | paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs) |
57 | 65 | end |
58 | 66 | |
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 | |
66 | 76 | subject { dsl.release_path } |
67 | 77 | |
68 | context 'where no release path has been set' do | |
78 | context "where no release path has been set" do | |
69 | 79 | before do |
70 | 80 | dsl.delete(:release_path) |
71 | 81 | end |
72 | 82 | |
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 | |
90 | 100 | let(:now) { Time.parse("Oct 21 16:29:00 2015") } |
91 | 101 | subject { dsl.release_path } |
92 | 102 | |
93 | context 'without a timestamp' do | |
103 | context "without a timestamp" do | |
94 | 104 | before do |
95 | 105 | dsl.env.expects(:timestamp).returns(now) |
96 | 106 | dsl.set_release_path |
97 | 107 | end |
98 | 108 | |
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 | |
116 | 156 | subject { dsl.deploy_config_path.to_s } |
117 | 157 | |
118 | context 'when not specified' do | |
158 | context "when not specified" do | |
119 | 159 | before do |
120 | 160 | dsl.delete(:deploy_config_path) |
121 | 161 | end |
122 | 162 | |
123 | 163 | 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 | |
140 | 180 | subject { dsl.stage_config_path.to_s } |
141 | 181 | |
142 | context 'when not specified' do | |
143 | ||
182 | context "when not specified" do | |
144 | 183 | before do |
145 | 184 | dsl.delete(:stage_config_path) |
146 | 185 | end |
147 | 186 | |
148 | 187 | 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 | |
165 | 204 | subject { dsl.repo_path.to_s } |
166 | 205 | |
167 | context 'when not specified' do | |
168 | ||
206 | context "when not specified" do | |
169 | 207 | before do |
170 | 208 | dsl.delete(:repo_path) |
171 | 209 | end |
172 | 210 | |
173 | 211 | 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" | |
186 | 224 | end |
187 | 225 | end |
188 | 226 | end |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | 3 | class DummyTaskEnhancements |
7 | 7 | describe TaskEnhancements do |
8 | 8 | let(:task_enhancements) { DummyTaskEnhancements.new } |
9 | 9 | |
10 | describe 'ordering' do | |
11 | ||
10 | describe "ordering" do | |
12 | 11 | after do |
13 | 12 | task.clear |
14 | 13 | before_task.clear |
18 | 17 | |
19 | 18 | let(:order) { [] } |
20 | 19 | 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" | |
23 | 22 | end |
24 | 23 | end |
25 | 24 | |
26 | 25 | 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" | |
29 | 28 | end |
30 | 29 | end |
31 | 30 | |
32 | 31 | 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" | |
35 | 34 | end |
36 | 35 | end |
37 | 36 | |
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") | |
41 | 40 | |
42 | Rake::Task['task'].invoke order | |
41 | Rake::Task["task"].invoke order | |
43 | 42 | |
44 | expect(order).to eq(['before_task', 'task', 'after_task']) | |
43 | expect(order).to eq(%w(before_task task after_task)) | |
45 | 44 | end |
46 | 45 | |
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") | |
50 | 49 | |
51 | Rake::Task['task'].invoke order | |
50 | Rake::Task["task"].invoke order | |
52 | 51 | |
53 | expect(order).to eq(['before_task', 'task', 'after_task']) | |
52 | expect(order).to eq(%w(before_task task after_task)) | |
54 | 53 | end |
55 | 54 | |
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" | |
59 | 60 | end |
60 | 61 | |
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" | |
63 | 70 | end |
64 | 71 | |
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 | |
66 | 75 | |
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)) | |
68 | 79 | end |
69 | 80 | |
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 | |
71 | 93 | |
72 | describe 'remote_file' do | |
73 | subject(:remote_file) { task_enhancements.remote_file('source' => 'destination') } | |
94 | Rake::Task["namespace:task"].invoke | |
74 | 95 | |
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 | |
77 | 100 | |
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') | |
84 | 104 | end |
85 | 105 | end |
86 | 106 | end |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | ||
4 | 3 | class DummyDSL |
5 | 4 | include DSL |
6 | 5 | end |
9 | 8 | describe DSL do |
10 | 9 | let(:dsl) { DummyDSL.new } |
11 | 10 | |
12 | describe '#t' do | |
11 | describe "#t" do | |
13 | 12 | before do |
14 | I18n.expects(:t).with(:phrase, {count: 2, scope: :capistrano}) | |
13 | I18n.expects(:t).with(:phrase, count: 2, scope: :capistrano) | |
15 | 14 | end |
16 | 15 | |
17 | it 'delegates to I18n' do | |
16 | it "delegates to I18n" do | |
18 | 17 | dsl.t(:phrase, count: 2) |
19 | 18 | end |
20 | 19 | end |
21 | 20 | |
22 | describe '#stage_set?' do | |
21 | describe "#stage_set?" do | |
23 | 22 | subject { dsl.stage_set? } |
24 | 23 | |
25 | context 'stage is set' do | |
24 | context "stage is set" do | |
26 | 25 | before do |
27 | 26 | dsl.set(:stage, :sandbox) |
28 | 27 | end |
29 | 28 | it { expect(subject).to be_truthy } |
30 | 29 | end |
31 | 30 | |
32 | context 'stage is not set' do | |
31 | context "stage is not set" do | |
33 | 32 | before do |
34 | 33 | dsl.set(:stage, nil) |
35 | 34 | end |
37 | 36 | end |
38 | 37 | end |
39 | 38 | |
40 | describe '#sudo' do | |
41 | ||
39 | describe "#sudo" do | |
42 | 40 | before do |
43 | 41 | dsl.expects(:execute).with(:sudo, :my, :command) |
44 | 42 | end |
45 | 43 | |
46 | it 'prepends sudo, delegates to execute' do | |
44 | it "prepends sudo, delegates to execute" do | |
47 | 45 | 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 | |
48 | 121 | end |
49 | 122 | end |
50 | 123 | end |
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 | 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" | |
1 | 1 | |
2 | require 'capistrano/scm' | |
2 | require "capistrano/scm" | |
3 | 3 | |
4 | 4 | module RaiseNotImplementedMacro |
5 | 5 | def raise_not_implemented_on(method) |
6 | 6 | it "should raise NotImplemented on #{method}" do |
7 | expect { | |
7 | expect do | |
8 | 8 | subject.send(method) |
9 | }.to raise_error(NotImplementedError) | |
9 | end.to raise_error(NotImplementedError) | |
10 | 10 | end |
11 | 11 | end |
12 | 12 | end |
25 | 25 | |
26 | 26 | module Capistrano |
27 | 27 | describe SCM do |
28 | let(:context) { Class.new.new } | |
28 | let(:context) { mock } | |
29 | 29 | |
30 | 30 | describe "#initialize" do |
31 | 31 | subject { Capistrano::SCM.new(context, DummyStrategy) } |
62 | 62 | |
63 | 63 | describe "#release_path" do |
64 | 64 | 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") | |
67 | 67 | end |
68 | 68 | end |
69 | 69 | |
100 | 100 | end |
101 | 101 | end |
102 | 102 | end |
103 |
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" | |
1 | 1 | |
2 | 2 | describe Capistrano::UploadTask do |
3 | 3 | let(:app) { Rake.application = Rake::Application.new } |
4 | 4 | |
5 | subject(:upload_task) { described_class.define_task('path/file.yml') } | |
5 | subject(:upload_task) { described_class.define_task("path/file.yml") } | |
6 | 6 | |
7 | 7 | it { is_expected.to be_a(Rake::FileCreationTask) } |
8 | 8 | it { is_expected.to be_needed } |
9 | 9 | |
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") } | |
12 | 12 | |
13 | around { |ex| app.in_namespace('namespace', &ex) } | |
13 | around { |ex| app.in_namespace("namespace", &ex) } | |
14 | 14 | |
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") } | |
17 | 17 | end |
18 | 18 | end |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | ||
4 | 3 | describe VersionValidator do |
5 | 4 | let(:validator) { VersionValidator.new(version) } |
6 | 5 | let(:version) { stub } |
7 | 6 | |
8 | describe '#new' do | |
9 | it 'takes a version' do | |
7 | describe "#new" do | |
8 | it "takes a version" do | |
10 | 9 | expect(validator) |
11 | 10 | end |
12 | 11 | end |
13 | 12 | |
14 | describe '#verify' do | |
15 | let(:current_version) { '3.0.1' } | |
13 | describe "#verify" do | |
14 | let(:current_version) { "3.0.1" } | |
16 | 15 | |
17 | 16 | subject { validator.verify } |
18 | 17 | |
20 | 19 | validator.stubs(:current_version).returns(current_version) |
21 | 20 | end |
22 | 21 | |
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" } | |
26 | 25 | it { expect(subject).to be_truthy } |
27 | 26 | end |
28 | 27 | |
29 | context 'invalid - lower' do | |
30 | let(:version) { '3.0.0' } | |
28 | context "invalid - lower" do | |
29 | let(:version) { "3.0.0" } | |
31 | 30 | |
32 | it 'fails' do | |
33 | expect { subject }.to raise_error | |
31 | it "fails" do | |
32 | expect { subject }.to raise_error(RuntimeError) | |
34 | 33 | end |
35 | 34 | end |
36 | 35 | |
37 | context 'invalid - higher' do | |
38 | let(:version) { '3.0.2' } | |
36 | context "invalid - higher" do | |
37 | let(:version) { "3.0.2" } | |
39 | 38 | |
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) | |
58 | 41 | end |
59 | 42 | end |
60 | 43 | end |
61 | 44 | |
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 | |
62 | 50 | |
51 | context "invalid - lower" do | |
52 | let(:version) { "<= 2.0.0" } | |
63 | 53 | |
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" } | |
68 | 64 | it { expect(subject).to be_truthy } |
69 | 65 | end |
70 | 66 | |
71 | context 'invalid' do | |
72 | let(:version) { '~> 3.1.0' } | |
67 | context "invalid" do | |
68 | let(:version) { "~> 3.1.0" } | |
73 | 69 | |
74 | it 'fails' do | |
75 | expect { subject }.to raise_error | |
70 | it "fails" do | |
71 | expect { subject }.to raise_error(RuntimeError) | |
76 | 72 | end |
77 | 73 | end |
78 | 74 | end |
79 | 75 | |
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" } | |
82 | 78 | |
83 | context 'valid' do | |
84 | let(:version) { '~> 3.1' } | |
79 | context "valid" do | |
80 | let(:version) { "~> 3.1" } | |
85 | 81 | it { expect(subject).to be_truthy } |
86 | 82 | end |
87 | 83 | |
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) | |
92 | 88 | end |
93 | 89 | end |
94 | 90 | end |
95 | 91 | |
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 | |
96 | 114 | end |
97 | ||
98 | 115 | end |
99 | ||
100 | 116 | end |
101 | ||
102 | 117 | end |
0 | require 'spec_helper' | |
0 | require "spec_helper" | |
1 | 1 | |
2 | 2 | module Capistrano |
3 | ||
4 | 3 | describe Application do |
5 | let(:app) { Application.new } | |
4 | let(:app) { Application.new } | |
6 | 5 | end |
7 | 6 | end |
0 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) | |
0 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) | |
1 | 1 | $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" | |
6 | 6 | |
7 | 7 | # Requires supporting files with custom matchers and macros, etc, |
8 | 8 | # 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 } | |
10 | 10 | |
11 | 11 | RSpec.configure do |config| |
12 | config.raise_errors_for_deprecations! | |
12 | config.raise_errors_for_deprecations! | |
13 | 13 | config.mock_framework = :mocha |
14 | config.order = 'random' | |
14 | config.order = "random" | |
15 | 15 | end |
0 | require 'open-uri' | |
0 | require "open-uri" | |
1 | 1 | |
2 | 2 | Vagrant.configure("2") do |config| |
3 | ||
4 | 3 | config.ssh.insert_key = false |
5 | 4 | |
6 | 5 | [: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" | |
12 | 11 | |
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) | |
14 | 13 | |
15 | config.vm.provision :shell, | |
16 | inline: <<-INLINE | |
14 | primary.vm.provision :shell, | |
15 | inline: <<-INLINE | |
17 | 16 | install -d -m 700 /root/.ssh |
18 | 17 | echo -e "#{vagrantkey}" > /root/.ssh/authorized_keys |
19 | 18 | chmod 0600 /root/.ssh/authorized_keys |
0 | 0 | namespace :deploy do |
1 | 1 | namespace :check do |
2 | task :linked_files => 'config/database.yml' | |
2 | task linked_files: "config/database.yml" | |
3 | 3 | end |
4 | 4 | end |
5 | 5 | |
6 | remote_file 'config/database.yml' => '/tmp/database.yml', roles: :all | |
6 | remote_file "config/database.yml" => "/tmp/database.yml", :roles => :all | |
7 | 7 | |
8 | file '/tmp/database.yml' do |t| | |
8 | file "/tmp/database.yml" do |t| | |
9 | 9 | sh "touch #{t.name}" |
10 | 10 | end |
0 | set :fail, proc { fail } | |
1 | before 'deploy:starting', :fail do | |
0 | set :fail, proc { raise } | |
1 | before "deploy:starting", :fail do | |
2 | 2 | on roles :all do |
3 | execute :touch, shared_path.join('fail') | |
3 | execute :mkdir, "-p", shared_path | |
4 | execute :touch, shared_path.join("fail") | |
4 | 5 | end |
5 | 6 | fetch(:fail) |
6 | 7 | end |
0 | after 'deploy:failed', :failed do | |
0 | after "deploy:failed", :custom_failed do | |
1 | 1 | on roles :all do |
2 | execute :touch, shared_path.join('failed') | |
2 | execute :touch, shared_path.join("failed") | |
3 | 3 | end |
4 | 4 | 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 |
0 | 0 | task :am_i_root do |
1 | 1 | on roles(:all) do |host| |
2 | host.user = 'root' | |
3 | ident = capture :id, '-a' | |
2 | host.user = "root" | |
3 | ident = capture :id, "-a" | |
4 | 4 | info "I am #{ident}" |
5 | 5 | end |
6 | on roles(:all) do |host| | |
7 | ident = capture :id, '-a' | |
6 | on roles(:all) do |_host| | |
7 | ident = capture :id, "-a" | |
8 | 8 | info "I am #{ident}" |
9 | 9 | end |
10 | 10 | end |
0 | require 'fileutils' | |
1 | require 'pathname' | |
0 | require "English" | |
1 | require "fileutils" | |
2 | require "pathname" | |
2 | 3 | |
3 | 4 | module TestApp |
4 | 5 | extend self |
8 | 9 | end |
9 | 10 | |
10 | 11 | def default_config |
11 | %{ | |
12 | <<-CONFIG | |
12 | 13 | set :deploy_to, '#{deploy_to}' |
13 | 14 | set :repo_url, 'git://github.com/capistrano/capistrano.git' |
14 | 15 | set :branch, 'master' |
16 | 17 | server 'vagrant@localhost:2220', roles: %w{web app} |
17 | 18 | set :linked_files, #{linked_files} |
18 | 19 | set :linked_dirs, #{linked_dirs} |
19 | } | |
20 | set :format_options, log_file: nil | |
21 | set :local_user, #{current_user.inspect} | |
22 | CONFIG | |
20 | 23 | end |
21 | 24 | |
22 | 25 | def linked_files |
28 | 31 | end |
29 | 32 | |
30 | 33 | def linked_dirs |
31 | %w{bin log public/system vendor/bundle} | |
34 | %w{bin log public/system} | |
32 | 35 | end |
33 | 36 | |
34 | 37 | def create_test_app |
35 | 38 | FileUtils.rm_rf(test_app_path) |
36 | 39 | FileUtils.mkdir(test_app_path) |
37 | 40 | |
38 | File.open(gemfile, 'w+') do |file| | |
41 | File.open(gemfile, "w+") do |file| | |
39 | 42 | file.write "gem 'capistrano', path: '#{path_to_cap}'" |
40 | 43 | end |
41 | 44 | |
42 | 45 | Dir.chdir(test_app_path) do |
43 | %x[bundle] | |
46 | run "bundle" | |
44 | 47 | end |
45 | 48 | end |
46 | 49 | |
47 | 50 | def install_test_app_with(config) |
48 | 51 | create_test_app |
49 | 52 | Dir.chdir(test_app_path) do |
50 | %x[bundle exec cap install STAGES=#{stage}] | |
53 | run "cap install STAGES=#{stage}" | |
51 | 54 | end |
52 | 55 | write_local_deploy_file(config) |
53 | 56 | end |
54 | 57 | |
55 | 58 | def write_local_deploy_file(config) |
56 | File.open(test_stage_path, 'w') do |file| | |
59 | File.open(test_stage_path, "w") do |file| | |
57 | 60 | file.write config |
58 | 61 | end |
59 | 62 | end |
60 | 63 | |
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 | ||
61 | 70 | def append_to_deploy_file(config) |
62 | File.open(test_stage_path, 'a') do |file| | |
71 | File.open(test_stage_path, "a") do |file| | |
63 | 72 | file.write config + "\n" |
64 | 73 | end |
65 | 74 | end |
66 | 75 | |
67 | 76 | def prepend_to_capfile(config) |
68 | 77 | current_capfile = File.read(capfile) |
69 | File.open(capfile, 'w') do |file| | |
78 | File.open(capfile, "w") do |file| | |
70 | 79 | file.write config |
71 | 80 | file.write current_capfile |
72 | 81 | end |
77 | 86 | end |
78 | 87 | |
79 | 88 | def create_shared_file(path) |
80 | File.open(shared_path.join(path), 'w') | |
89 | File.open(shared_path.join(path), "w") | |
81 | 90 | end |
82 | 91 | |
83 | def cap(task) | |
84 | run "bundle exec cap #{stage} #{task}" | |
92 | def cap(task, subdirectory=nil) | |
93 | run "cap #{stage} #{task} --trace", subdirectory | |
85 | 94 | end |
86 | 95 | |
87 | def run(command) | |
96 | def run(command, subdirectory=nil) | |
88 | 97 | 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}` } | |
91 | 102 | end |
92 | [$?.success?, output] | |
103 | [$CHILD_STATUS.success?, output] | |
93 | 104 | end |
94 | 105 | |
95 | 106 | def stage |
96 | 'test' | |
107 | "test" | |
97 | 108 | end |
98 | 109 | |
99 | 110 | def test_stage_path |
100 | test_app_path.join('config/deploy/test.rb') | |
111 | test_app_path.join("config/deploy/test.rb") | |
101 | 112 | end |
102 | 113 | |
103 | 114 | def test_app_path |
104 | Pathname.new('/tmp/test_app') | |
115 | Pathname.new("/tmp/test_app") | |
105 | 116 | end |
106 | 117 | |
107 | 118 | def deploy_to |
108 | Pathname.new('/home/vagrant/var/www/deploy') | |
119 | Pathname.new("/home/vagrant/var/www/deploy") | |
109 | 120 | end |
110 | 121 | |
111 | 122 | def shared_path |
112 | deploy_to.join('shared') | |
123 | deploy_to.join("shared") | |
113 | 124 | end |
114 | 125 | |
115 | 126 | def current_path |
116 | deploy_to.join('current') | |
127 | deploy_to.join("current") | |
117 | 128 | end |
118 | 129 | |
119 | 130 | def releases_path |
120 | deploy_to.join('releases') | |
131 | deploy_to.join("releases") | |
121 | 132 | end |
122 | 133 | |
123 | def release_path | |
124 | releases_path.join(timestamp) | |
134 | def release_path(t=timestamp) | |
135 | releases_path.join(t) | |
125 | 136 | end |
126 | 137 | |
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") | |
129 | 140 | end |
130 | 141 | |
131 | 142 | def repo_path |
132 | deploy_to.join('repo') | |
143 | deploy_to.join("repo") | |
133 | 144 | end |
134 | 145 | |
135 | 146 | def path_to_cap |
136 | File.expand_path('.') | |
147 | File.expand_path(".") | |
137 | 148 | end |
138 | 149 | |
139 | 150 | def gemfile |
140 | test_app_path.join('Gemfile') | |
151 | test_app_path.join("Gemfile") | |
141 | 152 | end |
142 | 153 | |
143 | 154 | def capfile |
144 | test_app_path.join('Capfile') | |
155 | test_app_path.join("Capfile") | |
145 | 156 | end |
146 | 157 | |
147 | 158 | def current_user |
148 | `whoami`.chomp | |
159 | "(GitHub Web Flow) via ShipIt" | |
149 | 160 | end |
150 | 161 | |
151 | 162 | def task_dir |
152 | test_app_path.join('lib/capistrano/tasks') | |
163 | test_app_path.join("lib/capistrano/tasks") | |
153 | 164 | end |
154 | 165 | |
155 | 166 | def copy_task_to_test_app(source) |
157 | 168 | end |
158 | 169 | |
159 | 170 | def config_path |
160 | test_app_path.join('config') | |
171 | test_app_path.join("config") | |
161 | 172 | end |
162 | 173 | |
163 | 174 | def move_configuration_to_custom_location(location) |
164 | 175 | prepend_to_capfile( |
165 | %{ | |
176 | <<-CONFIG | |
166 | 177 | set :stage_config_path, "app/config/deploy" |
167 | 178 | set :deploy_config_path, "app/config/deploy.rb" |
168 | } | |
179 | CONFIG | |
169 | 180 | ) |
170 | 181 | |
171 | 182 | location = test_app_path.join(location) |
173 | 184 | FileUtils.mv(config_path, location) |
174 | 185 | end |
175 | 186 | |
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 | |
176 | 195 | end |