Imported Upstream version 3.4.0
Antonio Terceiro
9 years ago
2 | 2 | - 2.1.0 |
3 | 3 | - 2.0.0 |
4 | 4 | - 1.9.3 |
5 | - 1.9.2 | |
6 | - rbx | |
5 | - rbx-2 | |
7 | 6 | script: bundle exec rake spec |
7 | install: bundle install --jobs=1 | |
8 | 8 | cache: bundler |
9 | bundler_args: --without cucumber | |
9 | branches: | |
10 | except: | |
11 | - legacy-v2 | |
12 | sudo: false |
3 | 3 | |
4 | 4 | ## master |
5 | 5 | |
6 | https://github.com/capistrano/capistrano/compare/v3.2.0...HEAD | |
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 | |
11 | ||
12 | * Fixed fetch revision for annotated git tags. (@igorsokolov) | |
13 | * Fixed updating roles when custom user or port is specified. (@ayastreb) | |
14 | * Disables statistics collection. | |
15 | ||
16 | * `bin/` is not suggested to be in `linked_dirs` anymore (@kirs) | |
17 | * bin/ is often checked out into repo | |
18 | * https://github.com/capistrano/bundler/issues/45#issuecomment-69349237 | |
19 | ||
20 | * Bugfix: | |
21 | * release_roles did not honour additional property filtering (@townsen) | |
22 | * Refactored and simplified property filtering code (@townsen) | |
23 | ||
24 | * Breaking Changes | |
25 | * Hosts with the same name are now consolidated into one irrespective of the | |
26 | 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 | |
28 | for details. | |
29 | * Inside the on() block the host variable is now a copy of the host, so changes can be | |
30 | made within the block (such as dynamically overriding the user) that will not persist. | |
31 | This is very convenient for switching the SSH user temporarily to 'root' for example. | |
32 | ||
33 | * Minor changes | |
34 | * Add role_properties() method (see capistrano.github.io PR for doc) (@townsen) | |
35 | * Add equality syntax ( eg. port: 1234) for property filtering (@townsen) | |
36 | * Add documentation regarding property filtering (@townsen) | |
37 | * Clarify wording and recommendation in stage template. (@Kriechi) | |
38 | * Both available syntaxes provide similar functionality, do not use both for the same server+role combination. | |
39 | * Allow specification of repo_path using stage variable | |
40 | default is as before (@townsen) | |
41 | ||
42 | ## `3.3.5` | |
43 | ||
44 | https://github.com/capistrano/capistrano/compare/v3.3.4...v3.3.5 | |
45 | ||
46 | * Fixed setting properties twice when creating new server. See [issue | |
47 | #1214](https://github.com/capistrano/capistrano/issues/1214) (@ayastreb) | |
48 | ||
49 | ## `3.3.4` | |
50 | ||
51 | https://github.com/capistrano/capistrano/compare/v3.3.3...v3.3.4 | |
52 | ||
53 | * Minor changes: | |
54 | * Rely on a newer version of capistrano-stats with better privacy (@leehambley) | |
55 | * Fix cucumber spec for loading tasks from stage configs (@sponomarev) | |
56 | * Minor documentation fixes (@deeeki, @seuros, @andresilveira) | |
57 | * Spec improvements (@dimitrid, @sponomarev) | |
58 | * Fix to CLI flags for git-ls-remote (@dimitrid) | |
59 | ||
60 | ## `3.3.3` | |
61 | ||
62 | https://github.com/capistrano/capistrano/compare/v3.2.1...v3.3.3 | |
63 | ||
64 | * Enhancement (@townsen) | |
65 | * Added the variable `:repo_tree` which allows the specification of a sub-tree that | |
66 | will be extracted from the repository. This is useful when deploying a project | |
67 | that lives in a subdirectory of a larger repository. | |
68 | Implemented only for git and hg. | |
69 | If not defined then the behaviour is as previously and the whole repository is | |
70 | extracted (subject to git-archive `.gitattributes` of course). | |
71 | ||
72 | * Enhancement (@townsen): Remove unnecessary entries from default backtrace | |
73 | ||
74 | When the `--backtrace` (or `--trace`) command line option is not supplied | |
75 | Rake lowers the noise level in exception backtraces by building | |
76 | a regular expression containing all the system library paths and | |
77 | using it to exclude backtrace entries that match. | |
78 | ||
79 | This does not always go far enough, particularly in RVM environments when | |
80 | many gem paths are added. This commit reverses that approach and _only_ | |
81 | include backtrace entries that fall within the Capfile and list of tasks | |
82 | imported thereafter. This makes reading exceptions much easier on the eye. | |
83 | ||
84 | If the full unexpurgated backtrace is required then the --backtrace | |
85 | and --trace options supply it as before. | |
86 | ||
87 | * Disable loading stages configs on `cap -T` (@sponomarev) | |
88 | ||
89 | * Enhancements (@townsen) | |
90 | * Fix matching on hosts with custom ports or users set | |
91 | * Previously filtering would affect any generated configuration files so that | |
92 | files newly deployed would not be the same as those on the hosts previously | |
93 | deployed (and now excluded by filters). This is almost certainly not what is | |
94 | wanted: the filters should apply only to the on() method and thus any | |
95 | configuration files deployed will be identical across the set of servers | |
96 | making up the stage. | |
97 | * Host and Role filtering now affects only `on()` commands | |
98 | and not the `roles()`, `release_roles()` and `primary()` methods. | |
99 | * This applies to filters defined via the command line, the environment | |
100 | and the :filter variable. | |
101 | * Filtering now supports Regular expressions | |
102 | * This change _could_ cause existing scripts that use filtering and depend on | |
103 | the old behaviour to fail, though it is unlikely. Users who rely on | |
104 | filtering should check that generated configuration files are correct, and | |
105 | where not introduce server properties to do the filtering. For example, if a | |
106 | filter was used to specify an active subset of servers (by hostname), it should | |
107 | be removed and replaced with an 'active' property (set to true or false) on the | |
108 | server definitions. This keeps the stage file as the canonical model of the | |
109 | deployment environment. | |
110 | ||
111 | * See the documentation in the README.md file | |
112 | ||
113 | * Enhancements (@townsen) | |
114 | * Added set_if_empty method to DSL to allow conditional setting | |
115 | * Altered standard Capistrano defaults so that they are not set | |
116 | at the start of a stage if they have been previously set. This | |
117 | allows variables like :default_env to be set in deploy.rb. | |
118 | * Deep copy properties added using the 'roles' keyword | |
119 | * If a property exists on a server when another definition is | |
120 | encountered and is an Array, Set or Hash then add the new values | |
121 | ||
122 | This allows roles to specify properties common to all servers and | |
123 | then for individual servers to modify them, keeping things DRY | |
124 | ||
125 | Breaking Changes: | |
126 | * By using Ruby's noecho method introduced in Ruby version 1.9.3, we dropped support for Ruby versions prior to 1.9.3. See [issue #878](https://github.com/capistrano/capistrano/issues/878) and [PR #1112](https://github.com/capistrano/capistrano/pull/1112) for more information. (@kaikuchn) | |
127 | * Track (anonymous) statistics, see https://github.com/capistrano/stats. This breaks automated deployment on continuous integration servers until the `.capistrano/metrics` file is created (with content `full` to simulate a "yes") via the interactive prompt or manually. | |
128 | ||
129 | * Bug Fixes: | |
130 | * Fixed compatibility with FreeBSD tar (@robbertkl) | |
131 | * remote_file can be used inside a namespace (@mikz) | |
132 | ||
133 | * Minor Changes | |
134 | * Remove -v flag from mkdir call. (@caligo-mentis) | |
135 | * Capistrano now allows to customize `local_user` for revision log. (@sauliusgrigaitis) | |
136 | * Added tests for after/before hooks features (@juanibiapina, @miry) | |
137 | * Added `--force` flag to `svn export` command to fix errors when the release directory already exists. | |
138 | * Improved the output of `cap --help`. (@mbrictson) | |
139 | * Cucumber suite now runs on the latest version of Vagrant (@tpett) | |
140 | * The `ask` method now supports the `echo: false` option. (@mbrictson, @kaikuchn) | |
141 | * Cucumber scenario improvements (@bruno-) | |
142 | * Added suggestion to Capfile to use 'capistrano-passenger' gem, replacing suggestion in config/deploy.rb to re-implement 'deploy:restart' (@betesh) | |
143 | * Updated svn fetch_revision method to use `svnversion` | |
144 | * `cap install` no longer overwrites existing files. (@dmarkow) | |
7 | 145 | |
8 | 146 | ## `3.2.1` |
147 | ||
148 | https://github.com/capistrano/capistrano/compare/v3.2.0...v3.2.1 | |
9 | 149 | |
10 | 150 | * Bug Fixes: |
11 | 151 | * 3.2.0 introduced some behaviour to modify the way before/after hooks were called, to allow the optional |
18 | 158 | * Changed asking question to more standard format (like common unix commandline tools) (@sponomarev) |
19 | 159 | * Fixed typos in the README. (@sponomarev) |
20 | 160 | * Added `keys` method to Configuration to allow introspection of configuration options. (@juanibiapina) |
161 | * Improve error message when git:check fails (raise instead of silently `exit 1`) (@mbrictson) | |
21 | 162 | |
22 | 163 | ## `3.2.0` |
23 | 164 | |
39 | 180 | Breaking changes: |
40 | 181 | |
41 | 182 | * `deploy:restart` task **is no longer run by default**. |
42 | From this version, developers who restart the app on each deploy need to declare it in their deploy flow (eg `after 'deploy:publishing', 'deploy:restart'`). | |
183 | From this version, developers who restart the app on each deploy need to declare it in their deploy flow (eg `after 'deploy:publishing', 'deploy:restart'`) | |
184 | or, for passenger applications, use the capistrano-passenger gem. | |
43 | 185 | |
44 | 186 | Please, check https://github.com/capistrano/capistrano/commit/4e6523e1f50707499cf75eb53dce37a89528a9b0 for more information. (@kirs) |
45 | 187 |
3 | 3 | gemspec |
4 | 4 | |
5 | 5 | group :cucumber do |
6 | gem 'kuroko' | |
7 | 6 | gem 'cucumber' |
7 | gem 'rspec', '~> 3.0.0' | |
8 | 8 | end |
9 | ||
10 | platforms :rbx do | |
11 | gem 'rubysl', '~> 2.0' | |
12 | end |
0 | # Capistrano [![Build Status](https://travis-ci.org/capistrano/capistrano.png?branch=v3)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](https://codeclimate.com/github/capistrano/capistrano.png)](https://codeclimate.com/github/capistrano/capistrano) <a href="http://codersclan.net/?repo_id=325&source=small"><img src="http://www.codersclan.net/gs_button/?repo_id=325&size=small" width="69"></a> | |
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 | 1 | |
2 | ## Requirements | |
2 | ## Documentation | |
3 | 3 | |
4 | * Ruby >= 1.9 (JRuby and C-Ruby/YARV are supported) | |
4 | Check out the [online documentation](http://capistranorb.com) of Capistrano 3 hosted via this [repository](https://github.com/capistrano/capistrano.github.io). | |
5 | 5 | |
6 | 6 | ## Support |
7 | 7 | |
8 | 8 | Need help with getting Capistrano up and running? Got a code problem you want to get solved quickly? |
9 | 9 | |
10 | Get <a href="http://codersclan.net/?repo_id=325&source=link">Capistrano support on CodersClan.</a> | |
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 | 11 | |
12 | <a href="http://codersclan.net/?repo_id=325&source=big"><img src="http://www.codersclan.net/gs_button/?repo_id=325" width="200"></a> | |
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. | |
13 | 23 | |
14 | 24 | ## Installation |
15 | 25 | |
16 | 26 | Add this line to your application's Gemfile: |
17 | 27 | |
18 | 28 | ``` ruby |
19 | gem 'capistrano', '~> 3.2.0' | |
29 | gem 'capistrano', '~> 3.3.0' | |
20 | 30 | ``` |
21 | 31 | |
22 | 32 | And then execute: |
54 | 64 | ## Usage |
55 | 65 | |
56 | 66 | ``` sh |
57 | $ bundle exec cap -vT | |
67 | # list all available tasks | |
68 | $ bundle exec cap -T | |
58 | 69 | |
70 | # deploy to the staging environment | |
59 | 71 | $ bundle exec cap staging deploy |
72 | ||
73 | # deploy to the production environment | |
60 | 74 | $ bundle exec cap production deploy |
61 | 75 | |
76 | # simulate deploying to the production environment | |
77 | # does not actually do anything | |
62 | 78 | $ bundle exec cap production deploy --dry-run |
79 | ||
80 | # list task dependencies | |
63 | 81 | $ bundle exec cap production deploy --prereqs |
82 | ||
83 | # trace through task invocations | |
64 | 84 | $ bundle exec cap production deploy --trace |
65 | 85 | ``` |
66 | 86 | |
67 | ## Tasks | |
87 | ## Testing | |
68 | 88 | |
69 | ``` ruby | |
70 | server 'example.com', roles: [:web, :app] | |
71 | server 'example.org', roles: [:db, :workers] | |
72 | desc "Report Uptimes" | |
73 | task :uptime do | |
74 | on roles(:all) do |host| | |
75 | execute :any_command, "with args", :here, "and here" | |
76 | info "Host #{host} (#{host.roles.to_a.join(', ')}):\t#{capture(:uptime)}" | |
77 | end | |
78 | end | |
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 | ||
79 | 97 | ``` |
98 | # To run the RSpec suite | |
99 | $ rake spec | |
80 | 100 | |
81 | **Note**: | |
101 | # To run the Cucumber suite | |
102 | $ rake features | |
82 | 103 | |
83 | **tl;dr**: `execute(:bundle, :install)` and `execute('bundle install')` don't behave identically! | |
84 | ||
85 | `execute()` has a subtle behaviour. When calling `within './directory' { execute(:bundle, :install) }` for example, the first argument to `execute()` is a *Stringish* with ***no whitespace***. This allows the command to pass through the [SSHKit::CommandMap](https://github.com/capistrano/sshkit#the-command-map) which enables a number of powerful features. | |
86 | ||
87 | When the first argument to `execute()` contains whitespace, for example `within './directory' { execute('bundle install') }` (or when using a heredoc), neither Capistrano, nor SSHKit can reliably predict how it should be shell escaped, and thus cannot perform any context, or command mapping, that means that the `within(){}` (as well as `with()`, `as()`, etc) have no effect. There have been a few attempts to resolve this, but we don't consider it a bug although we acknowledge that it might be a little counter intuitive. | |
88 | ## Before / After | |
89 | ||
90 | Where calling on the same task name, executed in order of inclusion | |
91 | ||
92 | ``` ruby | |
93 | # call an existing task | |
94 | before :starting, :ensure_user | |
95 | ||
96 | after :finishing, :notify | |
97 | ||
98 | ||
99 | # or define in block | |
100 | before :starting, :ensure_user do | |
101 | # | |
102 | end | |
103 | ||
104 | after :finishing, :notify do | |
105 | # | |
106 | end | |
104 | # To run the Cucumber suite and leave the VM running (faster for subsequent runs) | |
105 | $ rake features KEEP_RUNNING=1 | |
107 | 106 | ``` |
108 | ||
109 | If it makes sense for your use case (often, that means *generating a file*) | |
110 | the Rake prerequisite mechanism can be used: | |
111 | ||
112 | ``` ruby | |
113 | desc "Create Important File" | |
114 | file 'important.txt' do |t| | |
115 | sh "touch #{t.name}" | |
116 | end | |
117 | desc "Upload Important File" | |
118 | task :upload => 'important.txt' do |t| | |
119 | on roles(:all) do | |
120 | upload!(t.prerequisites.first, '/tmp') | |
121 | end | |
122 | end | |
123 | ``` | |
124 | ||
125 | The final way to call out to other tasks is to simply `invoke()` them: | |
126 | ||
127 | ``` ruby | |
128 | namespace :example do | |
129 | task :one do | |
130 | on roles(:all) { info "One" } | |
131 | end | |
132 | task :two do | |
133 | invoke "example:one" | |
134 | on roles(:all) { info "Two" } | |
135 | end | |
136 | end | |
137 | ``` | |
138 | ||
139 | This method is widely used. | |
140 | ||
141 | ## Getting User Input | |
142 | ||
143 | ``` ruby | |
144 | desc "Ask about breakfast" | |
145 | task :breakfast do | |
146 | ask(:breakfast, "pancakes") | |
147 | on roles(:all) do |h| | |
148 | execute "echo \"$(whoami) wants #{fetch(:breakfast)} for breakfast!\"" | |
149 | end | |
150 | end | |
151 | ``` | |
152 | ||
153 | Perfect, who needs telephones. | |
154 | ||
155 | ||
156 | ## Using password authentication | |
157 | ||
158 | Password authentication can be done via `set` and `ask` in your deploy environment file (e.g.: config/deploy/production.rb) | |
159 | ||
160 | ```ruby | |
161 | set :password, ask('Server password', nil) | |
162 | server 'server.domain.com', user: 'ssh_user_name', port: 22, password: fetch(:password), roles: %w{web app db} | |
163 | ``` | |
164 | ||
165 | ## Running local tasks | |
166 | ||
167 | Local tasks can be run by replacing `on` with `run_locally` | |
168 | ||
169 | ``` ruby | |
170 | desc 'Notify service of deployment' | |
171 | task :notify do | |
172 | run_locally do | |
173 | with rails_env: :development do | |
174 | rake 'service:notify' | |
175 | end | |
176 | end | |
177 | end | |
178 | ``` | |
179 | ||
180 | Of course, you can always just use standard ruby syntax to run things locally | |
181 | ``` ruby | |
182 | desc 'Notify service of deployment' | |
183 | task :notify do | |
184 | %x('RAILS_ENV=development bundle exec rake "service:notify"') | |
185 | end | |
186 | ``` | |
187 | ||
188 | Alternatively you could use the rake syntax | |
189 | ``` ruby | |
190 | desc "Notify service of deployment" | |
191 | task :notify do | |
192 | sh 'RAILS_ENV=development bundle exec rake "service:notify"' | |
193 | end | |
194 | ``` | |
195 | ## Console | |
196 | ||
197 | **Note:** Here be dragons. The console is very immature, but it's much more | |
198 | cleanly architected than previous incarnations and it'll only get better from | |
199 | here on in. | |
200 | ||
201 | Execute arbitrary remote commands, to use this simply add | |
202 | `require 'capistrano/console'` which will add the necessary tasks to your | |
203 | environment: | |
204 | ||
205 | ``` sh | |
206 | $ bundle exec cap staging console | |
207 | ``` | |
208 | ||
209 | Then, after setting up the server connections, this is how that might look: | |
210 | ||
211 | ``` sh | |
212 | $ bundle exec cap production console | |
213 | capistrano console - enter command to execute on production | |
214 | production> uptime | |
215 | INFO [94db8027] Running /usr/bin/env uptime on leehambley@example.com:22 | |
216 | DEBUG [94db8027] Command: /usr/bin/env uptime | |
217 | DEBUG [94db8027] 17:11:17 up 50 days, 22:31, 1 user, load average: 0.02, 0.02, 0.05 | |
218 | INFO [94db8027] Finished in 0.435 seconds command successful. | |
219 | production> who | |
220 | INFO [9ce34809] Running /usr/bin/env who on leehambley@example.com:22 | |
221 | DEBUG [9ce34809] Command: /usr/bin/env who | |
222 | DEBUG [9ce34809] leehambley pts/0 2013-06-13 17:11 (port-11262.pppoe.wtnet.de) | |
223 | INFO [9ce34809] Finished in 0.420 seconds command successful. | |
224 | ``` | |
225 | ||
226 | ## A word about PTYs | |
227 | ||
228 | There is a configuration option which asks the backend driver to ask the remote host | |
229 | to assign the connection a *pty*. A *pty* is a pseudo-terminal, which in effect means | |
230 | *tell the backend that this is an __interactive__ session*. This is normally a bad idea. | |
231 | ||
232 | Most of the differences are best explained by [this page](https://github.com/sstephenson/rbenv/wiki/Unix-shell-initialization) from the author of *rbenv*. | |
233 | ||
234 | **When Capistrano makes a connection it is a *non-login*, *non-interactive* shell. | |
235 | This was not an accident!** | |
236 | ||
237 | It's often used as a band aid to cure issues related to RVM and rbenv not loading login | |
238 | and shell initialisation scripts. In these scenarios RVM and rbenv are the tools at fault, | |
239 | or at least they are being used incorrectly. | |
240 | ||
241 | Whilst, especially in the case of language runtimes (Ruby, Node, Python and friends in | |
242 | particular) there is a temptation to run multiple versions in parallel on a single server | |
243 | and to switch between them using environmental variables, this is an anti-pattern, and | |
244 | symptomatic of bad design (e.g. you're testing a second version of Ruby in production because | |
245 | your company lacks the infrastructure to test this in a staging environment). | |
246 | ||
247 | ## Configuration | |
248 | ||
249 | The following variables are settable: | |
250 | ||
251 | | Variable Name | Description | Notes | | |
252 | |:---------------------:|----------------------------------------------------------------------|-----------------------------------------------------------------| | |
253 | | `:repo_url` | The URL of your scm repository (git, hg, svn) | file://, https://, ssh://, or svn+ssh:// are all supported | | |
254 | | `:branch` | The branch you wish to deploy | This only has meaning for git and hg repos, to specify the branch of an svn repo, set `:repo_url` to the branch location. | | |
255 | | `:scm` | The source control system used | `:git`, `:hg`, `:svn` are currently supported | | |
256 | | `:tmp_dir` | The (optional) temp directory that will be used (default: /tmp) | if you have a shared web host, this setting may need to be set (i.e. /home/user/tmp/capistrano). | | |
257 | ||
258 | __Support removed__ for following variables: | |
259 | ||
260 | | Variable Name | Description | Notes | | |
261 | |:---------------------:|---------------------------------------------------------------------|-----------------------------------------------------------------| | |
262 | | `:copy_exclude` | The (optional) array of files and/or folders excluded from deploy | Replaced by Git's native `.gitattributes`, see [#515](https://github.com/capistrano/capistrano/issues/515) for more info. | | |
263 | 107 | |
264 | 108 | ## SSHKit |
265 | 109 | |
272 | 116 | |
273 | 117 | MIT License (MIT) |
274 | 118 | |
275 | Copyright (c) 2012-2013 Tom Clements, Lee Hambley | |
119 | Copyright (c) 2012-2015 Tom Clements, Lee Hambley | |
276 | 120 | |
277 | 121 | Permission is hereby granted, free of charge, to any person obtaining a copy |
278 | 122 | of this software and associated documentation files (the "Software"), to deal |
0 | 0 | require "bundler/gem_tasks" |
1 | require "cucumber/rake/task" | |
2 | require "rspec/core/rake_task" | |
1 | 3 | |
2 | 4 | task :default => :spec |
3 | require 'rspec/core/rake_task' | |
4 | 5 | RSpec::Core::RakeTask.new |
6 | ||
7 | Cucumber::Rake::Task.new(:features) | |
8 |
19 | 19 | gem.licenses = ['MIT'] |
20 | 20 | |
21 | 21 | gem.post_install_message = <<eos |
22 | Capistrano 3.1 has some breaking changes, like `deploy:restart` callback should be added manually to your deploy.rb. Please, check the CHANGELOG: http://goo.gl/SxB0lr | |
22 | Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr | |
23 | 23 | |
24 | 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. | |
25 | 27 | eos |
26 | 28 | |
29 | gem.required_ruby_version = '>= 1.9.3' | |
27 | 30 | gem.add_dependency 'sshkit', '~> 1.3' |
28 | 31 | gem.add_dependency 'rake', '>= 10.0.0' |
29 | 32 | gem.add_dependency 'i18n' |
18 | 18 | Then the task is successful |
19 | 19 | And contains "install" in the output |
20 | 20 | |
21 | Scenario: Show install task with configuration in a custom location | |
21 | Scenario: Hide install task with configuration in a custom location | |
22 | 22 | And config stage file has line "desc 'Special Task'" |
23 | 23 | And config stage file has line "task :special_stage_task" |
24 | 24 | But the configuration is in a custom location |
25 | 25 | When I run "cap -T" |
26 | 26 | Then the task is successful |
27 | And contains "special_stage_task" in the output | |
27 | And doesn't contain "special_stage_task" in the output |
5 | 5 | |
6 | 6 | Scenario: Creating the repo |
7 | 7 | When I run cap "git:check" |
8 | Then references in the remote repo are listed | |
8 | Then the task is successful | |
9 | And references in the remote repo are listed | |
9 | 10 | |
10 | 11 | Scenario: Creating the directory structure |
11 | 12 | When I run cap "deploy:check:directories" |
21 | 22 | Then directories referenced in :linked_files are created in shared |
22 | 23 | |
23 | 24 | Scenario: Checking linked files - missing file |
24 | Given a required file | |
25 | But the file does not exist | |
25 | Given a linked file "missing_file.txt" | |
26 | But file "missing_file.txt" does not exist in shared path | |
26 | 27 | When I run cap "deploy:check:linked_files" |
27 | Then the task will exit | |
28 | Then the task fails | |
28 | 29 | |
29 | 30 | Scenario: Checking linked files - file exists |
30 | Given a required file | |
31 | And that file exists | |
31 | Given a linked file "existing_file.txt" | |
32 | And file "existing_file.txt" exists in shared path | |
32 | 33 | When I run cap "deploy:check:linked_files" |
33 | Then the task will be successful | |
34 | Then the task is successful | |
34 | 35 | |
35 | 36 | Scenario: Creating a release |
36 | 37 | Given I run cap "deploy:check:directories" |
39 | 40 | And the release is created |
40 | 41 | |
41 | 42 | Scenario: Symlink linked files |
42 | When I run cap "deploy:symlink:linked_files" as part of a release | |
43 | When I run cap "deploy:symlink:linked_files deploy:symlink:release" as part of a release | |
43 | 44 | Then file symlinks are created in the new release |
44 | 45 | |
45 | 46 | Scenario: Symlink linked dirs |
0 | Feature: SSH Connection | |
1 | ||
2 | Background: | |
3 | Given a test app with the default configuration | |
4 | And servers with the roles app and web | |
5 | And a task which executes as root | |
6 | ||
7 | Scenario: Switching from default user to root and back again | |
8 | When I run cap "am_i_root" | |
9 | Then the task is successful | |
10 | And the output matches "I am uid=0\(root\)" followed by "I am uid=\d+\(vagrant\)" |
0 | 0 | Then(/^references in the remote repo are listed$/) do |
1 | expect(@output).to include('refs/heads/master') | |
1 | 2 | end |
2 | 3 | |
3 | 4 | Then(/^the shared path is created$/) do |
21 | 22 | end |
22 | 23 | end |
23 | 24 | |
24 | Then(/^the task will be successful$/) do | |
25 | end | |
26 | ||
27 | ||
28 | Then(/^the task will exit$/) do | |
29 | end | |
30 | ||
31 | 25 | Then(/^the repo is cloned$/) do |
32 | 26 | run_vagrant_command(test_dir_exists(TestApp.repo_path)) |
33 | 27 | end |
37 | 31 | end |
38 | 32 | |
39 | 33 | Then(/^file symlinks are created in the new release$/) do |
40 | pending | |
41 | 34 | TestApp.linked_files.each do |file| |
42 | run_vagrant_command(test_symlink_exists(TestApp.release_path.join(file))) | |
35 | run_vagrant_command(test_symlink_exists(TestApp.current_path.join(file))) | |
43 | 36 | end |
44 | 37 | end |
45 | 38 | |
56 | 49 | |
57 | 50 | Then(/^the deploy\.rb file is created$/) do |
58 | 51 | file = TestApp.test_app_path.join('config/deploy.rb') |
59 | expect(File.exists?(file)).to be_true | |
52 | expect(File.exists?(file)).to be true | |
60 | 53 | end |
61 | 54 | |
62 | 55 | Then(/^the default stage files are created$/) do |
63 | 56 | staging = TestApp.test_app_path.join('config/deploy/staging.rb') |
64 | 57 | production = TestApp.test_app_path.join('config/deploy/production.rb') |
65 | expect(File.exists?(staging)).to be_true | |
66 | expect(File.exists?(production)).to be_true | |
58 | expect(File.exists?(staging)).to be true | |
59 | expect(File.exists?(production)).to be true | |
67 | 60 | end |
68 | 61 | |
69 | 62 | Then(/^the tasks folder is created$/) do |
70 | 63 | path = TestApp.test_app_path.join('lib/capistrano/tasks') |
71 | expect(Dir.exists?(path)).to be_true | |
64 | expect(Dir.exists?(path)).to be true | |
72 | 65 | end |
73 | 66 | |
74 | 67 | Then(/^the specified stage files are created$/) do |
75 | 68 | qa = TestApp.test_app_path.join('config/deploy/qa.rb') |
76 | 69 | production = TestApp.test_app_path.join('config/deploy/production.rb') |
77 | expect(File.exists?(qa)).to be_true | |
78 | expect(File.exists?(production)).to be_true | |
70 | expect(File.exists?(qa)).to be true | |
71 | expect(File.exists?(production)).to be true | |
79 | 72 | end |
80 | 73 | |
81 | 74 | Then(/^it creates the file with the remote_task prerequisite$/) do |
89 | 82 | end |
90 | 83 | |
91 | 84 | Then(/^the task is successful$/) do |
92 | expect(@success).to be_true | |
85 | expect(@success).to be true | |
86 | end | |
87 | ||
88 | Then(/^the task fails$/) do | |
89 | expect(@success).to be_falsey | |
93 | 90 | end |
94 | 91 | |
95 | 92 | Then(/^the failure task will run$/) do |
99 | 96 | |
100 | 97 | Then(/^the failure task will not run$/) do |
101 | 98 | failed = TestApp.shared_path.join('failed') |
102 | !run_vagrant_command(test_file_exists(failed)) | |
99 | expect { run_vagrant_command(test_file_exists(failed)) } | |
100 | .to raise_error(VagrantHelpers::VagrantSSHCommandError) | |
103 | 101 | end |
104 | 102 | |
105 | 103 | When(/^an error is raised$/) do |
107 | 105 | run_vagrant_command(test_file_exists(error)) |
108 | 106 | end |
109 | 107 | |
110 | Then(/contains "(.*?)" in the output/) do |expected| | |
108 | Then(/contains "([^"]*)" in the output/) do |expected| | |
111 | 109 | expect(@output).to include(expected) |
112 | 110 | end |
111 | ||
112 | Then(/the output matches "([^"]*)" followed by "([^"]*)"/) do |expected, followedby| | |
113 | expect(@output).to match(/#{expected}.*#{followedby}/m) | |
114 | end | |
115 | ||
116 | Then(/doesn't contain "([^"]*)" in the output/) do |expected| | |
117 | expect(@output).not_to include(expected) | |
118 | end |
0 | 0 | When(/^I run cap "(.*?)"$/) do |task| |
1 | @success = TestApp.cap(task) | |
1 | @success, @output = TestApp.cap(task) | |
2 | 2 | end |
3 | 3 | |
4 | 4 | When(/^I run cap "(.*?)" as part of a release$/) do |task| |
5 | 5 | vagrant_cli_command('up') rescue nil |
6 | 6 | end |
7 | 7 | |
8 | Given(/^a required file$/) do | |
8 | Given(/^a linked file "(.*?)"$/) do |file| | |
9 | # ignoring other linked files | |
10 | TestApp.append_to_deploy_file("set :linked_files, ['#{file}']") | |
9 | 11 | end |
10 | 12 | |
11 | Given(/^that file exists$/) do | |
12 | run_vagrant_command("touch #{TestApp.linked_file}") | |
13 | Given(/^file "(.*?)" exists in shared path$/) do |file| | |
14 | file_shared_path = TestApp.shared_path.join(file) | |
15 | run_vagrant_command("mkdir -p #{TestApp.shared_path}") | |
16 | run_vagrant_command("touch #{file_shared_path}") | |
13 | 17 | end |
14 | 18 | |
15 | Given(/^the file does not exist$/) do | |
16 | pending | |
17 | file = TestApp.linked_file | |
18 | run_vagrant_command("[ -f #{file} ] && rm #{file}") | |
19 | Given(/^file "(.*?)" does not exist in shared path$/) do |file| | |
20 | file_shared_path = TestApp.shared_path.join(file) | |
21 | run_vagrant_command("mkdir -p #{TestApp.shared_path}") | |
22 | run_vagrant_command("touch #{file_shared_path} && rm #{file_shared_path}") | |
19 | 23 | end |
20 | 24 | |
21 | 25 | Given(/^a custom task to generate a file$/) do |
22 | 26 | TestApp.copy_task_to_test_app('spec/support/tasks/database.rake') |
27 | end | |
28 | ||
29 | Given(/^a task which executes as root$/) do | |
30 | TestApp.copy_task_to_test_app('spec/support/tasks/root.rake') | |
23 | 31 | end |
24 | 32 | |
25 | 33 | Given(/config stage file has line "(.*?)"/) do |line| |
0 | require 'kuroko' | |
0 | PROJECT_ROOT = File.expand_path('../../../', __FILE__) | |
1 | VAGRANT_ROOT = File.join(PROJECT_ROOT, 'spec/support') | |
2 | VAGRANT_BIN = ENV['VAGRANT_BIN'] || "vagrant" | |
1 | 3 | |
2 | project_root = File.expand_path('../../../', __FILE__) | |
3 | vagrant_root = File.join(project_root, 'spec/support') | |
4 | ||
5 | Kuroko.configure do |config| | |
6 | config.vagrant_root = 'spec/support' | |
4 | at_exit do | |
5 | if ENV['KEEP_RUNNING'] | |
6 | VagrantHelpers.run_vagrant_command("rm -rf /home/vagrant/var") | |
7 | end | |
7 | 8 | end |
8 | 9 | |
9 | puts vagrant_root.inspect | |
10 | ||
11 | 10 | require_relative '../../spec/support/test_app' |
0 | 0 | module RemoteCommandHelpers |
1 | ||
2 | 1 | def test_dir_exists(path) |
3 | 2 | exists?('d', path) |
4 | 3 | end |
12 | 11 | end |
13 | 12 | |
14 | 13 | def exists?(type, path) |
15 | %{[ -#{type} "#{path}" ] && echo "#{path} exists." || echo "Error: #{path} does not exist."} | |
14 | %{[ -#{type} "#{path}" ]} | |
16 | 15 | end |
17 | 16 | |
18 | 17 | def safely_remove_file(path) |
19 | run_vagrant_command("rm #{test_file}") rescue Vagrant::Errors::VagrantError | |
18 | run_vagrant_command("rm #{test_file}") rescue VagrantHelpers::VagrantSSHCommandError | |
20 | 19 | end |
21 | 20 | end |
22 | 21 |
0 | module VagrantHelpers | |
1 | extend self | |
2 | ||
3 | class VagrantSSHCommandError < RuntimeError; end | |
4 | ||
5 | at_exit do | |
6 | if ENV['KEEP_RUNNING'] | |
7 | puts "Vagrant vm will be left up because KEEP_RUNNING is set." | |
8 | puts "Rerun without KEEP_RUNNING set to cleanup the vm." | |
9 | else | |
10 | vagrant_cli_command("destroy -f") | |
11 | end | |
12 | end | |
13 | ||
14 | def vagrant_cli_command(command) | |
15 | 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 | |
20 | end | |
21 | $? | |
22 | end | |
23 | ||
24 | 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 | |
30 | end | |
31 | ||
32 | end | |
33 | ||
34 | World(VagrantHelpers) |
0 | 0 | require 'rake' |
1 | 1 | require 'sshkit' |
2 | require 'sshkit/dsl' | |
2 | ||
3 | require 'io/console' | |
3 | 4 | |
4 | 5 | Rake.application.options.trace = true |
5 | 6 |
15 | 15 | end |
16 | 16 | |
17 | 17 | def sort_options(options) |
18 | options.push(version, roles, dry_run, hostfilter) | |
19 | super | |
18 | not_applicable_to_capistrano = %w(quiet silent verbose) | |
19 | options.reject! do |(switch, *)| | |
20 | switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/ | |
21 | end | |
22 | ||
23 | super.push(version, dry_run, roles, hostfilter) | |
20 | 24 | end |
25 | ||
26 | def handle_options | |
27 | options.rakelib = ['rakelib'] | |
28 | options.trace_output = $stderr | |
29 | ||
30 | OptionParser.new do |opts| | |
31 | opts.banner = "See full documentation at http://capistranorb.com/." | |
32 | opts.separator "" | |
33 | opts.separator "Install capistrano in a project:" | |
34 | opts.separator " bundle exec cap install [STAGES=qa,staging,production,...]" | |
35 | opts.separator "" | |
36 | opts.separator "Show available tasks:" | |
37 | opts.separator " bundle exec cap -T" | |
38 | opts.separator "" | |
39 | opts.separator "Invoke (or simulate invoking) a task:" | |
40 | opts.separator " bundle exec cap [--dry-run] STAGE TASK" | |
41 | opts.separator "" | |
42 | opts.separator "Advanced options:" | |
43 | ||
44 | opts.on_tail("-h", "--help", "-H", "Display this help message.") do | |
45 | puts opts | |
46 | exit | |
47 | end | |
48 | ||
49 | standard_rake_options.each { |args| opts.on(*args) } | |
50 | opts.environment('RAKEOPT') | |
51 | end.parse! | |
52 | end | |
53 | ||
21 | 54 | |
22 | 55 | def top_level_tasks |
23 | 56 | if tasks_without_stage_dependency.include?(@top_level_tasks.first) |
25 | 58 | else |
26 | 59 | @top_level_tasks.unshift(ensure_stage.to_s) |
27 | 60 | end |
61 | end | |
62 | ||
63 | def display_error_message(ex) | |
64 | 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 | |
70 | trace "(Backtrace restricted to imported tasks)" | |
71 | end | |
72 | super | |
28 | 73 | end |
29 | 74 | |
30 | 75 | def exit_because_of_exception(ex) |
41 | 86 | if options.show_tasks |
42 | 87 | invoke 'load:defaults' |
43 | 88 | set(:stage, '') |
44 | Dir[deploy_config_path, stage_definitions].each { |f| add_import f } | |
89 | Dir[deploy_config_path].each { |f| add_import f } | |
45 | 90 | end |
46 | 91 | |
47 | 92 | super |
73 | 118 | |
74 | 119 | def roles |
75 | 120 | ['--roles ROLES', '-r', |
76 | "Filter command to only apply to these roles (separate multiple roles with a comma)", | |
121 | "Run SSH commands only on hosts matching these roles", | |
77 | 122 | lambda { |value| |
78 | Configuration.env.set(:filter, roles: value.split(",")) | |
123 | Configuration.env.add_cmdline_filter(:role, value) | |
79 | 124 | } |
80 | 125 | ] |
81 | 126 | end |
82 | 127 | |
83 | 128 | def hostfilter |
84 | 129 | ['--hosts HOSTS', '-z', |
85 | "Filter command to only apply to these hosts (separate multiple hosts with a comma)", | |
130 | "Run SSH commands only on matching hosts", | |
86 | 131 | lambda { |value| |
87 | Configuration.env.set(:filter, hosts: value.split(",")) | |
132 | Configuration.env.add_cmdline_filter(:host, value) | |
88 | 133 | } |
89 | 134 | ] |
90 | 135 | end |
0 | require 'capistrano/configuration' | |
1 | ||
2 | module Capistrano | |
3 | class Configuration | |
4 | 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 | |
41 | 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 | |
52 | end | |
53 | end | |
54 | end | |
55 | end |
1 | 1 | class Configuration |
2 | 2 | class Question |
3 | 3 | |
4 | def initialize(env, key, default) | |
5 | @env, @key, @default = env, key, default | |
4 | def initialize(key, default, options = {}) | |
5 | @key, @default, @options = key, default, options | |
6 | 6 | end |
7 | 7 | |
8 | 8 | def call |
9 | 9 | ask_question |
10 | save_response | |
10 | value_or_default | |
11 | 11 | end |
12 | 12 | |
13 | 13 | private |
14 | attr_reader :env, :key, :default | |
14 | attr_reader :key, :default, :options | |
15 | 15 | |
16 | 16 | def ask_question |
17 | 17 | $stdout.print question |
18 | 18 | end |
19 | 19 | |
20 | def save_response | |
21 | env.set(key, value) | |
22 | end | |
23 | ||
24 | def value | |
20 | def value_or_default | |
25 | 21 | if response.empty? |
26 | 22 | default |
27 | 23 | else |
30 | 26 | end |
31 | 27 | |
32 | 28 | def response |
33 | @response ||= $stdin.gets.chomp | |
29 | return @response if defined? @response | |
30 | ||
31 | @response = (gets || "").chomp | |
32 | end | |
33 | ||
34 | def gets | |
35 | if echo? | |
36 | $stdin.gets | |
37 | else | |
38 | $stdin.noecho(&:gets).tap{ $stdout.print "\n" } | |
39 | end | |
40 | rescue Errno::EIO | |
41 | # when stdio gets closed | |
42 | end | |
43 | ||
44 | def question | |
45 | I18n.t(:question, key: key, default_value: default, scope: :capistrano) | |
34 | 46 | end |
35 | 47 | |
36 | def question | |
37 | I18n.t(:question, key: key, default_value: default, scope: :capistrano) | |
48 | def echo? | |
49 | (options || {}).fetch(:echo, true) | |
38 | 50 | end |
39 | 51 | end |
40 | 52 | end |
10 | 10 | |
11 | 11 | def add_roles(roles) |
12 | 12 | Array(roles).each { |role| add_role(role) } |
13 | self | |
13 | 14 | end |
14 | 15 | alias roles= add_roles |
15 | 16 | |
16 | 17 | def add_role(role) |
17 | 18 | roles.add role.to_sym |
19 | self | |
18 | 20 | end |
19 | 21 | |
20 | 22 | def has_role?(role) |
22 | 24 | end |
23 | 25 | |
24 | 26 | def select?(options) |
25 | selector = Selector.for(options) | |
26 | selector.call(self) | |
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 | |
37 | return false unless result | |
38 | end | |
39 | return true | |
27 | 40 | end |
28 | 41 | |
29 | 42 | def primary |
39 | 52 | @properties ||= Properties.new |
40 | 53 | end |
41 | 54 | |
42 | def netssh_options_with_options | |
43 | @netssh_options ||= netssh_options_without_options.merge( fetch(:ssh_options) || {} ) | |
55 | def netssh_options | |
56 | @netssh_options ||= super.merge( fetch(:ssh_options) || {} ) | |
44 | 57 | end |
45 | alias_method :netssh_options_without_options, :netssh_options | |
46 | alias_method :netssh_options, :netssh_options_with_options | |
47 | 58 | |
48 | 59 | def roles_array |
49 | 60 | roles.to_a |
50 | 61 | end |
51 | 62 | |
52 | 63 | def matches?(other) |
53 | user == other.user && hostname == other.hostname && port == other.port | |
64 | hostname == other.hostname | |
54 | 65 | end |
55 | 66 | |
56 | 67 | private |
70 | 81 | end |
71 | 82 | |
72 | 83 | def set(key, value) |
73 | @properties[key] = value | |
84 | pval = @properties[key] | |
85 | if pval.is_a? Hash and value.is_a? Hash | |
86 | pval.merge!(value) | |
87 | elsif pval.is_a? Set and value.is_a? Set | |
88 | pval.merge(value) | |
89 | elsif pval.is_a? Array and value.is_a? Array | |
90 | pval.concat value | |
91 | else | |
92 | @properties[key] = value | |
93 | end | |
74 | 94 | end |
75 | 95 | |
76 | 96 | def fetch(key) |
77 | 97 | @properties[key] |
78 | 98 | end |
79 | 99 | |
80 | def respond_to?(method) | |
100 | def respond_to?(method, include_all=false) | |
81 | 101 | @properties.has_key?(method) |
82 | 102 | end |
83 | 103 | |
105 | 125 | |
106 | 126 | end |
107 | 127 | |
108 | class Selector | |
109 | def initialize(options) | |
110 | @options = options | |
111 | end | |
112 | ||
113 | def self.for(options) | |
114 | if options.has_key?(:exclude) | |
115 | Exclusive | |
116 | else | |
117 | self | |
118 | end.new(options) | |
119 | end | |
120 | ||
121 | def callable | |
122 | if key.respond_to?(:call) | |
123 | key | |
124 | else | |
125 | ->(server) { server.fetch(key) } | |
126 | end | |
127 | end | |
128 | ||
129 | def call(server) | |
130 | callable.call(server) | |
131 | end | |
132 | ||
133 | private | |
134 | attr_reader :options | |
135 | ||
136 | def key | |
137 | options[:filter] || options[:select] || all | |
138 | end | |
139 | ||
140 | def all | |
141 | ->(server) { :all } | |
142 | end | |
143 | ||
144 | class Exclusive < Selector | |
145 | ||
146 | def key | |
147 | options[:exclude] | |
148 | end | |
149 | ||
150 | def call(server) | |
151 | !callable.call(server) | |
152 | end | |
153 | end | |
154 | ||
155 | end | |
156 | ||
157 | 128 | end |
158 | 129 | end |
159 | 130 | end |
0 | module Capistrano | |
1 | class Configuration | |
2 | class Servers | |
3 | class HostFilter | |
4 | ||
5 | def initialize(available) | |
6 | @available = available | |
7 | end | |
8 | ||
9 | def self.for(available) | |
10 | new(available).hosts | |
11 | end | |
12 | ||
13 | def hosts | |
14 | if host_filter.any? | |
15 | @available.select { |server| host_filter.include? server.hostname } | |
16 | else | |
17 | @available | |
18 | end | |
19 | end | |
20 | ||
21 | private | |
22 | ||
23 | def filter | |
24 | if host_filter.any? | |
25 | host_filter | |
26 | else | |
27 | @available | |
28 | end | |
29 | end | |
30 | ||
31 | def host_filter | |
32 | env_filter | configuration_filter | |
33 | end | |
34 | ||
35 | def configuration_filter | |
36 | ConfigurationFilter.new.hosts | |
37 | end | |
38 | ||
39 | def env_filter | |
40 | EnvFilter.new.hosts | |
41 | end | |
42 | ||
43 | class ConfigurationFilter | |
44 | ||
45 | def hosts | |
46 | if filter | |
47 | Array(filter.fetch(:hosts, [])) | |
48 | else | |
49 | [] | |
50 | end | |
51 | end | |
52 | ||
53 | def config | |
54 | Configuration.env | |
55 | end | |
56 | ||
57 | def filter | |
58 | config.fetch(:filter) || config.fetch(:select) | |
59 | end | |
60 | end | |
61 | ||
62 | ||
63 | class EnvFilter | |
64 | ||
65 | def hosts | |
66 | if filter | |
67 | filter.split(',') | |
68 | else | |
69 | [] | |
70 | end | |
71 | end | |
72 | ||
73 | def filter | |
74 | ENV['HOSTS'] | |
75 | end | |
76 | end | |
77 | ||
78 | end | |
79 | end | |
80 | end | |
81 | end |
0 | module Capistrano | |
1 | class Configuration | |
2 | class Servers | |
3 | class RoleFilter | |
4 | ||
5 | def initialize(required, available) | |
6 | @required, @available = required, available | |
7 | end | |
8 | ||
9 | def self.for(required, available) | |
10 | new(required, available).roles | |
11 | end | |
12 | ||
13 | def roles | |
14 | if required.include?(:all) | |
15 | available | |
16 | else | |
17 | required.select { |name| available.include? name } | |
18 | end | |
19 | end | |
20 | ||
21 | private | |
22 | ||
23 | def required | |
24 | Array(@required).flat_map(&:to_sym) | |
25 | end | |
26 | ||
27 | def available | |
28 | if role_filter.any? | |
29 | role_filter | |
30 | else | |
31 | @available | |
32 | end | |
33 | end | |
34 | ||
35 | def role_filter | |
36 | env_filter | configuration_filter | |
37 | end | |
38 | ||
39 | def configuration_filter | |
40 | ConfigurationFilter.new.roles | |
41 | end | |
42 | ||
43 | def env_filter | |
44 | EnvFilter.new.roles | |
45 | end | |
46 | ||
47 | class ConfigurationFilter | |
48 | ||
49 | def roles | |
50 | if filter | |
51 | Array(filter.fetch(:roles, [])).map(&:to_sym) | |
52 | else | |
53 | [] | |
54 | end | |
55 | end | |
56 | ||
57 | def config | |
58 | Configuration.env | |
59 | end | |
60 | ||
61 | def filter | |
62 | config.fetch(:filter) || config.fetch(:select) | |
63 | end | |
64 | end | |
65 | ||
66 | ||
67 | class EnvFilter | |
68 | ||
69 | def roles | |
70 | if filter | |
71 | filter.split(',').map(&:to_sym) | |
72 | else | |
73 | [] | |
74 | end | |
75 | end | |
76 | ||
77 | def filter | |
78 | ENV['ROLES'] | |
79 | end | |
80 | end | |
81 | ||
82 | end | |
83 | end | |
84 | end | |
85 | end |
0 | 0 | require 'set' |
1 | require_relative 'servers/role_filter' | |
2 | require_relative 'servers/host_filter' | |
1 | require 'capistrano/configuration' | |
2 | require 'capistrano/configuration/filter' | |
3 | ||
3 | 4 | module Capistrano |
4 | 5 | class Configuration |
5 | 6 | class Servers |
6 | 7 | include Enumerable |
7 | 8 | |
8 | 9 | def add_host(host, properties={}) |
9 | servers.add server(host).with(properties) | |
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) | |
15 | else | |
16 | servers << new_host.with(properties) | |
17 | end | |
10 | 18 | end |
11 | 19 | |
12 | 20 | def add_role(role, hosts, options={}) |
13 | Array(hosts).each { |host| add_host(host, options.merge(roles: role)) } | |
21 | options_deepcopy = Marshal.dump(options.merge(roles: role)) | |
22 | Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) } | |
14 | 23 | end |
15 | 24 | |
16 | 25 | def roles_for(names) |
17 | 26 | options = extract_options(names) |
18 | fetch_roles(names, options) | |
27 | s = Filter.new(:role, names).filter(servers) | |
28 | s.select { |server| server.select?(options) } | |
29 | end | |
30 | ||
31 | def role_properties_for(rolenames) | |
32 | roles = rolenames.to_set | |
33 | rps = Set.new unless block_given? | |
34 | roles_for(rolenames).each do |host| | |
35 | host.roles.intersection(roles).each do |role| | |
36 | [host.properties.fetch(role)].flatten(1).each do |props| | |
37 | if block_given? | |
38 | yield host, role, props | |
39 | else | |
40 | rps << (props || {}).merge( role: role, hostname: host.hostname ) | |
41 | end | |
42 | end | |
43 | end | |
44 | end | |
45 | block_given? ? nil: rps | |
19 | 46 | end |
20 | 47 | |
21 | 48 | def fetch_primary(role) |
22 | hosts = fetch(role) | |
49 | hosts = roles_for([role]) | |
23 | 50 | hosts.find(&:primary) || hosts.first |
24 | 51 | end |
25 | 52 | |
29 | 56 | |
30 | 57 | private |
31 | 58 | |
32 | def server(host) | |
33 | servers.find { |server| server.matches? Server[host] } || Server[host] | |
34 | end | |
35 | ||
36 | def fetch(role) | |
37 | servers.find_all { |server| server.has_role? role} | |
38 | end | |
39 | ||
40 | def fetch_roles(required, options) | |
41 | filter_roles = RoleFilter.for(required, available_roles) | |
42 | HostFilter.for(select(servers_with_roles(filter_roles), options)) | |
43 | end | |
44 | ||
45 | def servers_with_roles(roles) | |
46 | roles.flat_map { |role| fetch role }.uniq | |
47 | end | |
48 | ||
49 | def select(servers, options) | |
50 | servers.select { |server| server.select?(options) } | |
51 | end | |
52 | ||
53 | def available_roles | |
54 | servers.flat_map { |server| server.roles_array }.uniq | |
55 | end | |
56 | ||
57 | 59 | def servers |
58 | @servers ||= Set.new | |
60 | @servers ||= [] | |
59 | 61 | end |
60 | 62 | |
61 | 63 | def extract_options(array) |
0 | require_relative 'configuration/filter' | |
0 | 1 | require_relative 'configuration/question' |
2 | require_relative 'configuration/server' | |
1 | 3 | require_relative 'configuration/servers' |
2 | require_relative 'configuration/server' | |
3 | 4 | |
4 | 5 | module Capistrano |
5 | 6 | class Configuration |
6 | 7 | |
7 | class << self | |
8 | def env | |
9 | @env ||= new | |
10 | end | |
11 | ||
12 | def reset! | |
13 | @env = new | |
14 | end | |
8 | def initialize(config = nil) | |
9 | @config ||= config | |
15 | 10 | end |
16 | 11 | |
17 | def ask(key, default=nil) | |
18 | question = Question.new(self, key, default) | |
12 | def self.env | |
13 | @env ||= new | |
14 | end | |
15 | ||
16 | def self.reset! | |
17 | @env = new | |
18 | end | |
19 | ||
20 | def ask(key, default=nil, options={}) | |
21 | question = Question.new(key, default, options) | |
19 | 22 | set(key, question) |
20 | 23 | end |
21 | 24 | |
22 | 25 | def set(key, value) |
23 | 26 | config[key] = value |
27 | end | |
28 | ||
29 | def set_if_empty(key, value) | |
30 | config[key] = value unless config.has_key? key | |
24 | 31 | end |
25 | 32 | |
26 | 33 | def delete(key) |
55 | 62 | servers.roles_for(names) |
56 | 63 | end |
57 | 64 | |
65 | def role_properties_for(names, &block) | |
66 | servers.role_properties_for(names, &block) | |
67 | end | |
68 | ||
58 | 69 | def primary(role) |
59 | 70 | servers.fetch_primary(role) |
60 | 71 | end |
74 | 85 | sshkit.backend.configure do |backend| |
75 | 86 | backend.pty = fetch(:pty) |
76 | 87 | backend.connection_timeout = fetch(:connection_timeout) |
77 | backend.ssh_options = fetch(:ssh_options) if fetch(:ssh_options) | |
88 | backend.ssh_options = (backend.ssh_options || {}).merge(fetch(:ssh_options,{})) | |
78 | 89 | end |
79 | 90 | end |
80 | 91 | end |
83 | 94 | @timestamp ||= Time.now.utc |
84 | 95 | end |
85 | 96 | |
97 | 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,{}) | |
102 | @filters << Filter.new(:host, fh[:host]) if fh[:host] | |
103 | @filters << Filter.new(:role, fh[:role]) if fh[:role] | |
104 | end | |
105 | ||
106 | def add_cmdline_filter(type, values) | |
107 | cmdline_filters << Filter.new(type, values) | |
108 | end | |
109 | ||
110 | def filter list | |
111 | setup_filters if @filters.nil? | |
112 | @filters.reduce(list) { |l,f| f.filter l } | |
113 | end | |
114 | ||
86 | 115 | private |
116 | ||
117 | def cmdline_filters | |
118 | @cmdline_filters ||= [] | |
119 | end | |
87 | 120 | |
88 | 121 | def servers |
89 | 122 | @servers ||= Servers.new |
0 | set :scm, :git | |
1 | set :branch, :master | |
2 | set :deploy_to, -> { "/var/www/#{fetch(:application)}" } | |
3 | set :tmp_dir, "/tmp" | |
0 | set_if_empty :scm, :git | |
1 | set_if_empty :branch, :master | |
2 | set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" } | |
3 | set_if_empty :tmp_dir, "/tmp" | |
4 | 4 | |
5 | set :default_env, {} | |
6 | set :keep_releases, 5 | |
5 | set_if_empty :default_env, {} | |
6 | set_if_empty :keep_releases, 5 | |
7 | 7 | |
8 | set :format, :pretty | |
9 | set :log_level, :debug | |
8 | set_if_empty :format, :pretty | |
9 | set_if_empty :log_level, :debug | |
10 | 10 | |
11 | set :pty, false | |
11 | set_if_empty :pty, false | |
12 | ||
13 | set_if_empty :local_user, -> { Etc.getlogin } |
22 | 22 | env.set(key, value) |
23 | 23 | end |
24 | 24 | |
25 | def set_if_empty(key, value) | |
26 | env.set_if_empty(key, value) | |
27 | end | |
28 | ||
25 | 29 | def delete(key) |
26 | 30 | env.delete(key) |
27 | 31 | end |
28 | 32 | |
29 | def ask(key, value) | |
30 | env.ask(key, value) | |
33 | def ask(key, value, options={}) | |
34 | env.ask(key, value, options) | |
31 | 35 | end |
32 | 36 | |
33 | 37 | def role(name, servers, options={}) |
42 | 46 | env.roles_for(names.flatten) |
43 | 47 | end |
44 | 48 | |
49 | def role_properties(*names, &block) | |
50 | env.role_properties_for(names, &block) | |
51 | end | |
52 | ||
45 | 53 | def release_roles(*names) |
46 | names << { exclude: :no_release } unless names.last.is_a? Hash | |
54 | if names.last.is_a? Hash | |
55 | names.last.merge!({ :exclude => :no_release }) | |
56 | else | |
57 | names << { exclude: :no_release } | |
58 | end | |
47 | 59 | roles(*names) |
48 | 60 | end |
49 | 61 |
53 | 53 | end |
54 | 54 | |
55 | 55 | def repo_path |
56 | deploy_path.join('repo') | |
56 | Pathname.new(fetch(:repo_path, ->(){deploy_path.join('repo')})) | |
57 | 57 | end |
58 | 58 | |
59 | 59 | def shared_path |
0 | require 'capistrano/upload_task' | |
1 | ||
0 | 2 | module Capistrano |
1 | 3 | module TaskEnhancements |
2 | 4 | def before(task, prerequisite, *args, &block) |
18 | 20 | end |
19 | 21 | |
20 | 22 | def define_remote_file_task(task, target_roles) |
21 | Rake::Task.define_task(task) do |t| | |
23 | Capistrano::UploadTask.define_task(task) do |t| | |
22 | 24 | prerequisite_file = t.prerequisites.first |
23 | 25 | file = shared_path.join(t.name) |
24 | 26 | |
25 | 27 | on roles(target_roles) do |
26 | unless test "[ -f #{file} ]" | |
28 | unless test "[ -f #{file.to_s.shellescape} ]" | |
27 | 29 | info "Uploading #{prerequisite_file} to #{file}" |
28 | 30 | upload! File.open(prerequisite_file), file |
29 | 31 | end |
50 | 52 | end |
51 | 53 | |
52 | 54 | def exit_deploy_because_of_exception(ex) |
53 | warn t(:deploy_failed, ex: ex.inspect) | |
55 | warn t(:deploy_failed, ex: ex.message) | |
54 | 56 | invoke 'deploy:failed' |
55 | 57 | exit(false) |
56 | 58 | end |
2 | 2 | require 'capistrano/dsl/paths' |
3 | 3 | require 'capistrano/dsl/stages' |
4 | 4 | require 'capistrano/dsl/env' |
5 | require 'capistrano/configuration/filter' | |
5 | 6 | |
6 | 7 | module Capistrano |
7 | 8 | module DSL |
32 | 33 | branch: fetch(:branch), |
33 | 34 | user: local_user, |
34 | 35 | sha: fetch(:current_revision), |
35 | release: release_timestamp) | |
36 | release: fetch(:release_timestamp)) | |
36 | 37 | ) |
37 | 38 | end |
38 | 39 | |
41 | 42 | end |
42 | 43 | |
43 | 44 | def local_user |
44 | Etc.getlogin | |
45 | fetch(:local_user) | |
45 | 46 | end |
46 | 47 | |
47 | 48 | def lock(locked_version) |
48 | 49 | VersionValidator.new(locked_version).verify |
49 | 50 | end |
51 | ||
52 | def on(hosts, options={}, &block) | |
53 | subset_copy = Marshal.dump(Configuration.env.filter(hosts)) | |
54 | SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block) | |
55 | end | |
56 | ||
57 | def run_locally(&block) | |
58 | SSHKit::Backend::Local.new(&block).run | |
59 | end | |
60 | ||
50 | 61 | end |
51 | 62 | end |
52 | 63 | self.extend Capistrano::DSL |
17 | 17 | end |
18 | 18 | |
19 | 19 | def check |
20 | test! :git, :'ls-remote -h', repo_url | |
20 | git :'ls-remote --heads', repo_url | |
21 | 21 | end |
22 | 22 | |
23 | 23 | def clone |
29 | 29 | end |
30 | 30 | |
31 | 31 | def release |
32 | git :archive, fetch(:branch), '| tar -x -C', release_path | |
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 | |
33 | 39 | end |
34 | 40 | |
35 | 41 | def fetch_revision |
36 | context.capture(:git, "rev-parse --short #{fetch(:branch)}") | |
42 | context.capture(:git, "rev-list --max-count=1 --abbrev-commit #{fetch(:branch)}") | |
37 | 43 | end |
38 | 44 | end |
39 | 45 | end |
26 | 26 | end |
27 | 27 | |
28 | 28 | def release |
29 | hg "archive", release_path, "--rev", fetch(:branch) | |
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 | |
30 | 36 | end |
31 | 37 | |
32 | 38 | def fetch_revision |
27 | 27 | end |
28 | 28 | |
29 | 29 | def release |
30 | svn :export, '.', release_path | |
30 | svn :export, '--force', '.', release_path | |
31 | 31 | end |
32 | 32 | |
33 | 33 | def fetch_revision |
34 | context.capture(:svn, "log -r HEAD -q | tail -n 2 | head -n 1 | sed s/\ \|.*/''/") | |
34 | context.capture(:svnversion, repo_path) | |
35 | 35 | end |
36 | 36 | end |
37 | 37 | end |
43 | 43 | desc 'Check shared and release directories exist' |
44 | 44 | task :directories do |
45 | 45 | on release_roles :all do |
46 | execute :mkdir, '-pv', shared_path, releases_path | |
46 | execute :mkdir, '-p', shared_path, releases_path | |
47 | 47 | end |
48 | 48 | end |
49 | 49 | |
51 | 51 | task :linked_dirs do |
52 | 52 | next unless any? :linked_dirs |
53 | 53 | on release_roles :all do |
54 | execute :mkdir, '-pv', linked_dirs(shared_path) | |
54 | execute :mkdir, '-p', linked_dirs(shared_path) | |
55 | 55 | end |
56 | 56 | end |
57 | 57 | |
59 | 59 | task :make_linked_dirs do |
60 | 60 | next unless any? :linked_files |
61 | 61 | on release_roles :all do |host| |
62 | execute :mkdir, '-pv', linked_file_dirs(shared_path) | |
62 | execute :mkdir, '-p', linked_file_dirs(shared_path) | |
63 | 63 | end |
64 | 64 | end |
65 | 65 | |
81 | 81 | desc 'Symlink release to current' |
82 | 82 | task :release do |
83 | 83 | on release_roles :all do |
84 | execute :rm, '-rf', current_path | |
85 | execute :ln, '-s', release_path, current_path | |
84 | tmp_current_path = release_path.parent.join(current_path.basename) | |
85 | execute :ln, '-s', release_path, tmp_current_path | |
86 | execute :mv, tmp_current_path, current_path.parent | |
86 | 87 | end |
87 | 88 | end |
88 | 89 | |
96 | 97 | task :linked_dirs do |
97 | 98 | next unless any? :linked_dirs |
98 | 99 | on release_roles :all do |
99 | execute :mkdir, '-pv', linked_dir_parents(release_path) | |
100 | execute :mkdir, '-p', linked_dir_parents(release_path) | |
100 | 101 | |
101 | 102 | fetch(:linked_dirs).each do |dir| |
102 | 103 | target = release_path.join(dir) |
115 | 116 | task :linked_files do |
116 | 117 | next unless any? :linked_files |
117 | 118 | on release_roles :all do |
118 | execute :mkdir, '-pv', linked_file_dirs(release_path) | |
119 | execute :mkdir, '-p', linked_file_dirs(release_path) | |
119 | 120 | |
120 | 121 | fetch(:linked_files).each do |file| |
121 | 122 | target = release_path.join(file) |
134 | 135 | desc 'Clean up old releases' |
135 | 136 | task :cleanup do |
136 | 137 | on release_roles :all do |host| |
137 | releases = capture(:ls, '-x', releases_path).split | |
138 | releases = capture(:ls, '-xtr', releases_path).split | |
138 | 139 | if releases.count >= fetch(:keep_releases) |
139 | 140 | info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count) |
140 | 141 | directories = (releases - releases.last(fetch(:keep_releases))) |
153 | 154 | desc 'Remove and archive rolled-back release.' |
154 | 155 | task :cleanup_rollback do |
155 | 156 | on release_roles(:all) do |
156 | last_release = capture(:ls, '-xr', releases_path).split.first | |
157 | last_release = capture(:ls, '-xt', releases_path).split.first | |
157 | 158 | last_release_path = releases_path.join(last_release) |
158 | 159 | if test "[ `readlink #{current_path}` != #{last_release_path} ]" |
159 | 160 | execute :tar, '-czf', |
188 | 189 | |
189 | 190 | task :rollback_release_path do |
190 | 191 | on release_roles(:all) do |
191 | releases = capture(:ls, '-xr', releases_path).split | |
192 | releases = capture(:ls, '-xt', releases_path).split | |
192 | 193 | if releases.count < 2 |
193 | 194 | error t(:cannot_rollback) |
194 | 195 | exit 1 |
24 | 24 | fetch(:branch) |
25 | 25 | on release_roles :all do |
26 | 26 | with fetch(:git_environmental_variables) do |
27 | exit 1 unless strategy.check | |
27 | strategy.check | |
28 | 28 | end |
29 | 29 | end |
30 | 30 | end |
13 | 13 | |
14 | 14 | mkdir_p deploy_dir |
15 | 15 | |
16 | template = File.read(deploy_rb) | |
17 | file = config_dir.join('deploy.rb') | |
18 | File.open(file, 'w+') do |f| | |
19 | f.write(ERB.new(template).result(binding)) | |
20 | puts I18n.t(:written_file, scope: :capistrano, file: file) | |
21 | end | |
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")} } | |
22 | 18 | |
23 | template = File.read(stage_rb) | |
24 | envs.split(',').each do |stage| | |
25 | file = deploy_dir.join("#{stage}.rb") | |
26 | File.open(file, 'w+') do |f| | |
27 | f.write(ERB.new(template).result(binding)) | |
28 | puts I18n.t(:written_file, scope: :capistrano, file: file) | |
19 | entries.each do |entry| | |
20 | if File.exists?(entry[:file]) | |
21 | warn "[skip] #{entry[:file]} already exists" | |
22 | else | |
23 | File.open(entry[:file], 'w+') do |f| | |
24 | f.write(ERB.new(File.read(entry[:template])).result(binding)) | |
25 | puts I18n.t(:written_file, scope: :capistrano, file: entry[:file]) | |
26 | end | |
29 | 27 | end |
30 | 28 | end |
31 | 29 | |
32 | 30 | mkdir_p tasks_dir |
33 | 31 | |
34 | FileUtils.cp(capfile, 'Capfile') | |
32 | if File.exists?('Capfile') | |
33 | warn "[skip] Capfile already exists" | |
34 | else | |
35 | FileUtils.cp(capfile, 'Capfile') | |
36 | puts I18n.t(:written_file, scope: :capistrano, file: 'Capfile') | |
37 | end | |
35 | 38 | |
36 | 39 | |
37 | 40 | puts I18n.t :capified, scope: :capistrano |
0 | # Load DSL and Setup Up Stages | |
0 | # Load DSL and set up stages | |
1 | 1 | require 'capistrano/setup' |
2 | 2 | |
3 | # Includes default deployment tasks | |
3 | # Include default deployment tasks | |
4 | 4 | require 'capistrano/deploy' |
5 | 5 | |
6 | # Includes tasks from other gems included in your Gemfile | |
6 | # Include tasks from other gems included in your Gemfile | |
7 | 7 | # |
8 | 8 | # For documentation on these, see for example: |
9 | 9 | # |
12 | 12 | # https://github.com/capistrano/chruby |
13 | 13 | # https://github.com/capistrano/bundler |
14 | 14 | # https://github.com/capistrano/rails |
15 | # https://github.com/capistrano/passenger | |
15 | 16 | # |
16 | 17 | # require 'capistrano/rvm' |
17 | 18 | # require 'capistrano/rbenv' |
19 | 20 | # require 'capistrano/bundler' |
20 | 21 | # require 'capistrano/rails/assets' |
21 | 22 | # require 'capistrano/rails/migrations' |
23 | # require 'capistrano/passenger' | |
22 | 24 | |
23 | # Loads custom tasks from `lib/capistrano/tasks' if you have any defined. | |
25 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined | |
24 | 26 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } |
0 | # config valid only for Capistrano 3.1 | |
0 | # config valid only for current version of Capistrano | |
1 | 1 | lock '<%= Capistrano::VERSION %>' |
2 | 2 | |
3 | 3 | set :application, 'my_app_name' |
4 | 4 | set :repo_url, 'git@example.com:me/my_repo.git' |
5 | 5 | |
6 | 6 | # Default branch is :master |
7 | # ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call | |
7 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp | |
8 | 8 | |
9 | # Default deploy_to directory is /var/www/my_app | |
10 | # set :deploy_to, '/var/www/my_app' | |
9 | # Default deploy_to directory is /var/www/my_app_name | |
10 | # set :deploy_to, '/var/www/my_app_name' | |
11 | 11 | |
12 | 12 | # Default value for :scm is :git |
13 | 13 | # set :scm, :git |
22 | 22 | # set :pty, true |
23 | 23 | |
24 | 24 | # Default value for :linked_files is [] |
25 | # set :linked_files, %w{config/database.yml} | |
25 | # set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml') | |
26 | 26 | |
27 | 27 | # Default value for linked_dirs is [] |
28 | # set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system} | |
28 | # set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system') | |
29 | 29 | |
30 | 30 | # Default value for default_env is {} |
31 | 31 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } |
34 | 34 | # set :keep_releases, 5 |
35 | 35 | |
36 | 36 | namespace :deploy do |
37 | ||
38 | desc 'Restart application' | |
39 | task :restart do | |
40 | on roles(:app), in: :sequence, wait: 5 do | |
41 | # Your restart mechanism here, for example: | |
42 | # execute :touch, release_path.join('tmp/restart.txt') | |
43 | end | |
44 | end | |
45 | ||
46 | after :publishing, :restart | |
47 | 37 | |
48 | 38 | after :restart, :clear_cache do |
49 | 39 | on roles(:web), in: :groups, limit: 3, wait: 10 do |
0 | # Simple Role Syntax | |
1 | # ================== | |
2 | # Supports bulk-adding hosts to roles, the primary server in each group | |
3 | # is considered to be the first unless any hosts have the primary | |
4 | # property set. Don't declare `role :all`, it's a meta role. | |
0 | # server-based syntax | |
1 | # ====================== | |
2 | # Defines a single server with a list of roles and multiple properties. | |
3 | # You can define all roles on a single server, or split them: | |
5 | 4 | |
6 | role :app, %w{deploy@example.com} | |
7 | role :web, %w{deploy@example.com} | |
8 | role :db, %w{deploy@example.com} | |
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} | |
9 | 8 | |
10 | 9 | |
11 | # Extended Server Syntax | |
12 | # ====================== | |
13 | # This can be used to drop a more detailed server definition into the | |
14 | # server list. The second argument is a, or duck-types, Hash and is | |
15 | # used to set extended properties on the server. | |
16 | 10 | |
17 | server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value | |
11 | # role-based syntax | |
12 | # ================== | |
13 | ||
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 | |
16 | # property set. Specify the username and a domain or IP for the server. | |
17 | # Don't use `:all`, it's a meta role. | |
18 | ||
19 | # role :app, %w{deploy@example.com}, my_property: :my_value | |
20 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value | |
21 | # role :db, %w{deploy@example.com} | |
22 | ||
23 | ||
24 | ||
25 | # Configuration | |
26 | # ============= | |
27 | # You can set any configuration variable like in config/deploy.rb | |
28 | # These variables are then only loaded and set in this stage. | |
29 | # For available Capistrano configuration variables see the documentation page. | |
30 | # http://capistranorb.com/documentation/getting-started/configuration/ | |
31 | # Feel free to add new variables to customise your setup. | |
32 | ||
18 | 33 | |
19 | 34 | |
20 | 35 | # Custom SSH Options |
21 | 36 | # ================== |
22 | 37 | # You may pass any option but keep in mind that net/ssh understands a |
23 | # limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start). | |
38 | # limited set of options, consult the Net::SSH documentation. | |
39 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start | |
24 | 40 | # |
25 | 41 | # Global options |
26 | 42 | # -------------- |
30 | 46 | # auth_methods: %w(password) |
31 | 47 | # } |
32 | 48 | # |
33 | # And/or per server (overrides global) | |
49 | # The server-based syntax can be used to override options: | |
34 | 50 | # ------------------------------------ |
35 | 51 | # server 'example.com', |
36 | 52 | # user: 'user_name', |
0 | require 'rake/file_creation_task' | |
1 | ||
2 | module Capistrano | |
3 | class UploadTask < Rake::FileCreationTask | |
4 | def needed? | |
5 | true # always needed because we can't check remote hosts | |
6 | end | |
7 | end | |
8 | end |
0 | 0 | --- !ruby/object:Gem::Specification |
1 | 1 | name: capistrano |
2 | 2 | version: !ruby/object:Gem::Version |
3 | version: 3.2.1 | |
3 | version: 3.4.0 | |
4 | 4 | platform: ruby |
5 | 5 | authors: |
6 | 6 | - Tom Clements |
8 | 8 | autorequire: |
9 | 9 | bindir: bin |
10 | 10 | cert_chain: [] |
11 | date: 2014-04-22 00:00:00.000000000 Z | |
11 | date: 2015-03-02 00:00:00.000000000 Z | |
12 | 12 | dependencies: |
13 | 13 | - !ruby/object:Gem::Dependency |
14 | 14 | name: sshkit |
15 | 15 | requirement: !ruby/object:Gem::Requirement |
16 | 16 | requirements: |
17 | - - ~> | |
17 | - - "~>" | |
18 | 18 | - !ruby/object:Gem::Version |
19 | 19 | version: '1.3' |
20 | 20 | type: :runtime |
21 | 21 | prerelease: false |
22 | 22 | version_requirements: !ruby/object:Gem::Requirement |
23 | 23 | requirements: |
24 | - - ~> | |
24 | - - "~>" | |
25 | 25 | - !ruby/object:Gem::Version |
26 | 26 | version: '1.3' |
27 | 27 | - !ruby/object:Gem::Dependency |
28 | 28 | name: rake |
29 | 29 | requirement: !ruby/object:Gem::Requirement |
30 | 30 | requirements: |
31 | - - '>=' | |
31 | - - ">=" | |
32 | 32 | - !ruby/object:Gem::Version |
33 | 33 | version: 10.0.0 |
34 | 34 | type: :runtime |
35 | 35 | prerelease: false |
36 | 36 | version_requirements: !ruby/object:Gem::Requirement |
37 | 37 | requirements: |
38 | - - '>=' | |
38 | - - ">=" | |
39 | 39 | - !ruby/object:Gem::Version |
40 | 40 | version: 10.0.0 |
41 | 41 | - !ruby/object:Gem::Dependency |
42 | 42 | name: i18n |
43 | 43 | requirement: !ruby/object:Gem::Requirement |
44 | 44 | requirements: |
45 | - - '>=' | |
45 | - - ">=" | |
46 | 46 | - !ruby/object:Gem::Version |
47 | 47 | version: '0' |
48 | 48 | type: :runtime |
49 | 49 | prerelease: false |
50 | 50 | version_requirements: !ruby/object:Gem::Requirement |
51 | 51 | requirements: |
52 | - - '>=' | |
52 | - - ">=" | |
53 | 53 | - !ruby/object:Gem::Version |
54 | 54 | version: '0' |
55 | 55 | - !ruby/object:Gem::Dependency |
56 | 56 | name: rspec |
57 | 57 | requirement: !ruby/object:Gem::Requirement |
58 | 58 | requirements: |
59 | - - '>=' | |
59 | - - ">=" | |
60 | 60 | - !ruby/object:Gem::Version |
61 | 61 | version: '0' |
62 | 62 | type: :development |
63 | 63 | prerelease: false |
64 | 64 | version_requirements: !ruby/object:Gem::Requirement |
65 | 65 | requirements: |
66 | - - '>=' | |
66 | - - ">=" | |
67 | 67 | - !ruby/object:Gem::Version |
68 | 68 | version: '0' |
69 | 69 | - !ruby/object:Gem::Dependency |
70 | 70 | name: mocha |
71 | 71 | requirement: !ruby/object:Gem::Requirement |
72 | 72 | requirements: |
73 | - - '>=' | |
73 | - - ">=" | |
74 | 74 | - !ruby/object:Gem::Version |
75 | 75 | version: '0' |
76 | 76 | type: :development |
77 | 77 | prerelease: false |
78 | 78 | version_requirements: !ruby/object:Gem::Requirement |
79 | 79 | requirements: |
80 | - - '>=' | |
80 | - - ">=" | |
81 | 81 | - !ruby/object:Gem::Version |
82 | 82 | version: '0' |
83 | 83 | description: Capistrano is a utility and framework for executing commands in parallel |
91 | 91 | extensions: [] |
92 | 92 | extra_rdoc_files: [] |
93 | 93 | files: |
94 | - .gitignore | |
95 | - .travis.yml | |
94 | - ".gitignore" | |
95 | - ".travis.yml" | |
96 | 96 | - CHANGELOG.md |
97 | 97 | - CONTRIBUTING.md |
98 | 98 | - Gemfile |
107 | 107 | - features/deploy_failure.feature |
108 | 108 | - features/installation.feature |
109 | 109 | - features/remote_file_task.feature |
110 | - features/sshconnect.feature | |
110 | 111 | - features/step_definitions/assertions.rb |
111 | 112 | - features/step_definitions/cap_commands.rb |
112 | 113 | - features/step_definitions/setup.rb |
113 | 114 | - features/support/env.rb |
114 | 115 | - features/support/remote_command_helpers.rb |
116 | - features/support/vagrant_helpers.rb | |
115 | 117 | - lib/Capfile |
116 | 118 | - lib/capistrano.rb |
117 | 119 | - lib/capistrano/all.rb |
118 | 120 | - lib/capistrano/application.rb |
119 | 121 | - lib/capistrano/configuration.rb |
122 | - lib/capistrano/configuration/filter.rb | |
120 | 123 | - lib/capistrano/configuration/question.rb |
121 | 124 | - lib/capistrano/configuration/server.rb |
122 | 125 | - lib/capistrano/configuration/servers.rb |
123 | - lib/capistrano/configuration/servers/host_filter.rb | |
124 | - lib/capistrano/configuration/servers/role_filter.rb | |
125 | 126 | - lib/capistrano/console.rb |
126 | 127 | - lib/capistrano/defaults.rb |
127 | 128 | - lib/capistrano/deploy.rb |
149 | 150 | - lib/capistrano/templates/Capfile |
150 | 151 | - lib/capistrano/templates/deploy.rb.erb |
151 | 152 | - lib/capistrano/templates/stage.rb.erb |
153 | - lib/capistrano/upload_task.rb | |
152 | 154 | - lib/capistrano/version.rb |
153 | 155 | - lib/capistrano/version_validator.rb |
154 | 156 | - spec/integration/dsl_spec.rb |
155 | 157 | - spec/integration_spec_helper.rb |
156 | 158 | - spec/lib/capistrano/application_spec.rb |
159 | - spec/lib/capistrano/configuration/filter_spec.rb | |
157 | 160 | - spec/lib/capistrano/configuration/question_spec.rb |
158 | 161 | - spec/lib/capistrano/configuration/server_spec.rb |
159 | - spec/lib/capistrano/configuration/servers/host_filter_spec.rb | |
160 | - spec/lib/capistrano/configuration/servers/role_filter_spec.rb | |
161 | 162 | - spec/lib/capistrano/configuration/servers_spec.rb |
162 | 163 | - spec/lib/capistrano/configuration_spec.rb |
163 | 164 | - spec/lib/capistrano/dsl/paths_spec.rb |
165 | - spec/lib/capistrano/dsl/task_enhancements_spec.rb | |
164 | 166 | - spec/lib/capistrano/dsl_spec.rb |
165 | 167 | - spec/lib/capistrano/git_spec.rb |
166 | 168 | - spec/lib/capistrano/hg_spec.rb |
167 | 169 | - spec/lib/capistrano/scm_spec.rb |
168 | 170 | - spec/lib/capistrano/svn_spec.rb |
171 | - spec/lib/capistrano/upload_task_spec.rb | |
169 | 172 | - spec/lib/capistrano/version_validator_spec.rb |
170 | 173 | - spec/lib/capistrano_spec.rb |
171 | 174 | - spec/spec_helper.rb |
175 | 178 | - spec/support/tasks/database.rake |
176 | 179 | - spec/support/tasks/fail.rake |
177 | 180 | - spec/support/tasks/failed.rake |
181 | - spec/support/tasks/root.rake | |
178 | 182 | - spec/support/test_app.rb |
179 | 183 | homepage: http://capistranorb.com/ |
180 | 184 | licenses: |
181 | 185 | - MIT |
182 | 186 | metadata: {} |
183 | 187 | post_install_message: | |
184 | Capistrano 3.1 has some breaking changes, like `deploy:restart` callback should be added manually to your deploy.rb. Please, check the CHANGELOG: http://goo.gl/SxB0lr | |
188 | Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr | |
185 | 189 | |
186 | 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. | |
187 | 193 | rdoc_options: [] |
188 | 194 | require_paths: |
189 | 195 | - lib |
190 | 196 | required_ruby_version: !ruby/object:Gem::Requirement |
191 | 197 | requirements: |
192 | - - '>=' | |
198 | - - ">=" | |
193 | 199 | - !ruby/object:Gem::Version |
194 | version: '0' | |
200 | version: 1.9.3 | |
195 | 201 | required_rubygems_version: !ruby/object:Gem::Requirement |
196 | 202 | requirements: |
197 | - - '>=' | |
203 | - - ">=" | |
198 | 204 | - !ruby/object:Gem::Version |
199 | 205 | version: '0' |
200 | 206 | requirements: [] |
201 | 207 | rubyforge_project: |
202 | rubygems_version: 2.0.3 | |
208 | rubygems_version: 2.4.3 | |
203 | 209 | signing_key: |
204 | 210 | specification_version: 4 |
205 | 211 | summary: Capistrano - Welcome to easy deployment with Ruby over SSH |
209 | 215 | - features/deploy_failure.feature |
210 | 216 | - features/installation.feature |
211 | 217 | - features/remote_file_task.feature |
218 | - features/sshconnect.feature | |
212 | 219 | - features/step_definitions/assertions.rb |
213 | 220 | - features/step_definitions/cap_commands.rb |
214 | 221 | - features/step_definitions/setup.rb |
215 | 222 | - features/support/env.rb |
216 | 223 | - features/support/remote_command_helpers.rb |
224 | - features/support/vagrant_helpers.rb | |
217 | 225 | - spec/integration/dsl_spec.rb |
218 | 226 | - spec/integration_spec_helper.rb |
219 | 227 | - spec/lib/capistrano/application_spec.rb |
228 | - spec/lib/capistrano/configuration/filter_spec.rb | |
220 | 229 | - spec/lib/capistrano/configuration/question_spec.rb |
221 | 230 | - spec/lib/capistrano/configuration/server_spec.rb |
222 | - spec/lib/capistrano/configuration/servers/host_filter_spec.rb | |
223 | - spec/lib/capistrano/configuration/servers/role_filter_spec.rb | |
224 | 231 | - spec/lib/capistrano/configuration/servers_spec.rb |
225 | 232 | - spec/lib/capistrano/configuration_spec.rb |
226 | 233 | - spec/lib/capistrano/dsl/paths_spec.rb |
234 | - spec/lib/capistrano/dsl/task_enhancements_spec.rb | |
227 | 235 | - spec/lib/capistrano/dsl_spec.rb |
228 | 236 | - spec/lib/capistrano/git_spec.rb |
229 | 237 | - spec/lib/capistrano/hg_spec.rb |
230 | 238 | - spec/lib/capistrano/scm_spec.rb |
231 | 239 | - spec/lib/capistrano/svn_spec.rb |
240 | - spec/lib/capistrano/upload_task_spec.rb | |
232 | 241 | - spec/lib/capistrano/version_validator_spec.rb |
233 | 242 | - spec/lib/capistrano_spec.rb |
234 | 243 | - spec/spec_helper.rb |
238 | 247 | - spec/support/tasks/database.rake |
239 | 248 | - spec/support/tasks/fail.rake |
240 | 249 | - spec/support/tasks/failed.rake |
250 | - spec/support/tasks/root.rake | |
241 | 251 | - spec/support/test_app.rb |
242 | has_rdoc: |
14 | 14 | dsl.server 'example2.com', roles: %w{web} |
15 | 15 | dsl.server 'example3.com', roles: %w{app web}, active: true |
16 | 16 | dsl.server 'example4.com', roles: %w{app}, primary: true |
17 | dsl.server 'example5.com', roles: %w{db}, no_release: true | |
17 | dsl.server 'example5.com', roles: %w{db}, no_release: true, active:true | |
18 | 18 | end |
19 | 19 | |
20 | 20 | describe 'fetching all servers' do |
35 | 35 | end |
36 | 36 | end |
37 | 37 | |
38 | context 'with filter options' do | |
38 | context 'with property filter options' do | |
39 | 39 | subject { dsl.release_roles(:all, filter: :active) } |
40 | 40 | |
41 | it 'returns all release servers that match the filter' do | |
41 | it 'returns all release servers that match the property filter' do | |
42 | 42 | expect(subject.map(&:hostname)).to eq %w{example1.com example3.com} |
43 | 43 | end |
44 | 44 | end |
91 | 91 | end |
92 | 92 | end |
93 | 93 | |
94 | context 'when the attribute `primary` is explicity set' do | |
94 | context 'when the attribute `primary` is explicitly set' do | |
95 | 95 | subject { dsl.primary(:app) } |
96 | 96 | it 'returns the servers' do |
97 | 97 | expect(subject.hostname).to eq 'example4.com' |
98 | 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 | |
127 | subject { dsl.roles(:all) } | |
128 | it 'is ignored' do | |
129 | dsl.set :filter, { host: /1/ } | |
130 | expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com}) | |
99 | 131 | end |
100 | 132 | end |
101 | 133 | |
208 | 240 | end |
209 | 241 | |
210 | 242 | describe 'fetching all servers' do |
211 | subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" } } | |
212 | ||
213 | it 'creates a server instance for each unique user@host:port combination' do | |
214 | expect(subject).to eq %w{db@example1.com:1234 root@example1.com:1234 @example1.com:5678 deployer@example1.com:1234} | |
243 | it 'creates one server per hostname, ignoring user and port combinations' do | |
244 | expect(dsl.roles(:all).size).to eq(1) | |
215 | 245 | end |
216 | 246 | end |
217 | 247 | |
218 | 248 | describe 'fetching servers for a role' do |
219 | 249 | it 'roles defined using the `server` syntax are included' do |
220 | expect(dsl.roles(:web)).to have(2).items | |
250 | 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") | |
221 | 253 | end |
222 | 254 | |
223 | 255 | it 'roles defined using the `role` syntax are included' do |
224 | expect(dsl.roles(:app)).to have(2).items | |
225 | end | |
226 | end | |
227 | ||
256 | 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 | |
265 | subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first } | |
266 | ||
267 | describe "using the :user property" do | |
268 | it "takes precedence over in the host string" do | |
269 | dsl.server 'db@example1.com:1234', roles: %w{db}, active: true, user: 'brian' | |
270 | expect(subject).to eq("brian@example1.com:1234") | |
271 | end | |
272 | end | |
273 | ||
274 | describe "using the :port property" do | |
275 | it "takes precedence over in the host string" do | |
276 | dsl.server 'db@example1.com:9090', roles: %w{db}, active: true, port: 1234 | |
277 | expect(subject).to eq("db@example1.com:1234") | |
278 | end | |
279 | end | |
228 | 280 | end |
229 | 281 | |
230 | 282 | end |
316 | 368 | context 'variable is an non-empty array' do |
317 | 369 | let(:linked_files) { %w{1} } |
318 | 370 | |
319 | it { should be_true } | |
371 | it { expect(subject).to be_truthy } | |
320 | 372 | end |
321 | 373 | |
322 | 374 | context 'variable is an empty array' do |
323 | 375 | let(:linked_files) { [] } |
324 | it { should be_false } | |
376 | it { expect(subject).to be_falsey } | |
325 | 377 | end |
326 | 378 | |
327 | 379 | context 'variable exists, is not an array' do |
328 | 380 | let(:linked_files) { stub } |
329 | it { should be_true } | |
381 | it { expect(subject).to be_truthy } | |
330 | 382 | end |
331 | 383 | |
332 | 384 | context 'variable is nil' do |
333 | 385 | let(:linked_files) { nil } |
334 | it { should be_false } | |
386 | it { expect(subject).to be_falsey } | |
335 | 387 | end |
336 | 388 | end |
337 | 389 | |
367 | 419 | end |
368 | 420 | |
369 | 421 | it 'sets the backend pty' do |
370 | expect(backend.pty).to be_true | |
422 | expect(backend.pty).to be_truthy | |
371 | 423 | end |
372 | 424 | |
373 | 425 | it 'sets the backend connection timeout' do |
382 | 434 | |
383 | 435 | end |
384 | 436 | |
385 | describe 'release path' do | |
386 | ||
387 | before do | |
388 | dsl.set(:deploy_to, '/var/www') | |
389 | end | |
390 | ||
391 | describe 'fetching release path' do | |
392 | subject { dsl.release_path } | |
393 | ||
394 | context 'where no release path has been set' do | |
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 | |
395 | 446 | before do |
396 | dsl.delete(:release_path) | |
397 | end | |
398 | ||
399 | it 'returns the `current_path` value' do | |
400 | expect(subject.to_s).to eq '/var/www/current' | |
401 | end | |
402 | end | |
403 | ||
404 | context 'where the release path has been set' 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 | |
405 | 456 | before do |
406 | dsl.set(:release_path, '/var/www/release_path') | |
407 | end | |
408 | ||
409 | it 'returns the set `release_path` value' do | |
410 | expect(subject.to_s).to eq '/var/www/release_path' | |
411 | end | |
412 | end | |
413 | end | |
414 | ||
415 | describe 'setting release path' do | |
416 | let(:now) { Time.parse("Oct 21 16:29:00 2015") } | |
417 | subject { dsl.release_path } | |
418 | ||
419 | context 'without a timestamp' do | |
420 | before do | |
421 | dsl.env.expects(:timestamp).returns(now) | |
422 | dsl.set_release_path | |
423 | end | |
424 | ||
425 | it 'returns the release path with the current env timestamp' do | |
426 | expect(subject.to_s).to eq '/var/www/releases/20151021162900' | |
427 | end | |
428 | end | |
429 | ||
430 | context 'with a timestamp' do | |
431 | before do | |
432 | dsl.set_release_path('timestamp') | |
433 | end | |
434 | ||
435 | it 'returns the release path with the timestamp' do | |
436 | expect(subject.to_s).to eq '/var/www/releases/timestamp' | |
437 | end | |
438 | end | |
439 | end | |
440 | ||
441 | describe 'setting deploy configuration path' do | |
442 | subject { dsl.deploy_config_path.to_s } | |
443 | ||
444 | context 'where no config path is set' do | |
445 | before do | |
446 | dsl.delete(:deploy_config_path) | |
447 | end | |
448 | ||
449 | it 'returns "config/deploy.rb"' do | |
450 | expect(subject).to eq 'config/deploy.rb' | |
451 | end | |
452 | end | |
453 | ||
454 | context 'where a custom path is set' do | |
455 | before do | |
456 | dsl.set(:deploy_config_path, 'my/custom/path.rb') | |
457 | end | |
458 | ||
459 | it 'returns the custom path' do | |
460 | expect(subject).to eq 'my/custom/path.rb' | |
461 | end | |
462 | end | |
463 | end | |
464 | ||
465 | describe 'setting stage configuration path' do | |
466 | subject { dsl.stage_config_path.to_s } | |
467 | ||
468 | context 'where no config path is set' do | |
469 | ||
470 | before do | |
471 | dsl.delete(:stage_config_path) | |
472 | end | |
473 | ||
474 | it 'returns "config/deploy"' do | |
475 | expect(subject).to eq 'config/deploy' | |
476 | end | |
477 | end | |
478 | ||
479 | context 'where a custom path is set' do | |
480 | before do | |
481 | dsl.set(:stage_config_path, 'my/custom/path') | |
482 | end | |
483 | ||
484 | it 'returns the custom path' do | |
485 | expect(subject).to eq 'my/custom/path' | |
486 | end | |
487 | end | |
488 | end | |
489 | end | |
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 | ||
525 | before do | |
526 | 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 | |
533 | 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 | |
538 | 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}) | |
546 | dsl.role_properties(:redis) do |host, role, props| | |
547 | recipient.doit(host, role, props) | |
548 | end | |
549 | end | |
550 | ||
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) | |
556 | dsl.role_properties(:redis, :app) do |host, role, props| | |
557 | recipient.doit(host, role, props) | |
558 | end | |
559 | end | |
560 | ||
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 }) | |
567 | dsl.role_properties(:redis, :web) do |host, role, props| | |
568 | recipient.doit(host, role, props) | |
569 | end | |
570 | end | |
571 | ||
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 }) | |
576 | dsl.role_properties(:redis, :web, select: :active) do |host, role, props| | |
577 | recipient.doit(host, role, props) | |
578 | end | |
579 | end | |
580 | end | |
581 | ||
490 | 582 | end |
5 | 5 | |
6 | 6 | it "provides a --format option which enables the choice of output formatting" |
7 | 7 | |
8 | it "identifies itself as cap and not rake" do | |
8 | let(:help_output) do | |
9 | 9 | out, _ = capture_io do |
10 | 10 | flags '--help', '-h' |
11 | 11 | end |
12 | out.lines.first.should match(/cap \[-f rakefile\]/) | |
12 | out | |
13 | end | |
14 | ||
15 | it "displays documentation URL as help banner" do | |
16 | expect(help_output.lines.first).to match(/capistranorb.com/) | |
17 | end | |
18 | ||
19 | %w(quiet silent verbose).each do |switch| | |
20 | it "doesn't include --#{switch} in help" do | |
21 | expect(help_output).not_to match(/--#{switch}/) | |
22 | end | |
13 | 23 | end |
14 | 24 | |
15 | 25 | it "overrides the rake method, but still prints the rake version" do |
16 | 26 | out, _ = capture_io do |
17 | 27 | flags '--version', '-V' |
18 | 28 | end |
19 | out.should match(/\bCapistrano Version\b/) | |
20 | out.should match(/\b#{Capistrano::VERSION}\b/) | |
21 | out.should match(/\bRake Version\b/) | |
22 | out.should match(/\b#{RAKEVERSION}\b/) | |
29 | expect(out).to match(/\bCapistrano Version\b/) | |
30 | expect(out).to match(/\b#{Capistrano::VERSION}\b/) | |
31 | expect(out).to match(/\bRake Version\b/) | |
32 | expect(out).to match(/\b#{RAKEVERSION}\b/) | |
33 | end | |
34 | ||
35 | 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' | |
38 | end | |
39 | sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend) | |
40 | expect(sshkit_backend).to eq(SSHKit::Backend::Printer) | |
23 | 41 | end |
24 | 42 | |
25 | 43 | def flags(*sets) |
0 | require 'spec_helper' | |
1 | ||
2 | module Capistrano | |
3 | class Configuration | |
4 | ||
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 | |
13 | it "won't create an invalid type of filter" do | |
14 | expect { | |
15 | f = Filter.new(:zarg) | |
16 | }.to raise_error RuntimeError | |
17 | end | |
18 | ||
19 | it 'creates an empty host filter' do | |
20 | expect(Filter.new(:host).filter(available)).to be_empty | |
21 | end | |
22 | ||
23 | it 'creates a null host filter' do | |
24 | expect(Filter.new(:host, :all).filter(available)).to eq(available) | |
25 | end | |
26 | ||
27 | it 'creates an empty role filter' do | |
28 | expect(Filter.new(:role).filter(available)).to be_empty | |
29 | end | |
30 | ||
31 | it 'creates a null role filter' do | |
32 | expect(Filter.new(:role, :all).filter(available)).to eq(available) | |
33 | end | |
34 | ||
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}) | |
61 | end | |
62 | end | |
63 | ||
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') | |
69 | 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}) | |
74 | 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}) | |
79 | 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}) | |
84 | 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}) | |
106 | end | |
107 | end | |
108 | end | |
109 | end | |
110 | end |
4 | 4 | |
5 | 5 | describe Question do |
6 | 6 | |
7 | let(:question) { Question.new(env, key, default) } | |
7 | let(:question) { Question.new(key, default, options) } | |
8 | let(:question_without_echo) { Question.new(key, default, echo: false) } | |
8 | 9 | let(:default) { :default } |
9 | 10 | let(:key) { :branch } |
10 | let(:env) { stub } | |
11 | let(:options) { nil } | |
11 | 12 | |
12 | 13 | describe '.new' do |
13 | it 'takes a key, default' do | |
14 | it 'takes a key, default, options' do | |
14 | 15 | question |
15 | 16 | end |
16 | 17 | end |
17 | 18 | |
18 | 19 | describe '#call' do |
19 | subject { question.call } | |
20 | ||
21 | 20 | context 'value is entered' do |
22 | 21 | let(:branch) { 'branch' } |
23 | 22 | |
24 | 23 | before do |
25 | 24 | $stdout.expects(:print).with('Please enter branch (default): ') |
26 | $stdin.expects(:gets).returns(branch) | |
27 | 25 | end |
28 | 26 | |
29 | it 'sets the value' do | |
30 | env.expects(:set).with(key, branch) | |
31 | question.call | |
27 | it 'returns the echoed value' do | |
28 | $stdin.expects(:gets).returns(branch) | |
29 | $stdin.expects(:noecho).never | |
30 | ||
31 | expect(question.call).to eq(branch) | |
32 | end | |
33 | ||
34 | it 'returns the value but does not echo it' do | |
35 | $stdin.expects(:noecho).returns(branch) | |
36 | $stdout.expects(:print).with("\n") | |
37 | ||
38 | expect(question_without_echo.call).to eq(branch) | |
32 | 39 | end |
33 | 40 | end |
34 | 41 | |
40 | 47 | $stdin.expects(:gets).returns('') |
41 | 48 | end |
42 | 49 | |
43 | it 'sets the default as the value' do | |
44 | env.expects(:set).with(key, branch) | |
45 | question.call | |
50 | ||
51 | it 'returns the default as the value' do | |
52 | expect(question.call).to eq(branch) | |
46 | 53 | end |
47 | ||
48 | 54 | end |
49 | 55 | end |
50 | 56 | end |
27 | 27 | end |
28 | 28 | |
29 | 29 | it 'adds the role' do |
30 | expect{subject}.to be_true | |
30 | expect(subject).to be_truthy | |
31 | 31 | end |
32 | 32 | end |
33 | 33 | |
34 | 34 | describe 'comparing identity' do |
35 | subject { server.matches? Server[hostname] } | |
35 | subject { server.hostname == Server[hostname].hostname } | |
36 | 36 | |
37 | 37 | context 'with the same user, hostname and port' do |
38 | 38 | let(:hostname) { 'root@hostname:1234' } |
39 | it { should be_true } | |
39 | it { expect(subject).to be_truthy } | |
40 | 40 | end |
41 | 41 | |
42 | 42 | context 'with a different user' do |
43 | 43 | let(:hostname) { 'deployer@hostname:1234' } |
44 | it { should be_false } | |
44 | it { expect(subject).to be_truthy } | |
45 | 45 | end |
46 | 46 | |
47 | 47 | context 'with a different port' do |
48 | 48 | let(:hostname) { 'root@hostname:5678' } |
49 | it { should be_false } | |
49 | it { expect(subject).to be_truthy } | |
50 | 50 | end |
51 | 51 | |
52 | 52 | context 'with a different hostname' do |
53 | 53 | let(:hostname) { 'root@otherserver:1234' } |
54 | it { should be_false } | |
54 | it { expect(subject).to be_falsey } | |
55 | 55 | end |
56 | 56 | end |
57 | 57 | |
68 | 68 | |
69 | 69 | context 'server is not primary' do |
70 | 70 | it 'is falesy' do |
71 | expect(subject).to be_false | |
71 | expect(subject).to be_falsey | |
72 | 72 | end |
73 | 73 | end |
74 | 74 | end |
92 | 92 | |
93 | 93 | it 'sets the user' do |
94 | 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' | |
95 | 99 | end |
96 | 100 | end |
97 | 101 | |
148 | 152 | end |
149 | 153 | |
150 | 154 | context 'options are empty' do |
151 | it { should be_true } | |
155 | it { expect(subject).to be_truthy } | |
152 | 156 | end |
153 | 157 | |
154 | 158 | context 'value is a symbol' do |
156 | 160 | |
157 | 161 | context 'with :filter' do |
158 | 162 | let(:options) { { filter: :active }} |
159 | it { should be_true } | |
163 | it { expect(subject).to be_truthy } | |
160 | 164 | end |
161 | 165 | |
162 | 166 | context 'with :select' do |
163 | 167 | let(:options) { { select: :active }} |
164 | it { should be_true } | |
168 | it { expect(subject).to be_truthy } | |
165 | 169 | end |
166 | 170 | |
167 | 171 | context 'with :exclude' do |
168 | 172 | let(:options) { { exclude: :active }} |
169 | it { should be_false } | |
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 } | |
170 | 186 | end |
171 | 187 | end |
172 | 188 | |
173 | 189 | context 'value does not match server properly' do |
174 | 190 | context 'with :filter' do |
175 | 191 | let(:options) { { filter: :inactive }} |
176 | it { should be_false } | |
192 | it { expect(subject).to be_falsey } | |
177 | 193 | end |
178 | 194 | |
179 | 195 | context 'with :select' do |
180 | 196 | let(:options) { { select: :inactive }} |
181 | it { should be_false } | |
197 | it { expect(subject).to be_falsey } | |
182 | 198 | end |
183 | 199 | |
184 | 200 | context 'with :exclude' do |
185 | 201 | let(:options) { { exclude: :inactive }} |
186 | it { should be_true } | |
187 | end | |
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 }} | |
210 | it { expect(subject).to be_truthy } | |
211 | end | |
212 | ||
213 | context 'with :active false' do | |
214 | let(:options) { { active: false }} | |
215 | it { expect(subject).to be_falsey } | |
188 | 216 | end |
189 | 217 | end |
190 | 218 | |
193 | 221 | |
194 | 222 | context 'with :filter' do |
195 | 223 | let(:options) { { filter: ->(s) { s.properties.active } } } |
196 | it { should be_true } | |
224 | it { expect(subject).to be_truthy } | |
197 | 225 | end |
198 | 226 | |
199 | 227 | context 'with :select' do |
200 | 228 | let(:options) { { select: ->(s) { s.properties.active } } } |
201 | it { should be_true } | |
229 | it { expect(subject).to be_truthy } | |
202 | 230 | end |
203 | 231 | |
204 | 232 | context 'with :exclude' do |
205 | 233 | let(:options) { { exclude: ->(s) { s.properties.active } } } |
206 | it { should be_false } | |
234 | it { expect(subject).to be_falsey } | |
207 | 235 | end |
208 | 236 | |
209 | 237 | end |
211 | 239 | context 'value does not match server properly' do |
212 | 240 | context 'with :filter' do |
213 | 241 | let(:options) { { filter: ->(s) { s.properties.inactive } } } |
214 | it { should be_false } | |
242 | it { expect(subject).to be_falsey } | |
215 | 243 | end |
216 | 244 | |
217 | 245 | context 'with :select' do |
218 | 246 | let(:options) { { select: ->(s) { s.properties.inactive } } } |
219 | it { should be_false } | |
247 | it { expect(subject).to be_falsey } | |
220 | 248 | end |
221 | 249 | |
222 | 250 | context 'with :exclude' do |
223 | 251 | let(:options) { { exclude: ->(s) { s.properties.inactive } } } |
224 | it { should be_true } | |
252 | it { expect(subject).to be_truthy } | |
225 | 253 | end |
226 | 254 | |
227 | 255 | end |
260 | 288 | it 'contains correct user' do |
261 | 289 | expect(server.netssh_options[:user]).to eq 'another_user' |
262 | 290 | end |
291 | it 'does not affect server user in host' do | |
292 | expect(server.user).to eq 'user_name' | |
293 | end | |
263 | 294 | it 'contains keys' do |
264 | 295 | expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa) |
265 | 296 | end |
0 | require 'spec_helper' | |
1 | ||
2 | module Capistrano | |
3 | class Configuration | |
4 | class Servers | |
5 | ||
6 | describe HostFilter do | |
7 | let(:host_filter) { HostFilter.new(available) } | |
8 | let(:available) { [ Server.new('server1'), Server.new('server2'), Server.new('server3') ] } | |
9 | ||
10 | describe '#new' do | |
11 | it 'takes one array of hostnames' do | |
12 | expect(host_filter) | |
13 | end | |
14 | end | |
15 | ||
16 | describe '.for' do | |
17 | ||
18 | subject { HostFilter.for(available) } | |
19 | ||
20 | context 'without env vars' do | |
21 | it 'returns all available hosts' do | |
22 | expect(subject).to eq available | |
23 | end | |
24 | end | |
25 | ||
26 | context 'with ENV vars' do | |
27 | before do | |
28 | ENV.stubs(:[]).with('HOSTS').returns('server1,server2') | |
29 | end | |
30 | ||
31 | it 'returns all required hosts defined in HOSTS' do | |
32 | expect(subject).to eq [Server.new('server1'), Server.new('server2')] | |
33 | end | |
34 | end | |
35 | ||
36 | context 'with configuration filters' do | |
37 | before do | |
38 | Configuration.env.set(:filter, hosts: %w{server1 server2}) | |
39 | end | |
40 | ||
41 | it 'returns all required hosts defined in the filter' do | |
42 | expect(subject).to eq [Server.new('server1'), Server.new('server2')] | |
43 | end | |
44 | ||
45 | after do | |
46 | Configuration.env.delete(:filter) | |
47 | end | |
48 | end | |
49 | ||
50 | context 'with a single configuration filter' do | |
51 | before do | |
52 | Configuration.env.set(:filter, hosts: 'server3') | |
53 | end | |
54 | ||
55 | it 'returns all required hosts defined in the filter' do | |
56 | expect(subject).to eq [Server.new('server3')] | |
57 | end | |
58 | ||
59 | after do | |
60 | Configuration.env.delete(:filter) | |
61 | end | |
62 | end | |
63 | ||
64 | context 'with configuration filters and ENV vars' do | |
65 | before do | |
66 | Configuration.env.set(:filter, hosts: 'server1') | |
67 | ENV.stubs(:[]).with('HOSTS').returns('server3') | |
68 | end | |
69 | ||
70 | it 'returns all required hosts defined in the filter' do | |
71 | expect(subject).to eq [Server.new('server1'), Server.new('server3')] | |
72 | end | |
73 | ||
74 | after do | |
75 | Configuration.env.delete(:filter) | |
76 | end | |
77 | end | |
78 | end | |
79 | end | |
80 | ||
81 | end | |
82 | end | |
83 | end |
0 | require 'spec_helper' | |
1 | ||
2 | module Capistrano | |
3 | class Configuration | |
4 | class Servers | |
5 | ||
6 | describe RoleFilter do | |
7 | let(:role_filter) { RoleFilter.new(required, available) } | |
8 | let(:required) { [] } | |
9 | let(:available) { [:web, :app, :db] } | |
10 | ||
11 | describe '#new' do | |
12 | it 'takes two arrays of role names' do | |
13 | expect(role_filter) | |
14 | end | |
15 | end | |
16 | ||
17 | describe '.for' do | |
18 | ||
19 | subject { RoleFilter.for(required, available) } | |
20 | ||
21 | context 'without env vars' do | |
22 | context ':all required' do | |
23 | let(:required) { [:all] } | |
24 | ||
25 | it 'returns all available names' do | |
26 | expect(subject).to eq available | |
27 | end | |
28 | end | |
29 | ||
30 | context 'role names required' do | |
31 | let(:required) { [:web, :app] } | |
32 | it 'returns all required names' do | |
33 | expect(subject).to eq required | |
34 | end | |
35 | end | |
36 | end | |
37 | ||
38 | context 'with ENV vars' do | |
39 | before do | |
40 | ENV.stubs(:[]).with('ROLES').returns('app,web') | |
41 | end | |
42 | ||
43 | context ':all required' do | |
44 | let(:required) { [:all] } | |
45 | ||
46 | it 'returns available names defined in ROLES' do | |
47 | expect(subject).to eq [:app, :web] | |
48 | end | |
49 | end | |
50 | ||
51 | context 'role names required' do | |
52 | let(:required) { [:web, :db] } | |
53 | it 'returns all required names defined in ROLES' do | |
54 | expect(subject).to eq [:web] | |
55 | end | |
56 | end | |
57 | end | |
58 | ||
59 | context 'with configuration filters' do | |
60 | before do | |
61 | Configuration.env.set(:filter, roles: %w{app web}) | |
62 | end | |
63 | ||
64 | context ':all required' do | |
65 | let(:required) { [:all] } | |
66 | ||
67 | it 'returns available names defined in the filter' do | |
68 | expect(subject).to eq [:app, :web] | |
69 | end | |
70 | end | |
71 | ||
72 | context 'role names required' do | |
73 | let(:required) { [:web, :db] } | |
74 | it 'returns all required names defined in the filter' do | |
75 | expect(subject).to eq [:web] | |
76 | end | |
77 | end | |
78 | ||
79 | after do | |
80 | Configuration.env.delete(:filter) | |
81 | end | |
82 | end | |
83 | ||
84 | context 'with a single configuration filter' do | |
85 | before do | |
86 | Configuration.env.set(:filter, roles: 'web') | |
87 | end | |
88 | ||
89 | context ':all required' do | |
90 | let(:required) { [:all] } | |
91 | ||
92 | it 'returns available names defined in the filter' do | |
93 | expect(subject).to eq [:web] | |
94 | end | |
95 | end | |
96 | ||
97 | context 'role names required' do | |
98 | let(:required) { [:web, :db] } | |
99 | it 'returns all required names defined in the filter' do | |
100 | expect(subject).to eq [:web] | |
101 | end | |
102 | end | |
103 | ||
104 | after do | |
105 | Configuration.env.delete(:filter) | |
106 | end | |
107 | end | |
108 | ||
109 | context 'with configuration filters and ENV vars' do | |
110 | before do | |
111 | Configuration.env.set(:filter, roles: %w{app}) | |
112 | ENV.stubs(:[]).with('ROLES').returns('web') | |
113 | end | |
114 | ||
115 | context ':all required' do | |
116 | let(:required) { [:all] } | |
117 | ||
118 | it 'returns available names defined in the filter' do | |
119 | expect(subject).to eq [:web, :app] | |
120 | end | |
121 | end | |
122 | ||
123 | context 'role names required' do | |
124 | let(:required) { [:web, :db] } | |
125 | it 'returns all required names defined in the filter' do | |
126 | expect(subject).to eq [:web] | |
127 | end | |
128 | end | |
129 | ||
130 | after do | |
131 | Configuration.env.delete(:filter) | |
132 | end | |
133 | end | |
134 | end | |
135 | end | |
136 | ||
137 | end | |
138 | end | |
139 | end |
17 | 17 | expect(servers.count).to eq 1 |
18 | 18 | end |
19 | 19 | |
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') | |
23 | expect(servers.count).to eq 1 | |
24 | end | |
25 | ||
20 | 26 | it 'accepts instances of server objects' do |
21 | 27 | servers.add_role(:app, [Capistrano::Configuration::Server.new('example.net'), 'example.com']) |
22 | 28 | expect(servers.roles_for([:app]).length).to eq 2 |
25 | 31 | it 'accepts non-enumerable types' do |
26 | 32 | servers.add_role(:app, '1') |
27 | 33 | expect(servers.roles_for([:app]).count).to eq 1 |
34 | end | |
35 | ||
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 }) | |
39 | expect(servers.count).to eq(2) | |
40 | 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 }) | |
28 | 43 | end |
29 | 44 | |
30 | 45 | end |
58 | 73 | end |
59 | 74 | |
60 | 75 | describe 'finding the primary server' do |
76 | after do | |
77 | Configuration.reset! | |
78 | end | |
61 | 79 | it 'takes the first server if none have the primary property' do |
62 | 80 | servers.add_role(:app, %w{1 2}) |
63 | servers.fetch_primary(:app).hostname.should == '1' | |
81 | expect(servers.fetch_primary(:app).hostname).to eq('1') | |
64 | 82 | end |
65 | 83 | |
66 | 84 | it 'takes the first server with the primary have the primary flag' do |
67 | 85 | servers.add_role(:app, %w{1 2}) |
68 | 86 | servers.add_host('2', primary: true) |
69 | servers.fetch_primary(:app).hostname.should == '2' | |
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') | |
70 | 95 | end |
71 | 96 | end |
72 | 97 | |
114 | 139 | servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root', port: 34) |
115 | 140 | servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 34) |
116 | 141 | servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 56) |
117 | servers.should have(8).items | |
118 | end | |
142 | expect(servers.count).to eq(1) | |
143 | end | |
144 | ||
145 | 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') | |
149 | 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') | |
155 | 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) | |
163 | expect(servers.count).to eq(1) | |
164 | expect(servers.roles_for([:all]).first.properties.test).to eq :volatile | |
165 | end | |
166 | ||
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 }) | |
170 | 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]) | |
177 | 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]) | |
184 | 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 | ||
119 | 207 | end |
120 | 208 | |
121 | 209 | describe 'selecting roles' do |
180 | 268 | |
181 | 269 | end |
182 | 270 | |
183 | describe 'filtering roles' do | |
184 | ||
185 | before do | |
186 | ENV.stubs(:[]).with('ROLES').returns('web,db') | |
187 | ENV.stubs(:[]).with('HOSTS').returns(nil) | |
271 | describe 'filtering roles internally' do | |
272 | ||
273 | before do | |
188 | 274 | servers.add_host('1', roles: :app, active: true) |
189 | 275 | servers.add_host('2', roles: :app) |
190 | 276 | servers.add_host('3', roles: :web) |
194 | 280 | |
195 | 281 | subject { servers.roles_for(roles).map(&:hostname) } |
196 | 282 | |
197 | context 'when selecting all roles' do | |
198 | let(:roles) { [:all] } | |
199 | ||
200 | it 'returns the roles specified by ROLE' do | |
201 | expect(subject).to eq %w{3 4 5} | |
202 | end | |
203 | end | |
204 | ||
205 | context 'when selecting roles included in ROLE' do | |
206 | let(:roles) { [:app, :web] } | |
207 | ||
208 | it 'returns only roles that match ROLE' do | |
209 | expect(subject).to eq %w{3 4} | |
210 | end | |
211 | end | |
212 | ||
213 | context 'when selecting roles not included in ROLE' do | |
214 | let(:roles) { [:app] } | |
215 | ||
216 | it 'is empty' do | |
217 | expect(subject).to be_empty | |
218 | end | |
219 | end | |
220 | end | |
221 | ||
283 | context 'with the ROLES environment variable set' do | |
284 | ||
285 | 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 | |
291 | let(:roles) { [:all] } | |
292 | it 'ignores it' do | |
293 | expect(subject).to eq %w{1 2 3 4 5} | |
294 | end | |
295 | end | |
296 | ||
297 | context 'when selecting specific roles' do | |
298 | let(:roles) { [:app, :web] } | |
299 | it 'ignores it' do | |
300 | expect(subject).to eq %w{1 2 3 4} | |
301 | end | |
302 | end | |
303 | ||
304 | context 'when selecting roles not included in ROLE' do | |
305 | let(:roles) { [:app] } | |
306 | it 'ignores it' do | |
307 | expect(subject).to eq %w{1 2} | |
308 | end | |
309 | end | |
310 | ||
311 | end | |
312 | ||
313 | context 'with the HOSTS environment variable set' do | |
314 | ||
315 | 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 | |
321 | let(:roles) { [:all] } | |
322 | it 'ignores it' do | |
323 | expect(subject).to eq %w{1 2 3 4 5} | |
324 | end | |
325 | end | |
326 | ||
327 | context 'when selecting specific roles' do | |
328 | let(:roles) { [:app, :web] } | |
329 | it 'ignores it' do | |
330 | expect(subject).to eq %w{1 2 3 4} | |
331 | end | |
332 | end | |
333 | ||
334 | context 'when selecting no roles' do | |
335 | let(:roles) { [] } | |
336 | it 'ignores it' do | |
337 | expect(subject).to be_empty | |
338 | end | |
339 | end | |
340 | ||
341 | end | |
342 | ||
343 | end | |
222 | 344 | end |
223 | 345 | end |
224 | 346 | end |
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 | ||
7 | 14 | describe '.env' do |
8 | 15 | it 'is a global accessor to a single instance' do |
9 | 16 | Configuration.env.set(:test, true) |
10 | expect(Configuration.env.fetch(:test)).to be_true | |
17 | expect(Configuration.env.fetch(:test)).to be_truthy | |
11 | 18 | end |
12 | 19 | end |
13 | 20 | |
43 | 50 | end |
44 | 51 | |
45 | 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 | |
59 | config.set_if_empty(:key, :value) | |
60 | expect(subject).to eq :value | |
61 | end | |
62 | ||
63 | it 'does not overwrite the value' do | |
64 | config.set(:key, :value) | |
65 | config.set_if_empty(:key, :update) | |
46 | 66 | expect(subject).to eq :value |
47 | 67 | end |
48 | 68 | end |
137 | 157 | |
138 | 158 | describe 'asking' do |
139 | 159 | let(:question) { stub } |
160 | let(:options) { Hash.new } | |
140 | 161 | |
141 | 162 | before do |
142 | Configuration::Question.expects(:new).with(config, :branch, :default). | |
163 | Configuration::Question.expects(:new).with(:branch, :default, options). | |
143 | 164 | returns(question) |
144 | 165 | end |
145 | 166 | |
146 | 167 | it 'prompts for the value when fetching' do |
147 | config.ask(:branch, :default) | |
168 | config.ask(:branch, :default, options) | |
148 | 169 | expect(config.fetch(:branch)).to eq question |
149 | 170 | end |
150 | 171 | end |
158 | 179 | config.backend = :test |
159 | 180 | expect(config.backend).to eq :test |
160 | 181 | end |
182 | ||
183 | describe "ssh_options for Netssh" do | |
184 | it 'merges them with the :ssh_options variable' do | |
185 | config.set :format, :pretty | |
186 | 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 | |
189 | config.configure_backend | |
190 | expect(config.backend.config.backend.config.ssh_options).to eq({ user: 'albert', password: 'einstein' }) | |
191 | end | |
192 | end | |
161 | 193 | end |
162 | 194 | end |
163 | 195 | end |
0 | 0 | require 'spec_helper' |
1 | 1 | |
2 | module Capistrano | |
3 | module DSL | |
2 | describe Capistrano::DSL::Paths do | |
4 | 3 | |
5 | class DummyPaths | |
6 | include Paths | |
4 | let(:dsl) { Class.new.extend Capistrano::DSL } | |
5 | let(:parent) { Pathname.new('/var/shared') } | |
6 | let(:paths) { Class.new.extend Capistrano::DSL::Paths } | |
7 | ||
8 | let(:linked_dirs) { %w{log public/system} } | |
9 | let(:linked_files) { %w{config/database.yml log/my.log} } | |
10 | ||
11 | before do | |
12 | dsl.set(:deploy_to, '/var/www') | |
13 | end | |
14 | ||
15 | describe '#linked_dirs' do | |
16 | subject { paths.linked_dirs(parent) } | |
17 | ||
18 | before do | |
19 | paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs) | |
7 | 20 | end |
8 | 21 | |
9 | describe Paths do | |
10 | let(:paths) { DummyPaths.new } | |
11 | let(:parent) { Pathname.new('/var/shared') } | |
12 | ||
13 | let(:linked_dirs) { %w{log public/system} } | |
14 | let(:linked_files) { %w{config/database.yml log/my.log} } | |
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 | |
15 | 26 | |
16 | 27 | |
17 | describe '#linked_dirs' do | |
18 | subject { paths.linked_dirs(parent) } | |
28 | describe '#linked_files' do | |
29 | subject { paths.linked_files(parent) } | |
19 | 30 | |
20 | before do | |
21 | paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs) | |
22 | end | |
31 | before do | |
32 | paths.expects(:fetch).with(:linked_files).returns(linked_files) | |
33 | end | |
23 | 34 | |
24 | it 'returns the full pathnames' do | |
25 | expect(subject).to eq [Pathname.new('/var/shared/log'), Pathname.new('/var/shared/public/system')] | |
26 | end | |
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 | |
41 | subject { paths.linked_file_dirs(parent) } | |
42 | ||
43 | before do | |
44 | paths.expects(:fetch).with(:linked_files).returns(linked_files) | |
45 | end | |
46 | ||
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 | |
53 | subject { paths.linked_dir_parents(parent) } | |
54 | ||
55 | before do | |
56 | paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs) | |
57 | end | |
58 | ||
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 | ||
66 | subject { dsl.release_path } | |
67 | ||
68 | context 'where no release path has been set' do | |
69 | before do | |
70 | dsl.delete(:release_path) | |
27 | 71 | end |
28 | 72 | |
73 | it 'returns the `current_path` value' do | |
74 | expect(subject.to_s).to eq '/var/www/current' | |
75 | end | |
76 | end | |
29 | 77 | |
30 | describe '#linked_files' do | |
31 | subject { paths.linked_files(parent) } | |
32 | ||
33 | before do | |
34 | paths.expects(:fetch).with(:linked_files).returns(linked_files) | |
35 | end | |
36 | ||
37 | it 'returns the full pathnames' do | |
38 | expect(subject).to eq [Pathname.new('/var/shared/config/database.yml'), Pathname.new('/var/shared/log/my.log')] | |
39 | end | |
78 | context 'where the release path has been set' do | |
79 | before do | |
80 | dsl.set(:release_path,'/var/www/release_path') | |
40 | 81 | end |
41 | 82 | |
42 | describe '#linked_file_dirs' do | |
43 | subject { paths.linked_file_dirs(parent) } | |
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 | |
44 | 88 | |
45 | before do | |
46 | paths.expects(:fetch).with(:linked_files).returns(linked_files) | |
47 | end | |
89 | describe '#set_release_path' do | |
90 | let(:now) { Time.parse("Oct 21 16:29:00 2015") } | |
91 | subject { dsl.release_path } | |
48 | 92 | |
49 | it 'returns the full paths names of the parent dirs' do | |
50 | expect(subject).to eq [Pathname.new('/var/shared/config'), Pathname.new('/var/shared/log')] | |
51 | end | |
93 | context 'without a timestamp' do | |
94 | before do | |
95 | dsl.env.expects(:timestamp).returns(now) | |
96 | dsl.set_release_path | |
52 | 97 | end |
53 | 98 | |
54 | describe '#linked_dir_parents' do | |
55 | subject { paths.linked_dir_parents(parent) } | |
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 | |
56 | 103 | |
57 | before do | |
58 | paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs) | |
59 | end | |
60 | ||
61 | it 'returns the full paths names of the parent dirs' do | |
62 | expect(subject).to eq [Pathname.new('/var/shared'), Pathname.new('/var/shared/public')] | |
63 | end | |
104 | context 'with a timestamp' do | |
105 | before do | |
106 | dsl.set_release_path('timestamp') | |
64 | 107 | end |
65 | 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 | |
116 | subject { dsl.deploy_config_path.to_s } | |
117 | ||
118 | context 'when not specified' do | |
119 | before do | |
120 | dsl.delete(:deploy_config_path) | |
121 | end | |
122 | ||
123 | 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 | |
140 | subject { dsl.stage_config_path.to_s } | |
141 | ||
142 | context 'when not specified' do | |
143 | ||
144 | before do | |
145 | dsl.delete(:stage_config_path) | |
146 | end | |
147 | ||
148 | 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 | |
165 | subject { dsl.repo_path.to_s } | |
166 | ||
167 | context 'when not specified' do | |
168 | ||
169 | before do | |
170 | dsl.delete(:repo_path) | |
171 | end | |
172 | ||
173 | 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' | |
186 | end | |
66 | 187 | end |
67 | 188 | end |
68 | 189 | end |
0 | require 'spec_helper' | |
1 | ||
2 | module Capistrano | |
3 | class DummyTaskEnhancements | |
4 | include TaskEnhancements | |
5 | end | |
6 | ||
7 | describe TaskEnhancements do | |
8 | let(:task_enhancements) { DummyTaskEnhancements.new } | |
9 | ||
10 | describe 'ordering' do | |
11 | ||
12 | after do | |
13 | task.clear | |
14 | before_task.clear | |
15 | after_task.clear | |
16 | Rake::Task.clear | |
17 | end | |
18 | ||
19 | let(:order) { [] } | |
20 | let!(:task) do | |
21 | Rake::Task.define_task('task', [:order]) do |t, args| | |
22 | args['order'].push 'task' | |
23 | end | |
24 | end | |
25 | ||
26 | let!(:before_task) do | |
27 | Rake::Task.define_task('before_task') do | |
28 | order.push 'before_task' | |
29 | end | |
30 | end | |
31 | ||
32 | let!(:after_task) do | |
33 | Rake::Task.define_task('after_task') do | |
34 | order.push 'after_task' | |
35 | end | |
36 | end | |
37 | ||
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') | |
41 | ||
42 | Rake::Task['task'].invoke order | |
43 | ||
44 | expect(order).to eq(['before_task', 'task', 'after_task']) | |
45 | end | |
46 | ||
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') | |
50 | ||
51 | Rake::Task['task'].invoke order | |
52 | ||
53 | expect(order).to eq(['before_task', 'task', 'after_task']) | |
54 | end | |
55 | ||
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' | |
59 | end | |
60 | ||
61 | task_enhancements.before('task', 'before_task_custom', :order) do |t, args| | |
62 | order.push 'before_task' | |
63 | end | |
64 | ||
65 | Rake::Task['task'].invoke(order) | |
66 | ||
67 | expect(order).to eq(['before_task', 'task', 'after_task']) | |
68 | end | |
69 | ||
70 | end | |
71 | ||
72 | describe 'remote_file' do | |
73 | subject(:remote_file) { task_enhancements.remote_file('source' => 'destination') } | |
74 | ||
75 | it { expect(remote_file.name).to eq('source') } | |
76 | it { is_expected.to be_a(Capistrano::UploadTask) } | |
77 | ||
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) } | |
84 | end | |
85 | end | |
86 | end | |
87 | end |
26 | 26 | before do |
27 | 27 | dsl.set(:stage, :sandbox) |
28 | 28 | end |
29 | it { should be_true } | |
29 | it { expect(subject).to be_truthy } | |
30 | 30 | end |
31 | 31 | |
32 | 32 | context 'stage is not set' do |
33 | 33 | before do |
34 | 34 | dsl.set(:stage, nil) |
35 | 35 | end |
36 | it { should be_false } | |
36 | it { expect(subject).to be_falsey } | |
37 | 37 | end |
38 | 38 | end |
39 | 39 | |
47 | 47 | dsl.sudo(:my, :command) |
48 | 48 | end |
49 | 49 | end |
50 | ||
51 | describe '#local_user' do | |
52 | ||
53 | before do | |
54 | Etc.expects(:getlogin) | |
55 | end | |
56 | ||
57 | it 'delegates to Etc#getlogin' do | |
58 | dsl.local_user | |
59 | end | |
60 | end | |
61 | 50 | end |
62 | 51 | end |
30 | 30 | describe "#check" do |
31 | 31 | it "should test the repo url" do |
32 | 32 | context.expects(:repo_url).returns(:url) |
33 | context.expects(:test).with(:git, :'ls-remote -h', :url).returns(true) | |
33 | context.expects(:execute).with(:git, :'ls-remote --heads', :url).returns(true) | |
34 | 34 | |
35 | 35 | subject.check |
36 | 36 | end |
56 | 56 | end |
57 | 57 | |
58 | 58 | describe "#release" do |
59 | it "should run git archive" do | |
60 | context.expects(:fetch).returns(:branch) | |
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) | |
61 | 62 | context.expects(:release_path).returns(:path) |
62 | 63 | |
63 | context.expects(:execute).with(:git, :archive, :branch, '| tar -x -C', :path) | |
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) | |
64 | 75 | |
65 | 76 | subject.release |
66 | 77 | end |
56 | 56 | end |
57 | 57 | |
58 | 58 | describe "#release" do |
59 | it "should run hg archive" do | |
60 | context.expects(:fetch).returns(:branch) | |
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) | |
61 | 62 | context.expects(:release_path).returns(:path) |
62 | 63 | |
63 | 64 | context.expects(:execute).with(:hg, "archive", :path, "--rev", :branch) |
64 | 65 | |
65 | 66 | subject.release |
66 | 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 | |
67 | 78 | end |
68 | 79 | end |
69 | 80 | end |
49 | 49 | describe "#repo_url" do |
50 | 50 | it "should return the repo url according to the context" do |
51 | 51 | context.expects(:repo_url).returns(:url) |
52 | subject.repo_url.should == :url | |
52 | expect(subject.repo_url).to eq(:url) | |
53 | 53 | end |
54 | 54 | end |
55 | 55 | |
56 | 56 | describe "#repo_path" do |
57 | 57 | it "should return the repo path according to the context" do |
58 | 58 | context.expects(:repo_path).returns(:path) |
59 | subject.repo_path.should == :path | |
59 | expect(subject.repo_path).to eq(:path) | |
60 | 60 | end |
61 | 61 | end |
62 | 62 | |
63 | 63 | describe "#release_path" do |
64 | 64 | it "should return the release path according to the context" do |
65 | 65 | context.expects(:release_path).returns('/path/to/nowhere') |
66 | subject.release_path.should == '/path/to/nowhere' | |
66 | expect(subject.release_path).to eq('/path/to/nowhere') | |
67 | 67 | end |
68 | 68 | end |
69 | 69 |
59 | 59 | it "should run svn export" do |
60 | 60 | context.expects(:release_path).returns(:path) |
61 | 61 | |
62 | context.expects(:execute).with(:svn, :export, '.', :path) | |
62 | context.expects(:execute).with(:svn, :export, '--force', '.', :path) | |
63 | 63 | |
64 | 64 | subject.release |
65 | 65 | end |
66 | 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 | |
67 | 77 | end |
68 | 78 | end |
0 | require 'spec_helper' | |
1 | ||
2 | describe Capistrano::UploadTask do | |
3 | let(:app) { Rake.application = Rake::Application.new } | |
4 | ||
5 | subject(:upload_task) { described_class.define_task('path/file.yml') } | |
6 | ||
7 | it { is_expected.to be_a(Rake::FileCreationTask) } | |
8 | it { is_expected.to be_needed } | |
9 | ||
10 | context 'inside namespace' do | |
11 | let(:normal_task) { Rake::Task.define_task('path/other_file.yml') } | |
12 | ||
13 | around { |ex| app.in_namespace('namespace', &ex) } | |
14 | ||
15 | it { expect(upload_task.name).to eq('path/file.yml') } | |
16 | it { expect(upload_task.scope.path).to eq('namespace') } | |
17 | end | |
18 | end |
23 | 23 | context 'with exact version' do |
24 | 24 | context 'valid' do |
25 | 25 | let(:version) { '3.0.1' } |
26 | it { should be_true } | |
26 | it { expect(subject).to be_truthy } | |
27 | 27 | end |
28 | 28 | |
29 | 29 | context 'invalid - lower' do |
47 | 47 | context 'with optimistic versioning' do |
48 | 48 | context 'valid' do |
49 | 49 | let(:version) { '>= 3.0.0' } |
50 | it { should be_true } | |
50 | it { expect(subject).to be_truthy } | |
51 | 51 | end |
52 | 52 | |
53 | 53 | context 'invalid - lower' do |
65 | 65 | context '2 decimal places' do |
66 | 66 | context 'valid' do |
67 | 67 | let(:version) { '~> 3.0.0' } |
68 | it { should be_true } | |
68 | it { expect(subject).to be_truthy } | |
69 | 69 | end |
70 | 70 | |
71 | 71 | context 'invalid' do |
82 | 82 | |
83 | 83 | context 'valid' do |
84 | 84 | let(:version) { '~> 3.1' } |
85 | it { should be_true } | |
85 | it { expect(subject).to be_truthy } | |
86 | 86 | end |
87 | 87 | |
88 | 88 | context 'invalid' do |
2 | 2 | require 'capistrano/all' |
3 | 3 | require 'rspec' |
4 | 4 | require 'mocha/api' |
5 | require 'time' | |
5 | 6 | |
6 | 7 | # Requires supporting files with custom matchers and macros, etc, |
7 | 8 | # in ./support/ and its subdirectories. |
8 | 9 | Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each {|f| require f} |
9 | 10 | |
10 | 11 | RSpec.configure do |config| |
11 | config.treat_symbols_as_metadata_keys_with_true_values = true | |
12 | config.raise_errors_for_deprecations! | |
12 | 13 | config.mock_framework = :mocha |
13 | 14 | config.order = 'random' |
14 | 15 | end |
0 | Vagrant::Config.run do |config| | |
0 | require 'open-uri' | |
1 | ||
2 | Vagrant.configure("2") do |config| | |
3 | ||
4 | config.ssh.insert_key = false | |
1 | 5 | |
2 | 6 | [:app].each_with_index do |role, i| |
3 | 7 | config.vm.define(role, primary: true) do |config| |
4 | config.vm.box = role | |
5 | config.vm.box = 'precise64' | |
6 | config.vm.box_url = 'http://files.vagrantup.com/precise64.box' | |
7 | config.vm.forward_port 22, "222#{i}".to_i | |
8 | config.vm.provision :shell, inline: 'yes | sudo apt-get install git-core' | |
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' | |
12 | ||
13 | vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r",&:read) | |
14 | ||
15 | config.vm.provision :shell, | |
16 | inline: <<-INLINE | |
17 | install -d -m 700 /root/.ssh | |
18 | echo -e "#{vagrantkey}" > /root/.ssh/authorized_keys | |
19 | chmod 0600 /root/.ssh/authorized_keys | |
20 | INLINE | |
9 | 21 | end |
10 | 22 | end |
11 | ||
12 | 23 | end |
0 | task :am_i_root do | |
1 | on roles(:all) do |host| | |
2 | host.user = 'root' | |
3 | ident = capture :id, '-a' | |
4 | info "I am #{ident}" | |
5 | end | |
6 | on roles(:all) do |host| | |
7 | ident = capture :id, '-a' | |
8 | info "I am #{ident}" | |
9 | end | |
10 | end |
0 | 0 | require 'fileutils' |
1 | require 'pathname' | |
2 | ||
1 | 3 | module TestApp |
2 | 4 | extend self |
3 | 5 | |
10 | 12 | set :deploy_to, '#{deploy_to}' |
11 | 13 | set :repo_url, 'git://github.com/capistrano/capistrano.git' |
12 | 14 | set :branch, 'master' |
13 | set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key" } | |
15 | set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key", auth_methods: ['publickey'] } | |
14 | 16 | server 'vagrant@localhost:2220', roles: %w{web app} |
15 | 17 | set :linked_files, #{linked_files} |
16 | 18 | set :linked_dirs, #{linked_dirs} |