diff --git a/.travis.yml b/.travis.yml
index 5e7d8cc..b80bed5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,11 @@
- 2.1.0
- 2.0.0
- 1.9.3
- - 1.9.2
- - rbx
+ - rbx-2
script: bundle exec rake spec
+install: bundle install --jobs=1
cache: bundler
-bundler_args: --without cucumber
+branches:
+ except:
+ - legacy-v2
+sudo: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e09dcca..ec2da8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,149 @@
## master
-https://github.com/capistrano/capistrano/compare/v3.2.0...HEAD
+https://github.com/capistrano/capistrano/compare/v3.4.0...HEAD
+
+## `3.4.0`
+
+https://github.com/capistrano/capistrano/compare/v3.3.5...v3.4.0
+
+* Fixed fetch revision for annotated git tags. (@igorsokolov)
+* Fixed updating roles when custom user or port is specified. (@ayastreb)
+* Disables statistics collection.
+
+* `bin/` is not suggested to be in `linked_dirs` anymore (@kirs)
+ * bin/ is often checked out into repo
+ * https://github.com/capistrano/bundler/issues/45#issuecomment-69349237
+
+* Bugfix:
+ * release_roles did not honour additional property filtering (@townsen)
+ * Refactored and simplified property filtering code (@townsen)
+
+* Breaking Changes
+ * Hosts with the same name are now consolidated into one irrespective of the
+ user and port. This allows multiple declarations of a server to be made safely.
+ The last declared properties will win. See capistrnorb.com Properties documentation
+ for details.
+ * Inside the on() block the host variable is now a copy of the host, so changes can be
+ made within the block (such as dynamically overriding the user) that will not persist.
+ This is very convenient for switching the SSH user temporarily to 'root' for example.
+
+* Minor changes
+ * Add role_properties() method (see capistrano.github.io PR for doc) (@townsen)
+ * Add equality syntax ( eg. port: 1234) for property filtering (@townsen)
+ * Add documentation regarding property filtering (@townsen)
+ * Clarify wording and recommendation in stage template. (@Kriechi)
+ * Both available syntaxes provide similar functionality, do not use both for the same server+role combination.
+ * Allow specification of repo_path using stage variable
+ default is as before (@townsen)
+
+## `3.3.5`
+
+https://github.com/capistrano/capistrano/compare/v3.3.4...v3.3.5
+
+* Fixed setting properties twice when creating new server. See [issue
+ #1214](https://github.com/capistrano/capistrano/issues/1214) (@ayastreb)
+
+## `3.3.4`
+
+https://github.com/capistrano/capistrano/compare/v3.3.3...v3.3.4
+
+* Minor changes:
+ * Rely on a newer version of capistrano-stats with better privacy (@leehambley)
+ * Fix cucumber spec for loading tasks from stage configs (@sponomarev)
+ * Minor documentation fixes (@deeeki, @seuros, @andresilveira)
+ * Spec improvements (@dimitrid, @sponomarev)
+ * Fix to CLI flags for git-ls-remote (@dimitrid)
+
+## `3.3.3`
+
+https://github.com/capistrano/capistrano/compare/v3.2.1...v3.3.3
+
+* Enhancement (@townsen)
+ * Added the variable `:repo_tree` which allows the specification of a sub-tree that
+ will be extracted from the repository. This is useful when deploying a project
+ that lives in a subdirectory of a larger repository.
+ Implemented only for git and hg.
+ If not defined then the behaviour is as previously and the whole repository is
+ extracted (subject to git-archive `.gitattributes` of course).
+
+* Enhancement (@townsen): Remove unnecessary entries from default backtrace
+
+ When the `--backtrace` (or `--trace`) command line option is not supplied
+ Rake lowers the noise level in exception backtraces by building
+ a regular expression containing all the system library paths and
+ using it to exclude backtrace entries that match.
+
+ This does not always go far enough, particularly in RVM environments when
+ many gem paths are added. This commit reverses that approach and _only_
+ include backtrace entries that fall within the Capfile and list of tasks
+ imported thereafter. This makes reading exceptions much easier on the eye.
+
+ If the full unexpurgated backtrace is required then the --backtrace
+ and --trace options supply it as before.
+
+* Disable loading stages configs on `cap -T` (@sponomarev)
+
+* Enhancements (@townsen)
+ * Fix matching on hosts with custom ports or users set
+ * Previously filtering would affect any generated configuration files so that
+ files newly deployed would not be the same as those on the hosts previously
+ deployed (and now excluded by filters). This is almost certainly not what is
+ wanted: the filters should apply only to the on() method and thus any
+ configuration files deployed will be identical across the set of servers
+ making up the stage.
+ * Host and Role filtering now affects only `on()` commands
+ and not the `roles()`, `release_roles()` and `primary()` methods.
+ * This applies to filters defined via the command line, the environment
+ and the :filter variable.
+ * Filtering now supports Regular expressions
+ * This change _could_ cause existing scripts that use filtering and depend on
+ the old behaviour to fail, though it is unlikely. Users who rely on
+ filtering should check that generated configuration files are correct, and
+ where not introduce server properties to do the filtering. For example, if a
+ filter was used to specify an active subset of servers (by hostname), it should
+ be removed and replaced with an 'active' property (set to true or false) on the
+ server definitions. This keeps the stage file as the canonical model of the
+ deployment environment.
+
+ * See the documentation in the README.md file
+
+* Enhancements (@townsen)
+ * Added set_if_empty method to DSL to allow conditional setting
+ * Altered standard Capistrano defaults so that they are not set
+ at the start of a stage if they have been previously set. This
+ allows variables like :default_env to be set in deploy.rb.
+ * Deep copy properties added using the 'roles' keyword
+ * If a property exists on a server when another definition is
+ encountered and is an Array, Set or Hash then add the new values
+
+ This allows roles to specify properties common to all servers and
+ then for individual servers to modify them, keeping things DRY
+
+Breaking Changes:
+ * 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)
+ * 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.
+
+* Bug Fixes:
+ * Fixed compatibility with FreeBSD tar (@robbertkl)
+ * remote_file can be used inside a namespace (@mikz)
+
+* Minor Changes
+ * Remove -v flag from mkdir call. (@caligo-mentis)
+ * Capistrano now allows to customize `local_user` for revision log. (@sauliusgrigaitis)
+ * Added tests for after/before hooks features (@juanibiapina, @miry)
+ * Added `--force` flag to `svn export` command to fix errors when the release directory already exists.
+ * Improved the output of `cap --help`. (@mbrictson)
+ * Cucumber suite now runs on the latest version of Vagrant (@tpett)
+ * The `ask` method now supports the `echo: false` option. (@mbrictson, @kaikuchn)
+ * Cucumber scenario improvements (@bruno-)
+ * Added suggestion to Capfile to use 'capistrano-passenger' gem, replacing suggestion in config/deploy.rb to re-implement 'deploy:restart' (@betesh)
+ * Updated svn fetch_revision method to use `svnversion`
+ * `cap install` no longer overwrites existing files. (@dmarkow)
## `3.2.1`
+
+https://github.com/capistrano/capistrano/compare/v3.2.0...v3.2.1
* Bug Fixes:
* 3.2.0 introduced some behaviour to modify the way before/after hooks were called, to allow the optional
@@ -19,6 +159,7 @@
* Changed asking question to more standard format (like common unix commandline tools) (@sponomarev)
* Fixed typos in the README. (@sponomarev)
* Added `keys` method to Configuration to allow introspection of configuration options. (@juanibiapina)
+ * Improve error message when git:check fails (raise instead of silently `exit 1`) (@mbrictson)
## `3.2.0`
@@ -40,7 +181,8 @@
Breaking changes:
* `deploy:restart` task **is no longer run by default**.
- 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'`).
+ 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'`)
+ or, for passenger applications, use the capistrano-passenger gem.
Please, check https://github.com/capistrano/capistrano/commit/4e6523e1f50707499cf75eb53dce37a89528a9b0 for more information. (@kirs)
diff --git a/Gemfile b/Gemfile
index 8b274d5..d41a5ae 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,10 +4,6 @@
gemspec
group :cucumber do
- gem 'kuroko'
gem 'cucumber'
+ gem 'rspec', '~> 3.0.0'
end
-
-platforms :rbx do
- gem 'rubysl', '~> 2.0'
-end
diff --git a/README.md b/README.md
index c432174..99e1160 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,33 @@
-# 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)
+# 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)
-## Requirements
+## Documentation
-* Ruby >= 1.9 (JRuby and C-Ruby/YARV are supported)
+Check out the [online documentation](http://capistranorb.com) of Capistrano 3 hosted via this [repository](https://github.com/capistrano/capistrano.github.io).
## Support
Need help with getting Capistrano up and running? Got a code problem you want to get solved quickly?
-Get Capistrano support on CodersClan.
+Get Capistrano support on CodersClan.
-
+## Requirements
+
+* Ruby >= 1.9.3 (JRuby and C-Ruby/YARV are supported)
+
+Capistrano support these source code version control systems out of the box:
+
+* Git 1.8 or higher
+* Mercurial
+* SVN
+
+Binaries for these VCS might be required on the local and/or the remote systems.
## Installation
Add this line to your application's Gemfile:
``` ruby
-gem 'capistrano', '~> 3.2.0'
+gem 'capistrano', '~> 3.3.0'
```
And then execute:
@@ -55,212 +65,46 @@
## Usage
``` sh
-$ bundle exec cap -vT
+# list all available tasks
+$ bundle exec cap -T
+# deploy to the staging environment
$ bundle exec cap staging deploy
+
+# deploy to the production environment
$ bundle exec cap production deploy
+# simulate deploying to the production environment
+# does not actually do anything
$ bundle exec cap production deploy --dry-run
+
+# list task dependencies
$ bundle exec cap production deploy --prereqs
+
+# trace through task invocations
$ bundle exec cap production deploy --trace
```
-## Tasks
+## Testing
-``` ruby
-server 'example.com', roles: [:web, :app]
-server 'example.org', roles: [:db, :workers]
-desc "Report Uptimes"
-task :uptime do
- on roles(:all) do |host|
- execute :any_command, "with args", :here, "and here"
- info "Host #{host} (#{host.roles.to_a.join(', ')}):\t#{capture(:uptime)}"
- end
-end
+Capistrano has two test suites: an RSpec suite and a Cucumber suite. The
+RSpec suite handles quick feedback unit specs. The Cucumber features are
+an integration suite that uses Vagrant to deploy to a real virtual
+server. In order to run the Cucumber suite you will need to install
+[Vagrant](http://www.vagrantup.com/) and Vagrant supported
+virtualization software like
+[VirtualBox](https://www.virtualbox.org/wiki/Downloads).
+
```
+# To run the RSpec suite
+$ rake spec
-**Note**:
+# To run the Cucumber suite
+$ rake features
-**tl;dr**: `execute(:bundle, :install)` and `execute('bundle install')` don't behave identically!
-
-`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.
-
-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.
-## Before / After
-
-Where calling on the same task name, executed in order of inclusion
-
-``` ruby
-# call an existing task
-before :starting, :ensure_user
-
-after :finishing, :notify
-
-
-# or define in block
-before :starting, :ensure_user do
- #
-end
-
-after :finishing, :notify do
- #
-end
+# To run the Cucumber suite and leave the VM running (faster for subsequent runs)
+$ rake features KEEP_RUNNING=1
```
-
-If it makes sense for your use case (often, that means *generating a file*)
-the Rake prerequisite mechanism can be used:
-
-``` ruby
-desc "Create Important File"
-file 'important.txt' do |t|
- sh "touch #{t.name}"
-end
-desc "Upload Important File"
-task :upload => 'important.txt' do |t|
- on roles(:all) do
- upload!(t.prerequisites.first, '/tmp')
- end
-end
-```
-
-The final way to call out to other tasks is to simply `invoke()` them:
-
-``` ruby
-namespace :example do
- task :one do
- on roles(:all) { info "One" }
- end
- task :two do
- invoke "example:one"
- on roles(:all) { info "Two" }
- end
-end
-```
-
-This method is widely used.
-
-## Getting User Input
-
-``` ruby
-desc "Ask about breakfast"
-task :breakfast do
- ask(:breakfast, "pancakes")
- on roles(:all) do |h|
- execute "echo \"$(whoami) wants #{fetch(:breakfast)} for breakfast!\""
- end
-end
-```
-
-Perfect, who needs telephones.
-
-
-## Using password authentication
-
-Password authentication can be done via `set` and `ask` in your deploy environment file (e.g.: config/deploy/production.rb)
-
-```ruby
-set :password, ask('Server password', nil)
-server 'server.domain.com', user: 'ssh_user_name', port: 22, password: fetch(:password), roles: %w{web app db}
-```
-
-## Running local tasks
-
-Local tasks can be run by replacing `on` with `run_locally`
-
-``` ruby
-desc 'Notify service of deployment'
-task :notify do
- run_locally do
- with rails_env: :development do
- rake 'service:notify'
- end
- end
-end
-```
-
-Of course, you can always just use standard ruby syntax to run things locally
-``` ruby
-desc 'Notify service of deployment'
-task :notify do
- %x('RAILS_ENV=development bundle exec rake "service:notify"')
-end
-```
-
-Alternatively you could use the rake syntax
-``` ruby
-desc "Notify service of deployment"
-task :notify do
- sh 'RAILS_ENV=development bundle exec rake "service:notify"'
-end
-```
-## Console
-
-**Note:** Here be dragons. The console is very immature, but it's much more
-cleanly architected than previous incarnations and it'll only get better from
-here on in.
-
-Execute arbitrary remote commands, to use this simply add
-`require 'capistrano/console'` which will add the necessary tasks to your
-environment:
-
-``` sh
-$ bundle exec cap staging console
-```
-
-Then, after setting up the server connections, this is how that might look:
-
-``` sh
-$ bundle exec cap production console
-capistrano console - enter command to execute on production
-production> uptime
- INFO [94db8027] Running /usr/bin/env uptime on leehambley@example.com:22
-DEBUG [94db8027] Command: /usr/bin/env uptime
-DEBUG [94db8027] 17:11:17 up 50 days, 22:31, 1 user, load average: 0.02, 0.02, 0.05
- INFO [94db8027] Finished in 0.435 seconds command successful.
-production> who
- INFO [9ce34809] Running /usr/bin/env who on leehambley@example.com:22
-DEBUG [9ce34809] Command: /usr/bin/env who
-DEBUG [9ce34809] leehambley pts/0 2013-06-13 17:11 (port-11262.pppoe.wtnet.de)
- INFO [9ce34809] Finished in 0.420 seconds command successful.
-```
-
-## A word about PTYs
-
-There is a configuration option which asks the backend driver to ask the remote host
-to assign the connection a *pty*. A *pty* is a pseudo-terminal, which in effect means
-*tell the backend that this is an __interactive__ session*. This is normally a bad idea.
-
-Most of the differences are best explained by [this page](https://github.com/sstephenson/rbenv/wiki/Unix-shell-initialization) from the author of *rbenv*.
-
-**When Capistrano makes a connection it is a *non-login*, *non-interactive* shell.
-This was not an accident!**
-
-It's often used as a band aid to cure issues related to RVM and rbenv not loading login
-and shell initialisation scripts. In these scenarios RVM and rbenv are the tools at fault,
-or at least they are being used incorrectly.
-
-Whilst, especially in the case of language runtimes (Ruby, Node, Python and friends in
-particular) there is a temptation to run multiple versions in parallel on a single server
-and to switch between them using environmental variables, this is an anti-pattern, and
-symptomatic of bad design (e.g. you're testing a second version of Ruby in production because
-your company lacks the infrastructure to test this in a staging environment).
-
-## Configuration
-
-The following variables are settable:
-
-| Variable Name | Description | Notes |
-|:---------------------:|----------------------------------------------------------------------|-----------------------------------------------------------------|
-| `:repo_url` | The URL of your scm repository (git, hg, svn) | file://, https://, ssh://, or svn+ssh:// are all supported |
-| `: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. |
-| `:scm` | The source control system used | `:git`, `:hg`, `:svn` are currently supported |
-| `: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). |
-
-__Support removed__ for following variables:
-
-| Variable Name | Description | Notes |
-|:---------------------:|---------------------------------------------------------------------|-----------------------------------------------------------------|
-| `: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. |
## SSHKit
@@ -273,7 +117,7 @@
MIT License (MIT)
-Copyright (c) 2012-2013 Tom Clements, Lee Hambley
+Copyright (c) 2012-2015 Tom Clements, Lee Hambley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Rakefile b/Rakefile
index 5091758..fc943b1 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,5 +1,9 @@
require "bundler/gem_tasks"
+require "cucumber/rake/task"
+require "rspec/core/rake_task"
task :default => :spec
-require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
+
+Cucumber::Rake::Task.new(:features)
+
diff --git a/capistrano.gemspec b/capistrano.gemspec
index 4558e1d..40e3c48 100644
--- a/capistrano.gemspec
+++ b/capistrano.gemspec
@@ -20,11 +20,14 @@
gem.licenses = ['MIT']
gem.post_install_message = < 1.3'
gem.add_dependency 'rake', '>= 10.0.0'
gem.add_dependency 'i18n'
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
deleted file mode 100644
index 7c48547..0000000
Binary files a/checksums.yaml.gz and /dev/null differ
diff --git a/features/configuration.feature b/features/configuration.feature
index ff61319..1cd70af 100644
--- a/features/configuration.feature
+++ b/features/configuration.feature
@@ -19,10 +19,10 @@
Then the task is successful
And contains "install" in the output
- Scenario: Show install task with configuration in a custom location
+ Scenario: Hide install task with configuration in a custom location
And config stage file has line "desc 'Special Task'"
And config stage file has line "task :special_stage_task"
But the configuration is in a custom location
When I run "cap -T"
Then the task is successful
- And contains "special_stage_task" in the output
+ And doesn't contain "special_stage_task" in the output
diff --git a/features/deploy.feature b/features/deploy.feature
index a97a53b..b9fea56 100644
--- a/features/deploy.feature
+++ b/features/deploy.feature
@@ -6,7 +6,8 @@
Scenario: Creating the repo
When I run cap "git:check"
- Then references in the remote repo are listed
+ Then the task is successful
+ And references in the remote repo are listed
Scenario: Creating the directory structure
When I run cap "deploy:check:directories"
@@ -22,16 +23,16 @@
Then directories referenced in :linked_files are created in shared
Scenario: Checking linked files - missing file
- Given a required file
- But the file does not exist
+ Given a linked file "missing_file.txt"
+ But file "missing_file.txt" does not exist in shared path
When I run cap "deploy:check:linked_files"
- Then the task will exit
+ Then the task fails
Scenario: Checking linked files - file exists
- Given a required file
- And that file exists
+ Given a linked file "existing_file.txt"
+ And file "existing_file.txt" exists in shared path
When I run cap "deploy:check:linked_files"
- Then the task will be successful
+ Then the task is successful
Scenario: Creating a release
Given I run cap "deploy:check:directories"
@@ -40,7 +41,7 @@
And the release is created
Scenario: Symlink linked files
- When I run cap "deploy:symlink:linked_files" as part of a release
+ When I run cap "deploy:symlink:linked_files deploy:symlink:release" as part of a release
Then file symlinks are created in the new release
Scenario: Symlink linked dirs
diff --git a/features/sshconnect.feature b/features/sshconnect.feature
new file mode 100644
index 0000000..f6ee635
--- /dev/null
+++ b/features/sshconnect.feature
@@ -0,0 +1,11 @@
+Feature: SSH Connection
+
+ Background:
+ Given a test app with the default configuration
+ And servers with the roles app and web
+ And a task which executes as root
+
+ Scenario: Switching from default user to root and back again
+ When I run cap "am_i_root"
+ Then the task is successful
+ And the output matches "I am uid=0\(root\)" followed by "I am uid=\d+\(vagrant\)"
diff --git a/features/step_definitions/assertions.rb b/features/step_definitions/assertions.rb
index 36a0d3c..3e54ddd 100644
--- a/features/step_definitions/assertions.rb
+++ b/features/step_definitions/assertions.rb
@@ -1,4 +1,5 @@
Then(/^references in the remote repo are listed$/) do
+ expect(@output).to include('refs/heads/master')
end
Then(/^the shared path is created$/) do
@@ -22,13 +23,6 @@
end
end
-Then(/^the task will be successful$/) do
-end
-
-
-Then(/^the task will exit$/) do
-end
-
Then(/^the repo is cloned$/) do
run_vagrant_command(test_dir_exists(TestApp.repo_path))
end
@@ -38,9 +32,8 @@
end
Then(/^file symlinks are created in the new release$/) do
- pending
TestApp.linked_files.each do |file|
- run_vagrant_command(test_symlink_exists(TestApp.release_path.join(file)))
+ run_vagrant_command(test_symlink_exists(TestApp.current_path.join(file)))
end
end
@@ -57,26 +50,26 @@
Then(/^the deploy\.rb file is created$/) do
file = TestApp.test_app_path.join('config/deploy.rb')
- expect(File.exists?(file)).to be_true
+ expect(File.exists?(file)).to be true
end
Then(/^the default stage files are created$/) do
staging = TestApp.test_app_path.join('config/deploy/staging.rb')
production = TestApp.test_app_path.join('config/deploy/production.rb')
- expect(File.exists?(staging)).to be_true
- expect(File.exists?(production)).to be_true
+ expect(File.exists?(staging)).to be true
+ expect(File.exists?(production)).to be true
end
Then(/^the tasks folder is created$/) do
path = TestApp.test_app_path.join('lib/capistrano/tasks')
- expect(Dir.exists?(path)).to be_true
+ expect(Dir.exists?(path)).to be true
end
Then(/^the specified stage files are created$/) do
qa = TestApp.test_app_path.join('config/deploy/qa.rb')
production = TestApp.test_app_path.join('config/deploy/production.rb')
- expect(File.exists?(qa)).to be_true
- expect(File.exists?(production)).to be_true
+ expect(File.exists?(qa)).to be true
+ expect(File.exists?(production)).to be true
end
Then(/^it creates the file with the remote_task prerequisite$/) do
@@ -90,7 +83,11 @@
end
Then(/^the task is successful$/) do
- expect(@success).to be_true
+ expect(@success).to be true
+end
+
+Then(/^the task fails$/) do
+ expect(@success).to be_falsey
end
Then(/^the failure task will run$/) do
@@ -100,7 +97,8 @@
Then(/^the failure task will not run$/) do
failed = TestApp.shared_path.join('failed')
- !run_vagrant_command(test_file_exists(failed))
+ expect { run_vagrant_command(test_file_exists(failed)) }
+ .to raise_error(VagrantHelpers::VagrantSSHCommandError)
end
When(/^an error is raised$/) do
@@ -108,6 +106,14 @@
run_vagrant_command(test_file_exists(error))
end
-Then(/contains "(.*?)" in the output/) do |expected|
+Then(/contains "([^"]*)" in the output/) do |expected|
expect(@output).to include(expected)
end
+
+Then(/the output matches "([^"]*)" followed by "([^"]*)"/) do |expected, followedby|
+ expect(@output).to match(/#{expected}.*#{followedby}/m)
+end
+
+Then(/doesn't contain "([^"]*)" in the output/) do |expected|
+ expect(@output).not_to include(expected)
+end
diff --git a/features/step_definitions/cap_commands.rb b/features/step_definitions/cap_commands.rb
index e8da31f..2d6f30c 100644
--- a/features/step_definitions/cap_commands.rb
+++ b/features/step_definitions/cap_commands.rb
@@ -1,5 +1,5 @@
When(/^I run cap "(.*?)"$/) do |task|
- @success = TestApp.cap(task)
+ @success, @output = TestApp.cap(task)
end
When(/^I run cap "(.*?)" as part of a release$/) do |task|
diff --git a/features/step_definitions/setup.rb b/features/step_definitions/setup.rb
index f99ba9d..8fc1e1c 100644
--- a/features/step_definitions/setup.rb
+++ b/features/step_definitions/setup.rb
@@ -6,21 +6,29 @@
vagrant_cli_command('up') rescue nil
end
-Given(/^a required file$/) do
+Given(/^a linked file "(.*?)"$/) do |file|
+ # ignoring other linked files
+ TestApp.append_to_deploy_file("set :linked_files, ['#{file}']")
end
-Given(/^that file exists$/) do
- run_vagrant_command("touch #{TestApp.linked_file}")
+Given(/^file "(.*?)" exists in shared path$/) do |file|
+ file_shared_path = TestApp.shared_path.join(file)
+ run_vagrant_command("mkdir -p #{TestApp.shared_path}")
+ run_vagrant_command("touch #{file_shared_path}")
end
-Given(/^the file does not exist$/) do
- pending
- file = TestApp.linked_file
- run_vagrant_command("[ -f #{file} ] && rm #{file}")
+Given(/^file "(.*?)" does not exist in shared path$/) do |file|
+ file_shared_path = TestApp.shared_path.join(file)
+ run_vagrant_command("mkdir -p #{TestApp.shared_path}")
+ run_vagrant_command("touch #{file_shared_path} && rm #{file_shared_path}")
end
Given(/^a custom task to generate a file$/) do
TestApp.copy_task_to_test_app('spec/support/tasks/database.rake')
+end
+
+Given(/^a task which executes as root$/) do
+ TestApp.copy_task_to_test_app('spec/support/tasks/root.rake')
end
Given(/config stage file has line "(.*?)"/) do |line|
diff --git a/features/support/env.rb b/features/support/env.rb
index 3aed709..853b6f1 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,12 +1,11 @@
-require 'kuroko'
+PROJECT_ROOT = File.expand_path('../../../', __FILE__)
+VAGRANT_ROOT = File.join(PROJECT_ROOT, 'spec/support')
+VAGRANT_BIN = ENV['VAGRANT_BIN'] || "vagrant"
-project_root = File.expand_path('../../../', __FILE__)
-vagrant_root = File.join(project_root, 'spec/support')
-
-Kuroko.configure do |config|
- config.vagrant_root = 'spec/support'
+at_exit do
+ if ENV['KEEP_RUNNING']
+ VagrantHelpers.run_vagrant_command("rm -rf /home/vagrant/var")
+ end
end
-puts vagrant_root.inspect
-
require_relative '../../spec/support/test_app'
diff --git a/features/support/remote_command_helpers.rb b/features/support/remote_command_helpers.rb
index 7c04560..9b5232b 100644
--- a/features/support/remote_command_helpers.rb
+++ b/features/support/remote_command_helpers.rb
@@ -1,5 +1,4 @@
module RemoteCommandHelpers
-
def test_dir_exists(path)
exists?('d', path)
end
@@ -13,11 +12,11 @@
end
def exists?(type, path)
- %{[ -#{type} "#{path}" ] && echo "#{path} exists." || echo "Error: #{path} does not exist."}
+ %{[ -#{type} "#{path}" ]}
end
def safely_remove_file(path)
- run_vagrant_command("rm #{test_file}") rescue Vagrant::Errors::VagrantError
+ run_vagrant_command("rm #{test_file}") rescue VagrantHelpers::VagrantSSHCommandError
end
end
diff --git a/features/support/vagrant_helpers.rb b/features/support/vagrant_helpers.rb
new file mode 100644
index 0000000..1a1822a
--- /dev/null
+++ b/features/support/vagrant_helpers.rb
@@ -0,0 +1,35 @@
+module VagrantHelpers
+ extend self
+
+ class VagrantSSHCommandError < RuntimeError; end
+
+ at_exit do
+ if ENV['KEEP_RUNNING']
+ puts "Vagrant vm will be left up because KEEP_RUNNING is set."
+ puts "Rerun without KEEP_RUNNING set to cleanup the vm."
+ else
+ vagrant_cli_command("destroy -f")
+ end
+ end
+
+ def vagrant_cli_command(command)
+ puts "[vagrant] #{command}"
+ Dir.chdir(VAGRANT_ROOT) do
+ `#{VAGRANT_BIN} #{command} 2>&1`.split("\n").each do |line|
+ puts "[vagrant] #{line}"
+ end
+ end
+ $?
+ end
+
+ def run_vagrant_command(command)
+ if (status = vagrant_cli_command("ssh -c #{command.inspect}")).success?
+ true
+ else
+ fail VagrantSSHCommandError, status
+ end
+ end
+
+end
+
+World(VagrantHelpers)
diff --git a/lib/capistrano/all.rb b/lib/capistrano/all.rb
index 87dbc75..334d2a7 100644
--- a/lib/capistrano/all.rb
+++ b/lib/capistrano/all.rb
@@ -1,6 +1,7 @@
require 'rake'
require 'sshkit'
-require 'sshkit/dsl'
+
+require 'io/console'
Rake.application.options.trace = true
diff --git a/lib/capistrano/application.rb b/lib/capistrano/application.rb
index 5f1ad6f..caefa57 100644
--- a/lib/capistrano/application.rb
+++ b/lib/capistrano/application.rb
@@ -16,9 +16,42 @@
end
def sort_options(options)
- options.push(version, roles, dry_run, hostfilter)
- super
+ not_applicable_to_capistrano = %w(quiet silent verbose)
+ options.reject! do |(switch, *)|
+ switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/
+ end
+
+ super.push(version, dry_run, roles, hostfilter)
end
+
+ def handle_options
+ options.rakelib = ['rakelib']
+ options.trace_output = $stderr
+
+ OptionParser.new do |opts|
+ opts.banner = "See full documentation at http://capistranorb.com/."
+ opts.separator ""
+ opts.separator "Install capistrano in a project:"
+ opts.separator " bundle exec cap install [STAGES=qa,staging,production,...]"
+ opts.separator ""
+ opts.separator "Show available tasks:"
+ opts.separator " bundle exec cap -T"
+ opts.separator ""
+ opts.separator "Invoke (or simulate invoking) a task:"
+ opts.separator " bundle exec cap [--dry-run] STAGE TASK"
+ opts.separator ""
+ opts.separator "Advanced options:"
+
+ opts.on_tail("-h", "--help", "-H", "Display this help message.") do
+ puts opts
+ exit
+ end
+
+ standard_rake_options.each { |args| opts.on(*args) }
+ opts.environment('RAKEOPT')
+ end.parse!
+ end
+
def top_level_tasks
if tasks_without_stage_dependency.include?(@top_level_tasks.first)
@@ -26,6 +59,18 @@
else
@top_level_tasks.unshift(ensure_stage.to_s)
end
+ end
+
+ def display_error_message(ex)
+ unless options.backtrace
+ if loc = Rake.application.find_rakefile_location
+ whitelist = (@imported.dup << loc[0]).map{|f| File.absolute_path(f, loc[1])}
+ pattern = %r@^(?!#{whitelist.map{|p| Regexp.quote(p)}.join('|')})@
+ Rake.application.options.suppress_backtrace_pattern = pattern
+ end
+ trace "(Backtrace restricted to imported tasks)"
+ end
+ super
end
def exit_because_of_exception(ex)
@@ -42,7 +87,7 @@
if options.show_tasks
invoke 'load:defaults'
set(:stage, '')
- Dir[deploy_config_path, stage_definitions].each { |f| add_import f }
+ Dir[deploy_config_path].each { |f| add_import f }
end
super
@@ -74,18 +119,18 @@
def roles
['--roles ROLES', '-r',
- "Filter command to only apply to these roles (separate multiple roles with a comma)",
+ "Run SSH commands only on hosts matching these roles",
lambda { |value|
- Configuration.env.set(:filter, roles: value.split(","))
+ Configuration.env.add_cmdline_filter(:role, value)
}
]
end
def hostfilter
['--hosts HOSTS', '-z',
- "Filter command to only apply to these hosts (separate multiple hosts with a comma)",
+ "Run SSH commands only on matching hosts",
lambda { |value|
- Configuration.env.set(:filter, hosts: value.split(","))
+ Configuration.env.add_cmdline_filter(:host, value)
}
]
end
diff --git a/lib/capistrano/configuration/filter.rb b/lib/capistrano/configuration/filter.rb
new file mode 100644
index 0000000..2e22a19
--- /dev/null
+++ b/lib/capistrano/configuration/filter.rb
@@ -0,0 +1,56 @@
+require 'capistrano/configuration'
+
+module Capistrano
+ class Configuration
+ class Filter
+ def initialize type, values = nil
+ raise "Invalid filter type #{type}" unless [:host,:role].include? type
+ av = Array(values).dup
+ @mode = case
+ when av.size == 0 then :none
+ when av.include?(:all) then :all
+ else type
+ end
+ @rex = case @mode
+ when :host
+ av.map!{|v| (v.is_a?(String) && v =~ /^(?[-A-Za-z0-9.]+)(,\g)*$/) ? v.split(',') : v }
+ av.flatten!
+ av.map! do |v|
+ case v
+ when Regexp then v
+ else
+ vs = v.to_s
+ vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
+ end
+ end
+ Regexp.union av
+ when :role
+ av.map!{|v| v.is_a?(String) ? v.split(',') : v }
+ av.flatten!
+ av.map! do |v|
+ case v
+ when Regexp then v
+ else
+ vs = v.to_s
+ vs =~ %r{^/(.+)/$} ? Regexp.new($1) : %r{^#{vs}$}
+ end
+ end
+ Regexp.union av
+ else
+ nil
+ end
+ end
+ def filter servers
+ as = Array(servers)
+ case @mode
+ when :none then return []
+ when :all then return servers
+ when :host
+ as.select {|s| @rex.match s.hostname}
+ when :role
+ as.select {|s| s.roles.any? {|r| @rex.match r} }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/capistrano/configuration/question.rb b/lib/capistrano/configuration/question.rb
index ffd9960..4447011 100644
--- a/lib/capistrano/configuration/question.rb
+++ b/lib/capistrano/configuration/question.rb
@@ -2,27 +2,23 @@
class Configuration
class Question
- def initialize(env, key, default)
- @env, @key, @default = env, key, default
+ def initialize(key, default, options = {})
+ @key, @default, @options = key, default, options
end
def call
ask_question
- save_response
+ value_or_default
end
private
- attr_reader :env, :key, :default
+ attr_reader :key, :default, :options
def ask_question
$stdout.print question
end
- def save_response
- env.set(key, value)
- end
-
- def value
+ def value_or_default
if response.empty?
default
else
@@ -31,11 +27,27 @@
end
def response
- @response ||= $stdin.gets.chomp
+ return @response if defined? @response
+
+ @response = (gets || "").chomp
+ end
+
+ def gets
+ if echo?
+ $stdin.gets
+ else
+ $stdin.noecho(&:gets).tap{ $stdout.print "\n" }
+ end
+ rescue Errno::EIO
+ # when stdio gets closed
+ end
+
+ def question
+ I18n.t(:question, key: key, default_value: default, scope: :capistrano)
end
- def question
- I18n.t(:question, key: key, default_value: default, scope: :capistrano)
+ def echo?
+ (options || {}).fetch(:echo, true)
end
end
end
diff --git a/lib/capistrano/configuration/server.rb b/lib/capistrano/configuration/server.rb
index c4446b6..35c55d3 100644
--- a/lib/capistrano/configuration/server.rb
+++ b/lib/capistrano/configuration/server.rb
@@ -11,11 +11,13 @@
def add_roles(roles)
Array(roles).each { |role| add_role(role) }
+ self
end
alias roles= add_roles
def add_role(role)
roles.add role.to_sym
+ self
end
def has_role?(role)
@@ -23,8 +25,19 @@
end
def select?(options)
- selector = Selector.for(options)
- selector.call(self)
+ options.each do |k,v|
+ callable = v.respond_to?(:call) ? v: ->(server){server.fetch(v)}
+ result = case k
+ when :filter, :select
+ callable.call(self)
+ when :exclude
+ !callable.call(self)
+ else
+ self.fetch(k) == v
+ end
+ return false unless result
+ end
+ return true
end
def primary
@@ -40,18 +53,16 @@
@properties ||= Properties.new
end
- def netssh_options_with_options
- @netssh_options ||= netssh_options_without_options.merge( fetch(:ssh_options) || {} )
+ def netssh_options
+ @netssh_options ||= super.merge( fetch(:ssh_options) || {} )
end
- alias_method :netssh_options_without_options, :netssh_options
- alias_method :netssh_options, :netssh_options_with_options
def roles_array
roles.to_a
end
def matches?(other)
- user == other.user && hostname == other.hostname && port == other.port
+ hostname == other.hostname
end
private
@@ -71,14 +82,23 @@
end
def set(key, value)
- @properties[key] = value
+ pval = @properties[key]
+ if pval.is_a? Hash and value.is_a? Hash
+ pval.merge!(value)
+ elsif pval.is_a? Set and value.is_a? Set
+ pval.merge(value)
+ elsif pval.is_a? Array and value.is_a? Array
+ pval.concat value
+ else
+ @properties[key] = value
+ end
end
def fetch(key)
@properties[key]
end
- def respond_to?(method)
+ def respond_to?(method, include_all=false)
@properties.has_key?(method)
end
@@ -106,55 +126,6 @@
end
- class Selector
- def initialize(options)
- @options = options
- end
-
- def self.for(options)
- if options.has_key?(:exclude)
- Exclusive
- else
- self
- end.new(options)
- end
-
- def callable
- if key.respond_to?(:call)
- key
- else
- ->(server) { server.fetch(key) }
- end
- end
-
- def call(server)
- callable.call(server)
- end
-
- private
- attr_reader :options
-
- def key
- options[:filter] || options[:select] || all
- end
-
- def all
- ->(server) { :all }
- end
-
- class Exclusive < Selector
-
- def key
- options[:exclude]
- end
-
- def call(server)
- !callable.call(server)
- end
- end
-
- end
-
end
end
end
diff --git a/lib/capistrano/configuration/servers/host_filter.rb b/lib/capistrano/configuration/servers/host_filter.rb
deleted file mode 100644
index d4479b2..0000000
--- a/lib/capistrano/configuration/servers/host_filter.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-module Capistrano
- class Configuration
- class Servers
- class HostFilter
-
- def initialize(available)
- @available = available
- end
-
- def self.for(available)
- new(available).hosts
- end
-
- def hosts
- if host_filter.any?
- @available.select { |server| host_filter.include? server.hostname }
- else
- @available
- end
- end
-
- private
-
- def filter
- if host_filter.any?
- host_filter
- else
- @available
- end
- end
-
- def host_filter
- env_filter | configuration_filter
- end
-
- def configuration_filter
- ConfigurationFilter.new.hosts
- end
-
- def env_filter
- EnvFilter.new.hosts
- end
-
- class ConfigurationFilter
-
- def hosts
- if filter
- Array(filter.fetch(:hosts, []))
- else
- []
- end
- end
-
- def config
- Configuration.env
- end
-
- def filter
- config.fetch(:filter) || config.fetch(:select)
- end
- end
-
-
- class EnvFilter
-
- def hosts
- if filter
- filter.split(',')
- else
- []
- end
- end
-
- def filter
- ENV['HOSTS']
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/capistrano/configuration/servers/role_filter.rb b/lib/capistrano/configuration/servers/role_filter.rb
deleted file mode 100644
index 6de8858..0000000
--- a/lib/capistrano/configuration/servers/role_filter.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-module Capistrano
- class Configuration
- class Servers
- class RoleFilter
-
- def initialize(required, available)
- @required, @available = required, available
- end
-
- def self.for(required, available)
- new(required, available).roles
- end
-
- def roles
- if required.include?(:all)
- available
- else
- required.select { |name| available.include? name }
- end
- end
-
- private
-
- def required
- Array(@required).flat_map(&:to_sym)
- end
-
- def available
- if role_filter.any?
- role_filter
- else
- @available
- end
- end
-
- def role_filter
- env_filter | configuration_filter
- end
-
- def configuration_filter
- ConfigurationFilter.new.roles
- end
-
- def env_filter
- EnvFilter.new.roles
- end
-
- class ConfigurationFilter
-
- def roles
- if filter
- Array(filter.fetch(:roles, [])).map(&:to_sym)
- else
- []
- end
- end
-
- def config
- Configuration.env
- end
-
- def filter
- config.fetch(:filter) || config.fetch(:select)
- end
- end
-
-
- class EnvFilter
-
- def roles
- if filter
- filter.split(',').map(&:to_sym)
- else
- []
- end
- end
-
- def filter
- ENV['ROLES']
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/capistrano/configuration/servers.rb b/lib/capistrano/configuration/servers.rb
index 54f8d4a..7bc2fdd 100644
--- a/lib/capistrano/configuration/servers.rb
+++ b/lib/capistrano/configuration/servers.rb
@@ -1,26 +1,53 @@
require 'set'
-require_relative 'servers/role_filter'
-require_relative 'servers/host_filter'
+require 'capistrano/configuration'
+require 'capistrano/configuration/filter'
+
module Capistrano
class Configuration
class Servers
include Enumerable
def add_host(host, properties={})
- servers.add server(host).with(properties)
+ new_host = Server[host]
+ if server = servers.find { |s| s.matches? new_host }
+ server.user = new_host.user if new_host.user
+ server.port = new_host.port if new_host.port
+ server.with(properties)
+ else
+ servers << new_host.with(properties)
+ end
end
def add_role(role, hosts, options={})
- Array(hosts).each { |host| add_host(host, options.merge(roles: role)) }
+ options_deepcopy = Marshal.dump(options.merge(roles: role))
+ Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) }
end
def roles_for(names)
options = extract_options(names)
- fetch_roles(names, options)
+ s = Filter.new(:role, names).filter(servers)
+ s.select { |server| server.select?(options) }
+ end
+
+ def role_properties_for(rolenames)
+ roles = rolenames.to_set
+ rps = Set.new unless block_given?
+ roles_for(rolenames).each do |host|
+ host.roles.intersection(roles).each do |role|
+ [host.properties.fetch(role)].flatten(1).each do |props|
+ if block_given?
+ yield host, role, props
+ else
+ rps << (props || {}).merge( role: role, hostname: host.hostname )
+ end
+ end
+ end
+ end
+ block_given? ? nil: rps
end
def fetch_primary(role)
- hosts = fetch(role)
+ hosts = roles_for([role])
hosts.find(&:primary) || hosts.first
end
@@ -30,33 +57,8 @@
private
- def server(host)
- servers.find { |server| server.matches? Server[host] } || Server[host]
- end
-
- def fetch(role)
- servers.find_all { |server| server.has_role? role}
- end
-
- def fetch_roles(required, options)
- filter_roles = RoleFilter.for(required, available_roles)
- HostFilter.for(select(servers_with_roles(filter_roles), options))
- end
-
- def servers_with_roles(roles)
- roles.flat_map { |role| fetch role }.uniq
- end
-
- def select(servers, options)
- servers.select { |server| server.select?(options) }
- end
-
- def available_roles
- servers.flat_map { |server| server.roles_array }.uniq
- end
-
def servers
- @servers ||= Set.new
+ @servers ||= []
end
def extract_options(array)
diff --git a/lib/capistrano/configuration.rb b/lib/capistrano/configuration.rb
index a5f978e..e025869 100644
--- a/lib/capistrano/configuration.rb
+++ b/lib/capistrano/configuration.rb
@@ -1,27 +1,34 @@
+require_relative 'configuration/filter'
require_relative 'configuration/question'
+require_relative 'configuration/server'
require_relative 'configuration/servers'
-require_relative 'configuration/server'
module Capistrano
class Configuration
- class << self
- def env
- @env ||= new
- end
-
- def reset!
- @env = new
- end
+ def initialize(config = nil)
+ @config ||= config
end
- def ask(key, default=nil)
- question = Question.new(self, key, default)
+ def self.env
+ @env ||= new
+ end
+
+ def self.reset!
+ @env = new
+ end
+
+ def ask(key, default=nil, options={})
+ question = Question.new(key, default, options)
set(key, question)
end
def set(key, value)
config[key] = value
+ end
+
+ def set_if_empty(key, value)
+ config[key] = value unless config.has_key? key
end
def delete(key)
@@ -56,6 +63,10 @@
servers.roles_for(names)
end
+ def role_properties_for(names, &block)
+ servers.role_properties_for(names, &block)
+ end
+
def primary(role)
servers.fetch_primary(role)
end
@@ -75,7 +86,7 @@
sshkit.backend.configure do |backend|
backend.pty = fetch(:pty)
backend.connection_timeout = fetch(:connection_timeout)
- backend.ssh_options = fetch(:ssh_options) if fetch(:ssh_options)
+ backend.ssh_options = (backend.ssh_options || {}).merge(fetch(:ssh_options,{}))
end
end
end
@@ -84,7 +95,29 @@
@timestamp ||= Time.now.utc
end
+ def setup_filters
+ @filters = cmdline_filters.clone
+ @filters << Filter.new(:role, ENV['ROLES']) if ENV['ROLES']
+ @filters << Filter.new(:host, ENV['HOSTS']) if ENV['HOSTS']
+ fh = fetch_for(:filter,{})
+ @filters << Filter.new(:host, fh[:host]) if fh[:host]
+ @filters << Filter.new(:role, fh[:role]) if fh[:role]
+ end
+
+ def add_cmdline_filter(type, values)
+ cmdline_filters << Filter.new(type, values)
+ end
+
+ def filter list
+ setup_filters if @filters.nil?
+ @filters.reduce(list) { |l,f| f.filter l }
+ end
+
private
+
+ def cmdline_filters
+ @cmdline_filters ||= []
+ end
def servers
@servers ||= Servers.new
diff --git a/lib/capistrano/defaults.rb b/lib/capistrano/defaults.rb
index 2a16dd6..92dff7c 100644
--- a/lib/capistrano/defaults.rb
+++ b/lib/capistrano/defaults.rb
@@ -1,12 +1,14 @@
-set :scm, :git
-set :branch, :master
-set :deploy_to, -> { "/var/www/#{fetch(:application)}" }
-set :tmp_dir, "/tmp"
+set_if_empty :scm, :git
+set_if_empty :branch, :master
+set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" }
+set_if_empty :tmp_dir, "/tmp"
-set :default_env, {}
-set :keep_releases, 5
+set_if_empty :default_env, {}
+set_if_empty :keep_releases, 5
-set :format, :pretty
-set :log_level, :debug
+set_if_empty :format, :pretty
+set_if_empty :log_level, :debug
-set :pty, false
+set_if_empty :pty, false
+
+set_if_empty :local_user, -> { Etc.getlogin }
diff --git a/lib/capistrano/dsl/env.rb b/lib/capistrano/dsl/env.rb
index 6728bad..c12a408 100644
--- a/lib/capistrano/dsl/env.rb
+++ b/lib/capistrano/dsl/env.rb
@@ -23,12 +23,16 @@
env.set(key, value)
end
+ def set_if_empty(key, value)
+ env.set_if_empty(key, value)
+ end
+
def delete(key)
env.delete(key)
end
- def ask(key, value)
- env.ask(key, value)
+ def ask(key, value, options={})
+ env.ask(key, value, options)
end
def role(name, servers, options={})
@@ -43,8 +47,16 @@
env.roles_for(names.flatten)
end
+ def role_properties(*names, &block)
+ env.role_properties_for(names, &block)
+ end
+
def release_roles(*names)
- names << { exclude: :no_release } unless names.last.is_a? Hash
+ if names.last.is_a? Hash
+ names.last.merge!({ :exclude => :no_release })
+ else
+ names << { exclude: :no_release }
+ end
roles(*names)
end
diff --git a/lib/capistrano/dsl/paths.rb b/lib/capistrano/dsl/paths.rb
index a4d4f09..7c8bde4 100644
--- a/lib/capistrano/dsl/paths.rb
+++ b/lib/capistrano/dsl/paths.rb
@@ -54,7 +54,7 @@
end
def repo_path
- deploy_path.join('repo')
+ Pathname.new(fetch(:repo_path, ->(){deploy_path.join('repo')}))
end
def shared_path
diff --git a/lib/capistrano/dsl/task_enhancements.rb b/lib/capistrano/dsl/task_enhancements.rb
index c35caab..940785b 100644
--- a/lib/capistrano/dsl/task_enhancements.rb
+++ b/lib/capistrano/dsl/task_enhancements.rb
@@ -1,3 +1,5 @@
+require 'capistrano/upload_task'
+
module Capistrano
module TaskEnhancements
def before(task, prerequisite, *args, &block)
@@ -19,12 +21,12 @@
end
def define_remote_file_task(task, target_roles)
- Rake::Task.define_task(task) do |t|
+ Capistrano::UploadTask.define_task(task) do |t|
prerequisite_file = t.prerequisites.first
file = shared_path.join(t.name)
on roles(target_roles) do
- unless test "[ -f #{file} ]"
+ unless test "[ -f #{file.to_s.shellescape} ]"
info "Uploading #{prerequisite_file} to #{file}"
upload! File.open(prerequisite_file), file
end
@@ -51,7 +53,7 @@
end
def exit_deploy_because_of_exception(ex)
- warn t(:deploy_failed, ex: ex.inspect)
+ warn t(:deploy_failed, ex: ex.message)
invoke 'deploy:failed'
exit(false)
end
diff --git a/lib/capistrano/dsl.rb b/lib/capistrano/dsl.rb
index 9e12097..431967e 100644
--- a/lib/capistrano/dsl.rb
+++ b/lib/capistrano/dsl.rb
@@ -3,6 +3,7 @@
require 'capistrano/dsl/paths'
require 'capistrano/dsl/stages'
require 'capistrano/dsl/env'
+require 'capistrano/configuration/filter'
module Capistrano
module DSL
@@ -33,7 +34,7 @@
branch: fetch(:branch),
user: local_user,
sha: fetch(:current_revision),
- release: release_timestamp)
+ release: fetch(:release_timestamp))
)
end
@@ -42,12 +43,22 @@
end
def local_user
- Etc.getlogin
+ fetch(:local_user)
end
def lock(locked_version)
VersionValidator.new(locked_version).verify
end
+
+ def on(hosts, options={}, &block)
+ subset_copy = Marshal.dump(Configuration.env.filter(hosts))
+ SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
+ end
+
+ def run_locally(&block)
+ SSHKit::Backend::Local.new(&block).run
+ end
+
end
end
self.extend Capistrano::DSL
diff --git a/lib/capistrano/git.rb b/lib/capistrano/git.rb
index 619e2aa..3a6727a 100644
--- a/lib/capistrano/git.rb
+++ b/lib/capistrano/git.rb
@@ -18,7 +18,7 @@
end
def check
- test! :git, :'ls-remote -h', repo_url
+ git :'ls-remote --heads', repo_url
end
def clone
@@ -30,11 +30,17 @@
end
def release
- git :archive, fetch(:branch), '| tar -x -C', release_path
+ if tree = fetch(:repo_tree)
+ tree = tree.slice %r#^/?(.*?)/?$#, 1
+ components = tree.split('/').size
+ git :archive, fetch(:branch), tree, "| tar -x --strip-components #{components} -f - -C", release_path
+ else
+ git :archive, fetch(:branch), '| tar -x -f - -C', release_path
+ end
end
def fetch_revision
- context.capture(:git, "rev-parse --short #{fetch(:branch)}")
+ context.capture(:git, "rev-list --max-count=1 --abbrev-commit #{fetch(:branch)}")
end
end
end
diff --git a/lib/capistrano/hg.rb b/lib/capistrano/hg.rb
index 2c5bca3..e7f0290 100644
--- a/lib/capistrano/hg.rb
+++ b/lib/capistrano/hg.rb
@@ -27,7 +27,13 @@
end
def release
- hg "archive", release_path, "--rev", fetch(:branch)
+ if tree = fetch(:repo_tree)
+ tree = tree.slice %r#^/?(.*?)/?$#, 1
+ components = tree.split('/').size
+ hg "archive --type tgz -p . -I", tree, "--rev", fetch(:branch), "| tar -x --strip-components #{components} -f - -C", release_path
+ else
+ hg "archive", release_path, "--rev", fetch(:branch)
+ end
end
def fetch_revision
diff --git a/lib/capistrano/svn.rb b/lib/capistrano/svn.rb
index 72cc994..f3f64b8 100644
--- a/lib/capistrano/svn.rb
+++ b/lib/capistrano/svn.rb
@@ -28,11 +28,11 @@
end
def release
- svn :export, '.', release_path
+ svn :export, '--force', '.', release_path
end
def fetch_revision
- context.capture(:svn, "log -r HEAD -q | tail -n 2 | head -n 1 | sed s/\ \|.*/''/")
+ context.capture(:svnversion, repo_path)
end
end
end
diff --git a/lib/capistrano/tasks/deploy.rake b/lib/capistrano/tasks/deploy.rake
index 40b36ab..ef1af89 100644
--- a/lib/capistrano/tasks/deploy.rake
+++ b/lib/capistrano/tasks/deploy.rake
@@ -44,7 +44,7 @@
desc 'Check shared and release directories exist'
task :directories do
on release_roles :all do
- execute :mkdir, '-pv', shared_path, releases_path
+ execute :mkdir, '-p', shared_path, releases_path
end
end
@@ -52,7 +52,7 @@
task :linked_dirs do
next unless any? :linked_dirs
on release_roles :all do
- execute :mkdir, '-pv', linked_dirs(shared_path)
+ execute :mkdir, '-p', linked_dirs(shared_path)
end
end
@@ -60,7 +60,7 @@
task :make_linked_dirs do
next unless any? :linked_files
on release_roles :all do |host|
- execute :mkdir, '-pv', linked_file_dirs(shared_path)
+ execute :mkdir, '-p', linked_file_dirs(shared_path)
end
end
@@ -82,8 +82,9 @@
desc 'Symlink release to current'
task :release do
on release_roles :all do
- execute :rm, '-rf', current_path
- execute :ln, '-s', release_path, current_path
+ tmp_current_path = release_path.parent.join(current_path.basename)
+ execute :ln, '-s', release_path, tmp_current_path
+ execute :mv, tmp_current_path, current_path.parent
end
end
@@ -97,7 +98,7 @@
task :linked_dirs do
next unless any? :linked_dirs
on release_roles :all do
- execute :mkdir, '-pv', linked_dir_parents(release_path)
+ execute :mkdir, '-p', linked_dir_parents(release_path)
fetch(:linked_dirs).each do |dir|
target = release_path.join(dir)
@@ -116,7 +117,7 @@
task :linked_files do
next unless any? :linked_files
on release_roles :all do
- execute :mkdir, '-pv', linked_file_dirs(release_path)
+ execute :mkdir, '-p', linked_file_dirs(release_path)
fetch(:linked_files).each do |file|
target = release_path.join(file)
@@ -135,7 +136,7 @@
desc 'Clean up old releases'
task :cleanup do
on release_roles :all do |host|
- releases = capture(:ls, '-x', releases_path).split
+ releases = capture(:ls, '-xtr', releases_path).split
if releases.count >= fetch(:keep_releases)
info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count)
directories = (releases - releases.last(fetch(:keep_releases)))
@@ -154,7 +155,7 @@
desc 'Remove and archive rolled-back release.'
task :cleanup_rollback do
on release_roles(:all) do
- last_release = capture(:ls, '-xr', releases_path).split.first
+ last_release = capture(:ls, '-xt', releases_path).split.first
last_release_path = releases_path.join(last_release)
if test "[ `readlink #{current_path}` != #{last_release_path} ]"
execute :tar, '-czf',
@@ -189,7 +190,7 @@
task :rollback_release_path do
on release_roles(:all) do
- releases = capture(:ls, '-xr', releases_path).split
+ releases = capture(:ls, '-xt', releases_path).split
if releases.count < 2
error t(:cannot_rollback)
exit 1
diff --git a/lib/capistrano/tasks/git.rake b/lib/capistrano/tasks/git.rake
index 3a43373..3af4d07 100644
--- a/lib/capistrano/tasks/git.rake
+++ b/lib/capistrano/tasks/git.rake
@@ -25,7 +25,7 @@
fetch(:branch)
on release_roles :all do
with fetch(:git_environmental_variables) do
- exit 1 unless strategy.check
+ strategy.check
end
end
end
diff --git a/lib/capistrano/tasks/install.rake b/lib/capistrano/tasks/install.rake
index 59215b7..6e3818e 100644
--- a/lib/capistrano/tasks/install.rake
+++ b/lib/capistrano/tasks/install.rake
@@ -14,25 +14,28 @@
mkdir_p deploy_dir
- template = File.read(deploy_rb)
- file = config_dir.join('deploy.rb')
- File.open(file, 'w+') do |f|
- f.write(ERB.new(template).result(binding))
- puts I18n.t(:written_file, scope: :capistrano, file: file)
- end
+ entries = [{template: deploy_rb, file: config_dir.join('deploy.rb')}]
+ entries += envs.split(',').map { |stage| {template: stage_rb, file: deploy_dir.join("#{stage}.rb")} }
- template = File.read(stage_rb)
- envs.split(',').each do |stage|
- file = deploy_dir.join("#{stage}.rb")
- File.open(file, 'w+') do |f|
- f.write(ERB.new(template).result(binding))
- puts I18n.t(:written_file, scope: :capistrano, file: file)
+ entries.each do |entry|
+ if File.exists?(entry[:file])
+ warn "[skip] #{entry[:file]} already exists"
+ else
+ File.open(entry[:file], 'w+') do |f|
+ f.write(ERB.new(File.read(entry[:template])).result(binding))
+ puts I18n.t(:written_file, scope: :capistrano, file: entry[:file])
+ end
end
end
mkdir_p tasks_dir
- FileUtils.cp(capfile, 'Capfile')
+ if File.exists?('Capfile')
+ warn "[skip] Capfile already exists"
+ else
+ FileUtils.cp(capfile, 'Capfile')
+ puts I18n.t(:written_file, scope: :capistrano, file: 'Capfile')
+ end
puts I18n.t :capified, scope: :capistrano
diff --git a/lib/capistrano/templates/Capfile b/lib/capistrano/templates/Capfile
index 080238a..a0cf0b5 100644
--- a/lib/capistrano/templates/Capfile
+++ b/lib/capistrano/templates/Capfile
@@ -1,10 +1,10 @@
-# Load DSL and Setup Up Stages
+# Load DSL and set up stages
require 'capistrano/setup'
-# Includes default deployment tasks
+# Include default deployment tasks
require 'capistrano/deploy'
-# Includes tasks from other gems included in your Gemfile
+# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
@@ -13,6 +13,7 @@
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails
+# https://github.com/capistrano/passenger
#
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
@@ -20,6 +21,7 @@
# require 'capistrano/bundler'
# require 'capistrano/rails/assets'
# require 'capistrano/rails/migrations'
+# require 'capistrano/passenger'
-# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
+# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
diff --git a/lib/capistrano/templates/deploy.rb.erb b/lib/capistrano/templates/deploy.rb.erb
index 7b96491..49a3772 100644
--- a/lib/capistrano/templates/deploy.rb.erb
+++ b/lib/capistrano/templates/deploy.rb.erb
@@ -1,14 +1,14 @@
-# config valid only for Capistrano 3.1
+# config valid only for current version of Capistrano
lock '<%= Capistrano::VERSION %>'
set :application, 'my_app_name'
set :repo_url, 'git@example.com:me/my_repo.git'
# Default branch is :master
-# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call
+# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
-# Default deploy_to directory is /var/www/my_app
-# set :deploy_to, '/var/www/my_app'
+# Default deploy_to directory is /var/www/my_app_name
+# set :deploy_to, '/var/www/my_app_name'
# Default value for :scm is :git
# set :scm, :git
@@ -23,10 +23,10 @@
# set :pty, true
# Default value for :linked_files is []
-# set :linked_files, %w{config/database.yml}
+# set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
# Default value for linked_dirs is []
-# set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
+# set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }
@@ -35,16 +35,6 @@
# set :keep_releases, 5
namespace :deploy do
-
- desc 'Restart application'
- task :restart do
- on roles(:app), in: :sequence, wait: 5 do
- # Your restart mechanism here, for example:
- # execute :touch, release_path.join('tmp/restart.txt')
- end
- end
-
- after :publishing, :restart
after :restart, :clear_cache do
on roles(:web), in: :groups, limit: 3, wait: 10 do
diff --git a/lib/capistrano/templates/stage.rb.erb b/lib/capistrano/templates/stage.rb.erb
index e664a6c..4fc06fa 100644
--- a/lib/capistrano/templates/stage.rb.erb
+++ b/lib/capistrano/templates/stage.rb.erb
@@ -1,27 +1,43 @@
-# Simple Role Syntax
-# ==================
-# Supports bulk-adding hosts to roles, the primary server in each group
-# is considered to be the first unless any hosts have the primary
-# property set. Don't declare `role :all`, it's a meta role.
+# server-based syntax
+# ======================
+# Defines a single server with a list of roles and multiple properties.
+# You can define all roles on a single server, or split them:
-role :app, %w{deploy@example.com}
-role :web, %w{deploy@example.com}
-role :db, %w{deploy@example.com}
+# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
+# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
+# server 'db.example.com', user: 'deploy', roles: %w{db}
-# Extended Server Syntax
-# ======================
-# This can be used to drop a more detailed server definition into the
-# server list. The second argument is a, or duck-types, Hash and is
-# used to set extended properties on the server.
-server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value
+# role-based syntax
+# ==================
+
+# Defines a role with one or multiple servers. The primary server in each
+# group is considered to be the first unless any hosts have the primary
+# property set. Specify the username and a domain or IP for the server.
+# Don't use `:all`, it's a meta role.
+
+# role :app, %w{deploy@example.com}, my_property: :my_value
+# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
+# role :db, %w{deploy@example.com}
+
+
+
+# Configuration
+# =============
+# You can set any configuration variable like in config/deploy.rb
+# These variables are then only loaded and set in this stage.
+# For available Capistrano configuration variables see the documentation page.
+# http://capistranorb.com/documentation/getting-started/configuration/
+# Feel free to add new variables to customise your setup.
+
# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
-# limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start).
+# limited set of options, consult the Net::SSH documentation.
+# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
@@ -31,7 +47,7 @@
# auth_methods: %w(password)
# }
#
-# And/or per server (overrides global)
+# The server-based syntax can be used to override options:
# ------------------------------------
# server 'example.com',
# user: 'user_name',
diff --git a/lib/capistrano/upload_task.rb b/lib/capistrano/upload_task.rb
new file mode 100644
index 0000000..c38b710
--- /dev/null
+++ b/lib/capistrano/upload_task.rb
@@ -0,0 +1,9 @@
+require 'rake/file_creation_task'
+
+module Capistrano
+ class UploadTask < Rake::FileCreationTask
+ def needed?
+ true # always needed because we can't check remote hosts
+ end
+ end
+end
diff --git a/lib/capistrano/version.rb b/lib/capistrano/version.rb
index 41c4671..0d9779d 100644
--- a/lib/capistrano/version.rb
+++ b/lib/capistrano/version.rb
@@ -1,3 +1,3 @@
module Capistrano
- VERSION = "3.2.1"
+ VERSION = "3.4.0"
end
diff --git a/metadata.yml b/metadata.yml
index 84f2414..c33475c 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,7 +1,7 @@
--- !ruby/object:Gem::Specification
name: capistrano
version: !ruby/object:Gem::Version
- version: 3.2.1
+ version: 3.4.0
platform: ruby
authors:
- Tom Clements
@@ -9,76 +9,76 @@
autorequire:
bindir: bin
cert_chain: []
-date: 2014-04-22 00:00:00.000000000 Z
+date: 2015-03-02 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: sshkit
requirement: !ruby/object:Gem::Requirement
requirements:
- - - ~>
+ - - "~>"
- !ruby/object:Gem::Version
version: '1.3'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - ~>
+ - - "~>"
- !ruby/object:Gem::Version
version: '1.3'
- !ruby/object:Gem::Dependency
name: rake
requirement: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: 10.0.0
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: 10.0.0
- !ruby/object:Gem::Dependency
name: i18n
requirement: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rspec
requirement: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: mocha
requirement: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
description: Capistrano is a utility and framework for executing commands in parallel
@@ -92,8 +92,8 @@
extensions: []
extra_rdoc_files: []
files:
-- .gitignore
-- .travis.yml
+- ".gitignore"
+- ".travis.yml"
- CHANGELOG.md
- CONTRIBUTING.md
- Gemfile
@@ -108,21 +108,22 @@
- features/deploy_failure.feature
- features/installation.feature
- features/remote_file_task.feature
+- features/sshconnect.feature
- features/step_definitions/assertions.rb
- features/step_definitions/cap_commands.rb
- features/step_definitions/setup.rb
- features/support/env.rb
- features/support/remote_command_helpers.rb
+- features/support/vagrant_helpers.rb
- lib/Capfile
- lib/capistrano.rb
- lib/capistrano/all.rb
- lib/capistrano/application.rb
- lib/capistrano/configuration.rb
+- lib/capistrano/configuration/filter.rb
- lib/capistrano/configuration/question.rb
- lib/capistrano/configuration/server.rb
- lib/capistrano/configuration/servers.rb
-- lib/capistrano/configuration/servers/host_filter.rb
-- lib/capistrano/configuration/servers/role_filter.rb
- lib/capistrano/console.rb
- lib/capistrano/defaults.rb
- lib/capistrano/deploy.rb
@@ -150,23 +151,25 @@
- lib/capistrano/templates/Capfile
- lib/capistrano/templates/deploy.rb.erb
- lib/capistrano/templates/stage.rb.erb
+- lib/capistrano/upload_task.rb
- lib/capistrano/version.rb
- lib/capistrano/version_validator.rb
- spec/integration/dsl_spec.rb
- spec/integration_spec_helper.rb
- spec/lib/capistrano/application_spec.rb
+- spec/lib/capistrano/configuration/filter_spec.rb
- spec/lib/capistrano/configuration/question_spec.rb
- spec/lib/capistrano/configuration/server_spec.rb
-- spec/lib/capistrano/configuration/servers/host_filter_spec.rb
-- spec/lib/capistrano/configuration/servers/role_filter_spec.rb
- spec/lib/capistrano/configuration/servers_spec.rb
- spec/lib/capistrano/configuration_spec.rb
- spec/lib/capistrano/dsl/paths_spec.rb
+- spec/lib/capistrano/dsl/task_enhancements_spec.rb
- spec/lib/capistrano/dsl_spec.rb
- spec/lib/capistrano/git_spec.rb
- spec/lib/capistrano/hg_spec.rb
- spec/lib/capistrano/scm_spec.rb
- spec/lib/capistrano/svn_spec.rb
+- spec/lib/capistrano/upload_task_spec.rb
- spec/lib/capistrano/version_validator_spec.rb
- spec/lib/capistrano_spec.rb
- spec/spec_helper.rb
@@ -176,31 +179,34 @@
- spec/support/tasks/database.rake
- spec/support/tasks/fail.rake
- spec/support/tasks/failed.rake
+- spec/support/tasks/root.rake
- spec/support/test_app.rb
homepage: http://capistranorb.com/
licenses:
- MIT
metadata: {}
post_install_message: |
- 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
+ Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr
If you're upgrading Capistrano from 2.x, we recommend to read the upgrade guide: http://goo.gl/4536kB
+
+ 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.
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
- version: '0'
+ version: 1.9.3
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
-rubygems_version: 2.0.3
+rubygems_version: 2.4.3
signing_key:
specification_version: 4
summary: Capistrano - Welcome to easy deployment with Ruby over SSH
@@ -210,26 +216,29 @@
- features/deploy_failure.feature
- features/installation.feature
- features/remote_file_task.feature
+- features/sshconnect.feature
- features/step_definitions/assertions.rb
- features/step_definitions/cap_commands.rb
- features/step_definitions/setup.rb
- features/support/env.rb
- features/support/remote_command_helpers.rb
+- features/support/vagrant_helpers.rb
- spec/integration/dsl_spec.rb
- spec/integration_spec_helper.rb
- spec/lib/capistrano/application_spec.rb
+- spec/lib/capistrano/configuration/filter_spec.rb
- spec/lib/capistrano/configuration/question_spec.rb
- spec/lib/capistrano/configuration/server_spec.rb
-- spec/lib/capistrano/configuration/servers/host_filter_spec.rb
-- spec/lib/capistrano/configuration/servers/role_filter_spec.rb
- spec/lib/capistrano/configuration/servers_spec.rb
- spec/lib/capistrano/configuration_spec.rb
- spec/lib/capistrano/dsl/paths_spec.rb
+- spec/lib/capistrano/dsl/task_enhancements_spec.rb
- spec/lib/capistrano/dsl_spec.rb
- spec/lib/capistrano/git_spec.rb
- spec/lib/capistrano/hg_spec.rb
- spec/lib/capistrano/scm_spec.rb
- spec/lib/capistrano/svn_spec.rb
+- spec/lib/capistrano/upload_task_spec.rb
- spec/lib/capistrano/version_validator_spec.rb
- spec/lib/capistrano_spec.rb
- spec/spec_helper.rb
@@ -239,5 +248,5 @@
- spec/support/tasks/database.rake
- spec/support/tasks/fail.rake
- spec/support/tasks/failed.rake
+- spec/support/tasks/root.rake
- spec/support/test_app.rb
-has_rdoc:
diff --git a/spec/integration/dsl_spec.rb b/spec/integration/dsl_spec.rb
index ee9dd62..49653a3 100644
--- a/spec/integration/dsl_spec.rb
+++ b/spec/integration/dsl_spec.rb
@@ -15,7 +15,7 @@
dsl.server 'example2.com', roles: %w{web}
dsl.server 'example3.com', roles: %w{app web}, active: true
dsl.server 'example4.com', roles: %w{app}, primary: true
- dsl.server 'example5.com', roles: %w{db}, no_release: true
+ dsl.server 'example5.com', roles: %w{db}, no_release: true, active:true
end
describe 'fetching all servers' do
@@ -36,10 +36,10 @@
end
end
- context 'with filter options' do
+ context 'with property filter options' do
subject { dsl.release_roles(:all, filter: :active) }
- it 'returns all release servers that match the filter' do
+ it 'returns all release servers that match the property filter' do
expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
end
end
@@ -92,11 +92,43 @@
end
end
- context 'when the attribute `primary` is explicity set' do
+ context 'when the attribute `primary` is explicitly set' do
subject { dsl.primary(:app) }
it 'returns the servers' do
expect(subject.hostname).to eq 'example4.com'
end
+ end
+ end
+
+ describe 'setting an internal host filter' do
+ subject { dsl.roles(:app) }
+ it 'is ignored' do
+ dsl.set :filter, { host: 'example3.com' }
+ expect(subject.map(&:hostname)).to eq(['example3.com', 'example4.com'])
+ end
+ end
+
+ describe 'setting an internal role filter' do
+ subject { dsl.roles(:app) }
+ it 'ignores it' do
+ dsl.set :filter, { role: :web }
+ expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
+ end
+ end
+
+ describe 'setting an internal host and role filter' do
+ subject { dsl.roles(:app) }
+ it 'ignores it' do
+ dsl.set :filter, { role: :web, host: 'example1.com' }
+ expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
+ end
+ end
+
+ describe 'setting an internal regexp host filter' do
+ subject { dsl.roles(:all) }
+ it 'is ignored' do
+ dsl.set :filter, { host: /1/ }
+ expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
end
end
@@ -209,23 +241,43 @@
end
describe 'fetching all servers' do
- subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" } }
-
- it 'creates a server instance for each unique user@host:port combination' do
- expect(subject).to eq %w{db@example1.com:1234 root@example1.com:1234 @example1.com:5678 deployer@example1.com:1234}
+ it 'creates one server per hostname, ignoring user and port combinations' do
+ expect(dsl.roles(:all).size).to eq(1)
end
end
describe 'fetching servers for a role' do
it 'roles defined using the `server` syntax are included' do
- expect(dsl.roles(:web)).to have(2).items
+ as = dsl.roles(:web).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
+ expect(as.size).to eq(1)
+ expect(as[0]).to eq("deployer@example1.com:5678")
end
it 'roles defined using the `role` syntax are included' do
- expect(dsl.roles(:app)).to have(2).items
- end
- end
-
+ as = dsl.roles(:app).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
+ expect(as.size).to eq(1)
+ expect(as[0]).to eq("deployer@example1.com:5678")
+ end
+ end
+
+ end
+
+ describe 'when setting user and port' do
+ subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first }
+
+ describe "using the :user property" do
+ it "takes precedence over in the host string" do
+ dsl.server 'db@example1.com:1234', roles: %w{db}, active: true, user: 'brian'
+ expect(subject).to eq("brian@example1.com:1234")
+ end
+ end
+
+ describe "using the :port property" do
+ it "takes precedence over in the host string" do
+ dsl.server 'db@example1.com:9090', roles: %w{db}, active: true, port: 1234
+ expect(subject).to eq("db@example1.com:1234")
+ end
+ end
end
end
@@ -317,22 +369,22 @@
context 'variable is an non-empty array' do
let(:linked_files) { %w{1} }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'variable is an empty array' do
let(:linked_files) { [] }
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
context 'variable exists, is not an array' do
let(:linked_files) { stub }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'variable is nil' do
let(:linked_files) { nil }
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
end
@@ -368,7 +420,7 @@
end
it 'sets the backend pty' do
- expect(backend.pty).to be_true
+ expect(backend.pty).to be_truthy
end
it 'sets the backend connection timeout' do
@@ -383,109 +435,149 @@
end
- describe 'release path' do
-
- before do
- dsl.set(:deploy_to, '/var/www')
- end
-
- describe 'fetching release path' do
- subject { dsl.release_path }
-
- context 'where no release path has been set' do
+ describe 'local_user' do
+ before do
+ dsl.set :local_user, -> { Etc.getlogin }
+ end
+
+ describe 'fetching local_user' do
+ subject { dsl.local_user }
+
+ context 'where a local_user is not set' do
before do
- dsl.delete(:release_path)
- end
-
- it 'returns the `current_path` value' do
- expect(subject.to_s).to eq '/var/www/current'
- end
- end
-
- context 'where the release path has been set' do
+ Etc.expects(:getlogin).returns('login')
+ end
+
+ it 'returns the login name' do
+ expect(subject.to_s).to eq 'login'
+ end
+ end
+
+ context 'where a local_user is set' do
before do
- dsl.set(:release_path, '/var/www/release_path')
- end
-
- it 'returns the set `release_path` value' do
- expect(subject.to_s).to eq '/var/www/release_path'
- end
- end
- end
-
- describe 'setting release path' do
- let(:now) { Time.parse("Oct 21 16:29:00 2015") }
- subject { dsl.release_path }
-
- context 'without a timestamp' do
- before do
- dsl.env.expects(:timestamp).returns(now)
- dsl.set_release_path
- end
-
- it 'returns the release path with the current env timestamp' do
- expect(subject.to_s).to eq '/var/www/releases/20151021162900'
- end
- end
-
- context 'with a timestamp' do
- before do
- dsl.set_release_path('timestamp')
- end
-
- it 'returns the release path with the timestamp' do
- expect(subject.to_s).to eq '/var/www/releases/timestamp'
- end
- end
- end
-
- describe 'setting deploy configuration path' do
- subject { dsl.deploy_config_path.to_s }
-
- context 'where no config path is set' do
- before do
- dsl.delete(:deploy_config_path)
- end
-
- it 'returns "config/deploy.rb"' do
- expect(subject).to eq 'config/deploy.rb'
- end
- end
-
- context 'where a custom path is set' do
- before do
- dsl.set(:deploy_config_path, 'my/custom/path.rb')
- end
-
- it 'returns the custom path' do
- expect(subject).to eq 'my/custom/path.rb'
- end
- end
- end
-
- describe 'setting stage configuration path' do
- subject { dsl.stage_config_path.to_s }
-
- context 'where no config path is set' do
-
- before do
- dsl.delete(:stage_config_path)
- end
-
- it 'returns "config/deploy"' do
- expect(subject).to eq 'config/deploy'
- end
- end
-
- context 'where a custom path is set' do
- before do
- dsl.set(:stage_config_path, 'my/custom/path')
- end
-
- it 'returns the custom path' do
- expect(subject).to eq 'my/custom/path'
- end
- end
- end
- end
+ dsl.set(:local_user, -> { 'custom login' })
+ end
+
+ it 'returns the custom name' do
+ expect(subject.to_s).to eq 'custom login'
+ end
+ end
+ end
+ end
+
+ describe 'on()' do
+
+ before do
+ dsl.server 'example1.com', roles: %w{web}, active: true
+ dsl.server 'example2.com', roles: %w{web}
+ dsl.server 'example3.com', roles: %w{app web}, active: true
+ dsl.server 'example4.com', roles: %w{app}, primary: true
+ dsl.server 'example5.com', roles: %w{db}, no_release: true
+ @coordinator = mock('coordinator')
+ @coordinator.expects(:each).returns(nil)
+ ENV.delete 'ROLES'
+ ENV.delete 'HOSTS'
+
+ end
+
+ it 'filters by role from the :filter variable' do
+ hosts = dsl.roles(:web)
+ all = dsl.roles(:all)
+ SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+ dsl.set :filter, { role: 'web' }
+ dsl.on(all)
+ end
+
+ it 'filters by host and role from the :filter variable' do
+ all = dsl.roles(:all)
+ SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+ dsl.set :filter, { role: 'db', host: 'example3.com' }
+ dsl.on(all)
+ end
+
+ it 'filters from ENV[ROLES]' do
+ hosts = dsl.roles(:db)
+ all = dsl.roles(:all)
+ SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+ ENV['ROLES'] = 'db'
+ dsl.on(all)
+ end
+
+ it 'filters from ENV[HOSTS]' do
+ hosts = dsl.roles(:db)
+ all = dsl.roles(:all)
+ SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+ ENV['HOSTS'] = 'example5.com'
+ dsl.on(all)
+ end
+
+ it 'filters by ENV[HOSTS] && ENV[ROLES]' do
+ all = dsl.roles(:all)
+ SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+ ENV['HOSTS'] = 'example5.com'
+ ENV['ROLES'] = 'web'
+ dsl.on(all)
+ end
+
+ end
+
+ describe 'role_properties()' do
+
+ before do
+ dsl.role :redis, %w[example1.com example2.com], redis: { port: 6379, type: :slave }
+ dsl.server 'example1.com', roles: %w{web}, active: true, web: { port: 80 }
+ dsl.server 'example2.com', roles: %w{web redis}, web: { port: 81 }, redis: { type: :master }
+ dsl.server 'example3.com', roles: %w{app}, primary: true
+ end
+
+ it 'retrieves properties for a single role as a set' do
+ rps = dsl.role_properties(:app)
+ expect(rps).to eq(Set[{ hostname: 'example3.com', role: :app}])
+ end
+
+ it 'retrieves properties for multiple roles as a set' do
+ rps = dsl.role_properties(:app, :web)
+ expect(rps).to eq(Set[{ hostname: 'example3.com', role: :app},{ hostname: 'example1.com', role: :web, port: 80},{ hostname: 'example2.com', role: :web, port: 81}])
+ end
+
+ it 'yields the properties for a single role' do
+ recipient = mock('recipient')
+ recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
+ recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
+ dsl.role_properties(:redis) do |host, role, props|
+ recipient.doit(host, role, props)
+ end
+ end
+
+ it 'yields the properties for multiple roles' do
+ recipient = mock('recipient')
+ recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
+ recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
+ recipient.expects(:doit).with('example3.com', :app, nil)
+ dsl.role_properties(:redis, :app) do |host, role, props|
+ recipient.doit(host, role, props)
+ end
+ end
+
+ it 'yields the merged properties for multiple roles' do
+ recipient = mock('recipient')
+ recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
+ recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
+ recipient.expects(:doit).with('example1.com', :web, { port: 80 })
+ recipient.expects(:doit).with('example2.com', :web, { port: 81 })
+ dsl.role_properties(:redis, :web) do |host, role, props|
+ recipient.doit(host, role, props)
+ end
+ end
+
+ it 'honours a property filter before yielding' do
+ recipient = mock('recipient')
+ recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
+ recipient.expects(:doit).with('example1.com', :web, { port: 80 })
+ dsl.role_properties(:redis, :web, select: :active) do |host, role, props|
+ recipient.doit(host, role, props)
+ end
+ end
+ end
+
end
diff --git a/spec/lib/capistrano/application_spec.rb b/spec/lib/capistrano/application_spec.rb
index 9c2ecfb..bb52765 100644
--- a/spec/lib/capistrano/application_spec.rb
+++ b/spec/lib/capistrano/application_spec.rb
@@ -6,21 +6,39 @@
it "provides a --format option which enables the choice of output formatting"
- it "identifies itself as cap and not rake" do
+ let(:help_output) do
out, _ = capture_io do
flags '--help', '-h'
end
- out.lines.first.should match(/cap \[-f rakefile\]/)
+ out
+ end
+
+ it "displays documentation URL as help banner" do
+ expect(help_output.lines.first).to match(/capistranorb.com/)
+ end
+
+ %w(quiet silent verbose).each do |switch|
+ it "doesn't include --#{switch} in help" do
+ expect(help_output).not_to match(/--#{switch}/)
+ end
end
it "overrides the rake method, but still prints the rake version" do
out, _ = capture_io do
flags '--version', '-V'
end
- out.should match(/\bCapistrano Version\b/)
- out.should match(/\b#{Capistrano::VERSION}\b/)
- out.should match(/\bRake Version\b/)
- out.should match(/\b#{RAKEVERSION}\b/)
+ expect(out).to match(/\bCapistrano Version\b/)
+ expect(out).to match(/\b#{Capistrano::VERSION}\b/)
+ expect(out).to match(/\bRake Version\b/)
+ expect(out).to match(/\b#{RAKEVERSION}\b/)
+ end
+
+ it "overrides the rake method, and sets the sshkit_backend to SSHKit::Backend::Printer" do
+ out, _ = capture_io do
+ flags '--dry-run', '-n'
+ end
+ sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend)
+ expect(sshkit_backend).to eq(SSHKit::Backend::Printer)
end
def flags(*sets)
diff --git a/spec/lib/capistrano/configuration/filter_spec.rb b/spec/lib/capistrano/configuration/filter_spec.rb
new file mode 100644
index 0000000..65ad3f7
--- /dev/null
+++ b/spec/lib/capistrano/configuration/filter_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+
+module Capistrano
+ class Configuration
+
+ describe Filter do
+ let(:available) { [ Server.new('server1').add_roles([:web,:db]),
+ Server.new('server2').add_role(:web),
+ Server.new('server3').add_role(:redis),
+ Server.new('server4').add_role(:db),
+ Server.new('server5').add_role(:stageweb) ] }
+
+ describe '#new' do
+ it "won't create an invalid type of filter" do
+ expect {
+ f = Filter.new(:zarg)
+ }.to raise_error RuntimeError
+ end
+
+ it 'creates an empty host filter' do
+ expect(Filter.new(:host).filter(available)).to be_empty
+ end
+
+ it 'creates a null host filter' do
+ expect(Filter.new(:host, :all).filter(available)).to eq(available)
+ end
+
+ it 'creates an empty role filter' do
+ expect(Filter.new(:role).filter(available)).to be_empty
+ end
+
+ it 'creates a null role filter' do
+ expect(Filter.new(:role, :all).filter(available)).to eq(available)
+ end
+
+ end
+
+ describe 'host filter' do
+ it 'works with a single server' do
+ set = Filter.new(:host, 'server1').filter(available.first)
+ expect(set.map(&:hostname)).to eq(%w{server1})
+ end
+ it 'returns all hosts matching a string' do
+ set = Filter.new(:host, 'server1').filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1})
+ end
+ it 'returns all hosts matching a comma-separated string' do
+ set = Filter.new(:host, 'server1,server3').filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1 server3})
+ end
+ it 'returns all hosts matching an array of strings' do
+ set = Filter.new(:host, %w{server1 server3}).filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1 server3})
+ end
+ it 'returns all hosts matching regexp' do
+ set = Filter.new(:host, 'server[13]$').filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1 server3})
+ end
+ it 'correctly identifies a regex with a comma in' do
+ set = Filter.new(:host, 'server\d{1,3}$').filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server4 server5})
+ end
+ end
+
+ describe 'role filter' do
+ it 'returns all hosts' do
+ set = Filter.new(:role, [:all]).filter(available)
+ expect(set.size).to eq(available.size)
+ expect(set.first.hostname).to eq('server1')
+ end
+ it 'returns hosts in a single string role' do
+ set = Filter.new(:role, 'web').filter(available)
+ expect(set.size).to eq(2)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2})
+ end
+ it 'returns hosts in a single role' do
+ set = Filter.new(:role, [:web]).filter(available)
+ expect(set.size).to eq(2)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2})
+ end
+ it 'returns hosts in multiple roles specified by a string' do
+ set = Filter.new(:role, 'web,db').filter(available)
+ expect(set.size).to eq(3)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
+ end
+ it 'returns hosts in multiple roles' do
+ set = Filter.new(:role, [:web, :db]).filter(available)
+ expect(set.size).to eq(3)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
+ end
+ it 'returns only hosts for explicit roles' do
+ set = Filter.new(:role, [:web]).filter(available)
+ expect(set.size).to eq(2)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2})
+ end
+ it 'returns hosts with regex role selection' do
+ set = Filter.new(:role, /red/).filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server3})
+ end
+ it 'returns hosts with regex role selection using a string' do
+ set = Filter.new(:role, '/red|web/').filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server5})
+ end
+ it 'returns hosts with combination of string role and regex' do
+ set = Filter.new(:role, 'db,/red/').filter(available)
+ expect(set.map(&:hostname)).to eq(%w{server1 server3 server4})
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/capistrano/configuration/question_spec.rb b/spec/lib/capistrano/configuration/question_spec.rb
index cf1220c..289d7f7 100644
--- a/spec/lib/capistrano/configuration/question_spec.rb
+++ b/spec/lib/capistrano/configuration/question_spec.rb
@@ -5,31 +5,38 @@
describe Question do
- let(:question) { Question.new(env, key, default) }
+ let(:question) { Question.new(key, default, options) }
+ let(:question_without_echo) { Question.new(key, default, echo: false) }
let(:default) { :default }
let(:key) { :branch }
- let(:env) { stub }
+ let(:options) { nil }
describe '.new' do
- it 'takes a key, default' do
+ it 'takes a key, default, options' do
question
end
end
describe '#call' do
- subject { question.call }
-
context 'value is entered' do
let(:branch) { 'branch' }
before do
$stdout.expects(:print).with('Please enter branch (default): ')
- $stdin.expects(:gets).returns(branch)
end
- it 'sets the value' do
- env.expects(:set).with(key, branch)
- question.call
+ it 'returns the echoed value' do
+ $stdin.expects(:gets).returns(branch)
+ $stdin.expects(:noecho).never
+
+ expect(question.call).to eq(branch)
+ end
+
+ it 'returns the value but does not echo it' do
+ $stdin.expects(:noecho).returns(branch)
+ $stdout.expects(:print).with("\n")
+
+ expect(question_without_echo.call).to eq(branch)
end
end
@@ -41,11 +48,10 @@
$stdin.expects(:gets).returns('')
end
- it 'sets the default as the value' do
- env.expects(:set).with(key, branch)
- question.call
+
+ it 'returns the default as the value' do
+ expect(question.call).to eq(branch)
end
-
end
end
end
diff --git a/spec/lib/capistrano/configuration/server_spec.rb b/spec/lib/capistrano/configuration/server_spec.rb
index 0d7a719..ad81f3d 100644
--- a/spec/lib/capistrano/configuration/server_spec.rb
+++ b/spec/lib/capistrano/configuration/server_spec.rb
@@ -28,31 +28,31 @@
end
it 'adds the role' do
- expect{subject}.to be_true
+ expect(subject).to be_truthy
end
end
describe 'comparing identity' do
- subject { server.matches? Server[hostname] }
+ subject { server.hostname == Server[hostname].hostname }
context 'with the same user, hostname and port' do
let(:hostname) { 'root@hostname:1234' }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'with a different user' do
let(:hostname) { 'deployer@hostname:1234' }
- it { should be_false }
+ it { expect(subject).to be_truthy }
end
context 'with a different port' do
let(:hostname) { 'root@hostname:5678' }
- it { should be_false }
+ it { expect(subject).to be_truthy }
end
context 'with a different hostname' do
let(:hostname) { 'root@otherserver:1234' }
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
end
@@ -69,7 +69,7 @@
context 'server is not primary' do
it 'is falesy' do
- expect(subject).to be_false
+ expect(subject).to be_falsey
end
end
end
@@ -93,6 +93,10 @@
it 'sets the user' do
expect(server.user).to eq 'tomc'
+ end
+
+ it 'sets the netssh_options user' do
+ expect(server.netssh_options[:user]).to eq 'tomc'
end
end
@@ -149,7 +153,7 @@
end
context 'options are empty' do
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'value is a symbol' do
@@ -157,35 +161,59 @@
context 'with :filter' do
let(:options) { { filter: :active }}
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'with :select' do
let(:options) { { select: :active }}
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'with :exclude' do
let(:options) { { exclude: :active }}
- it { should be_false }
+ it { expect(subject).to be_falsey }
+ end
+ end
+
+ context 'value does not match server properly' do
+ context 'with :active true' do
+ let(:options) { { active: true }}
+ it { expect(subject).to be_truthy }
+ end
+
+ context 'with :active false' do
+ let(:options) { { active: false }}
+ it { expect(subject).to be_falsey }
end
end
context 'value does not match server properly' do
context 'with :filter' do
let(:options) { { filter: :inactive }}
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
context 'with :select' do
let(:options) { { select: :inactive }}
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
context 'with :exclude' do
let(:options) { { exclude: :inactive }}
- it { should be_true }
- end
+ it { expect(subject).to be_truthy }
+ end
+ end
+ end
+
+ context 'key is a property' do
+ context 'with :active true' do
+ let(:options) { { active: true }}
+ it { expect(subject).to be_truthy }
+ end
+
+ context 'with :active false' do
+ let(:options) { { active: false }}
+ it { expect(subject).to be_falsey }
end
end
@@ -194,17 +222,17 @@
context 'with :filter' do
let(:options) { { filter: ->(s) { s.properties.active } } }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'with :select' do
let(:options) { { select: ->(s) { s.properties.active } } }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'with :exclude' do
let(:options) { { exclude: ->(s) { s.properties.active } } }
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
end
@@ -212,17 +240,17 @@
context 'value does not match server properly' do
context 'with :filter' do
let(:options) { { filter: ->(s) { s.properties.inactive } } }
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
context 'with :select' do
let(:options) { { select: ->(s) { s.properties.inactive } } }
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
context 'with :exclude' do
let(:options) { { exclude: ->(s) { s.properties.inactive } } }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
end
@@ -261,6 +289,9 @@
it 'contains correct user' do
expect(server.netssh_options[:user]).to eq 'another_user'
end
+ it 'does not affect server user in host' do
+ expect(server.user).to eq 'user_name'
+ end
it 'contains keys' do
expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa)
end
diff --git a/spec/lib/capistrano/configuration/servers/host_filter_spec.rb b/spec/lib/capistrano/configuration/servers/host_filter_spec.rb
deleted file mode 100644
index b927c31..0000000
--- a/spec/lib/capistrano/configuration/servers/host_filter_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'spec_helper'
-
-module Capistrano
- class Configuration
- class Servers
-
- describe HostFilter do
- let(:host_filter) { HostFilter.new(available) }
- let(:available) { [ Server.new('server1'), Server.new('server2'), Server.new('server3') ] }
-
- describe '#new' do
- it 'takes one array of hostnames' do
- expect(host_filter)
- end
- end
-
- describe '.for' do
-
- subject { HostFilter.for(available) }
-
- context 'without env vars' do
- it 'returns all available hosts' do
- expect(subject).to eq available
- end
- end
-
- context 'with ENV vars' do
- before do
- ENV.stubs(:[]).with('HOSTS').returns('server1,server2')
- end
-
- it 'returns all required hosts defined in HOSTS' do
- expect(subject).to eq [Server.new('server1'), Server.new('server2')]
- end
- end
-
- context 'with configuration filters' do
- before do
- Configuration.env.set(:filter, hosts: %w{server1 server2})
- end
-
- it 'returns all required hosts defined in the filter' do
- expect(subject).to eq [Server.new('server1'), Server.new('server2')]
- end
-
- after do
- Configuration.env.delete(:filter)
- end
- end
-
- context 'with a single configuration filter' do
- before do
- Configuration.env.set(:filter, hosts: 'server3')
- end
-
- it 'returns all required hosts defined in the filter' do
- expect(subject).to eq [Server.new('server3')]
- end
-
- after do
- Configuration.env.delete(:filter)
- end
- end
-
- context 'with configuration filters and ENV vars' do
- before do
- Configuration.env.set(:filter, hosts: 'server1')
- ENV.stubs(:[]).with('HOSTS').returns('server3')
- end
-
- it 'returns all required hosts defined in the filter' do
- expect(subject).to eq [Server.new('server1'), Server.new('server3')]
- end
-
- after do
- Configuration.env.delete(:filter)
- end
- end
- end
- end
-
- end
- end
-end
diff --git a/spec/lib/capistrano/configuration/servers/role_filter_spec.rb b/spec/lib/capistrano/configuration/servers/role_filter_spec.rb
deleted file mode 100644
index c92ae55..0000000
--- a/spec/lib/capistrano/configuration/servers/role_filter_spec.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-require 'spec_helper'
-
-module Capistrano
- class Configuration
- class Servers
-
- describe RoleFilter do
- let(:role_filter) { RoleFilter.new(required, available) }
- let(:required) { [] }
- let(:available) { [:web, :app, :db] }
-
- describe '#new' do
- it 'takes two arrays of role names' do
- expect(role_filter)
- end
- end
-
- describe '.for' do
-
- subject { RoleFilter.for(required, available) }
-
- context 'without env vars' do
- context ':all required' do
- let(:required) { [:all] }
-
- it 'returns all available names' do
- expect(subject).to eq available
- end
- end
-
- context 'role names required' do
- let(:required) { [:web, :app] }
- it 'returns all required names' do
- expect(subject).to eq required
- end
- end
- end
-
- context 'with ENV vars' do
- before do
- ENV.stubs(:[]).with('ROLES').returns('app,web')
- end
-
- context ':all required' do
- let(:required) { [:all] }
-
- it 'returns available names defined in ROLES' do
- expect(subject).to eq [:app, :web]
- end
- end
-
- context 'role names required' do
- let(:required) { [:web, :db] }
- it 'returns all required names defined in ROLES' do
- expect(subject).to eq [:web]
- end
- end
- end
-
- context 'with configuration filters' do
- before do
- Configuration.env.set(:filter, roles: %w{app web})
- end
-
- context ':all required' do
- let(:required) { [:all] }
-
- it 'returns available names defined in the filter' do
- expect(subject).to eq [:app, :web]
- end
- end
-
- context 'role names required' do
- let(:required) { [:web, :db] }
- it 'returns all required names defined in the filter' do
- expect(subject).to eq [:web]
- end
- end
-
- after do
- Configuration.env.delete(:filter)
- end
- end
-
- context 'with a single configuration filter' do
- before do
- Configuration.env.set(:filter, roles: 'web')
- end
-
- context ':all required' do
- let(:required) { [:all] }
-
- it 'returns available names defined in the filter' do
- expect(subject).to eq [:web]
- end
- end
-
- context 'role names required' do
- let(:required) { [:web, :db] }
- it 'returns all required names defined in the filter' do
- expect(subject).to eq [:web]
- end
- end
-
- after do
- Configuration.env.delete(:filter)
- end
- end
-
- context 'with configuration filters and ENV vars' do
- before do
- Configuration.env.set(:filter, roles: %w{app})
- ENV.stubs(:[]).with('ROLES').returns('web')
- end
-
- context ':all required' do
- let(:required) { [:all] }
-
- it 'returns available names defined in the filter' do
- expect(subject).to eq [:web, :app]
- end
- end
-
- context 'role names required' do
- let(:required) { [:web, :db] }
- it 'returns all required names defined in the filter' do
- expect(subject).to eq [:web]
- end
- end
-
- after do
- Configuration.env.delete(:filter)
- end
- end
- end
- end
-
- end
- end
-end
diff --git a/spec/lib/capistrano/configuration/servers_spec.rb b/spec/lib/capistrano/configuration/servers_spec.rb
index 0af66ad..6582524 100644
--- a/spec/lib/capistrano/configuration/servers_spec.rb
+++ b/spec/lib/capistrano/configuration/servers_spec.rb
@@ -18,6 +18,12 @@
expect(servers.count).to eq 1
end
+ it 'handles de-duplification within roles with users' do
+ servers.add_role(:app, %w{1}, user: 'nick')
+ servers.add_role(:app, %w{1}, user: 'fred')
+ expect(servers.count).to eq 1
+ end
+
it 'accepts instances of server objects' do
servers.add_role(:app, [Capistrano::Configuration::Server.new('example.net'), 'example.com'])
expect(servers.roles_for([:app]).length).to eq 2
@@ -26,6 +32,15 @@
it 'accepts non-enumerable types' do
servers.add_role(:app, '1')
expect(servers.roles_for([:app]).count).to eq 1
+ end
+
+ it 'creates distinct server properties' do
+ servers.add_role(:db, %w{1 2}, db: { port: 1234 } )
+ servers.add_host('1', db: { master: true })
+ expect(servers.count).to eq(2)
+ expect(servers.roles_for([:db]).count).to eq 2
+ expect(servers.find(){|s| s.hostname == '1'}.properties.db).to eq({ port: 1234, master: true })
+ expect(servers.find(){|s| s.hostname == '2'}.properties.db).to eq({ port: 1234 })
end
end
@@ -59,15 +74,25 @@
end
describe 'finding the primary server' do
+ after do
+ Configuration.reset!
+ end
it 'takes the first server if none have the primary property' do
servers.add_role(:app, %w{1 2})
- servers.fetch_primary(:app).hostname.should == '1'
+ expect(servers.fetch_primary(:app).hostname).to eq('1')
end
it 'takes the first server with the primary have the primary flag' do
servers.add_role(:app, %w{1 2})
servers.add_host('2', primary: true)
- servers.fetch_primary(:app).hostname.should == '2'
+ expect(servers.fetch_primary(:app).hostname).to eq('2')
+ end
+
+ it 'ignores any on_filters' do
+ Configuration.env.set :filter, { host: '1'}
+ servers.add_role(:app, %w{1 2})
+ servers.add_host('2', primary: true)
+ expect(servers.fetch_primary(:app).hostname).to eq('2')
end
end
@@ -115,8 +140,71 @@
servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root', port: 34)
servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 34)
servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 56)
- servers.should have(8).items
- end
+ expect(servers.count).to eq(1)
+ end
+
+ describe "with a :user property" do
+
+ it 'sets the server ssh username' do
+ servers.add_host('1', roles: [:app, 'web'], user: 'nick')
+ expect(servers.count).to eq(1)
+ expect(servers.roles_for([:all]).first.user).to eq 'nick'
+ end
+
+ it 'overwrites the value of a user specified in the hostname' do
+ servers.add_host('brian@1', roles: [:app, 'web'], user: 'nick')
+ expect(servers.count).to eq(1)
+ expect(servers.roles_for([:all]).first.user).to eq 'nick'
+ end
+
+ end
+
+ it 'overwrites the value of a previously defined scalar property' do
+ servers.add_host('1', roles: [:app, 'web'], test: :volatile)
+ expect(servers.count).to eq(1)
+ expect(servers.roles_for([:all]).first.properties.test).to eq :volatile
+ end
+
+ it 'merges previously defined hash properties' do
+ servers.add_host('1', roles: [:b], db: { port: 1234 })
+ servers.add_host('1', roles: [:b], db: { master: true })
+ expect(servers.count).to eq(1)
+ expect(servers.roles_for([:b]).first.properties.db).to eq({ port: 1234, master: true })
+ end
+
+ it 'concatenates previously defined array properties' do
+ servers.add_host('1', roles: [:b], steps: [1,3,5])
+ servers.add_host('1', roles: [:b], steps: [1,9])
+ expect(servers.count).to eq(1)
+ expect(servers.roles_for([:b]).first.properties.steps).to eq([1,3,5,1,9])
+ end
+
+ it 'merges previously defined set properties' do
+ servers.add_host('1', roles: [:b], endpoints: Set[123,333])
+ servers.add_host('1', roles: [:b], endpoints: Set[222,333])
+ expect(servers.count).to eq(1)
+ expect(servers.roles_for([:b]).first.properties.endpoints).to eq(Set[123,222,333])
+ end
+
+ it 'adds array property value only ones for a new host' do
+ servers.add_host('2', roles: [:array_test], array_property: [1,2])
+ expect(servers.roles_for([:array_test]).first.properties.array_property).to eq [1,2]
+ end
+
+ it 'updates roles when custom user defined' do
+ servers.add_host('1', roles: ['foo'], user: 'custom')
+ servers.add_host('1', roles: ['bar'], user: 'custom')
+ expect(servers.roles_for([:foo]).first.hostname).to eq '1'
+ expect(servers.roles_for([:bar]).first.hostname).to eq '1'
+ end
+
+ it 'updates roles when custom port defined' do
+ servers.add_host('1', roles: ['foo'], port: 1234)
+ servers.add_host('1', roles: ['bar'], port: 1234)
+ expect(servers.roles_for([:foo]).first.hostname).to eq '1'
+ expect(servers.roles_for([:bar]).first.hostname).to eq '1'
+ end
+
end
describe 'selecting roles' do
@@ -181,11 +269,9 @@
end
- describe 'filtering roles' do
-
- before do
- ENV.stubs(:[]).with('ROLES').returns('web,db')
- ENV.stubs(:[]).with('HOSTS').returns(nil)
+ describe 'filtering roles internally' do
+
+ before do
servers.add_host('1', roles: :app, active: true)
servers.add_host('2', roles: :app)
servers.add_host('3', roles: :web)
@@ -195,31 +281,67 @@
subject { servers.roles_for(roles).map(&:hostname) }
- context 'when selecting all roles' do
- let(:roles) { [:all] }
-
- it 'returns the roles specified by ROLE' do
- expect(subject).to eq %w{3 4 5}
- end
- end
-
- context 'when selecting roles included in ROLE' do
- let(:roles) { [:app, :web] }
-
- it 'returns only roles that match ROLE' do
- expect(subject).to eq %w{3 4}
- end
- end
-
- context 'when selecting roles not included in ROLE' do
- let(:roles) { [:app] }
-
- it 'is empty' do
- expect(subject).to be_empty
- end
- end
- end
-
+ context 'with the ROLES environment variable set' do
+
+ before do
+ ENV.stubs(:[]).with('ROLES').returns('web,db')
+ ENV.stubs(:[]).with('HOSTS').returns(nil)
+ end
+
+ context 'when selecting all roles' do
+ let(:roles) { [:all] }
+ it 'ignores it' do
+ expect(subject).to eq %w{1 2 3 4 5}
+ end
+ end
+
+ context 'when selecting specific roles' do
+ let(:roles) { [:app, :web] }
+ it 'ignores it' do
+ expect(subject).to eq %w{1 2 3 4}
+ end
+ end
+
+ context 'when selecting roles not included in ROLE' do
+ let(:roles) { [:app] }
+ it 'ignores it' do
+ expect(subject).to eq %w{1 2}
+ end
+ end
+
+ end
+
+ context 'with the HOSTS environment variable set' do
+
+ before do
+ ENV.stubs(:[]).with('ROLES').returns(nil)
+ ENV.stubs(:[]).with('HOSTS').returns('3,5')
+ end
+
+ context 'when selecting all roles' do
+ let(:roles) { [:all] }
+ it 'ignores it' do
+ expect(subject).to eq %w{1 2 3 4 5}
+ end
+ end
+
+ context 'when selecting specific roles' do
+ let(:roles) { [:app, :web] }
+ it 'ignores it' do
+ expect(subject).to eq %w{1 2 3 4}
+ end
+ end
+
+ context 'when selecting no roles' do
+ let(:roles) { [] }
+ it 'ignores it' do
+ expect(subject).to be_empty
+ end
+ end
+
+ end
+
+ end
end
end
end
diff --git a/spec/lib/capistrano/configuration_spec.rb b/spec/lib/capistrano/configuration_spec.rb
index 42c37a2..ea2fa52 100644
--- a/spec/lib/capistrano/configuration_spec.rb
+++ b/spec/lib/capistrano/configuration_spec.rb
@@ -5,10 +5,17 @@
let(:config) { Configuration.new }
let(:servers) { stub }
+ describe '.new' do
+ it 'accepts initial hash' do
+ configuration = described_class.new(custom: 'value')
+ expect(configuration.fetch(:custom)).to eq('value')
+ end
+ end
+
describe '.env' do
it 'is a global accessor to a single instance' do
Configuration.env.set(:test, true)
- expect(Configuration.env.fetch(:test)).to be_true
+ expect(Configuration.env.fetch(:test)).to be_truthy
end
end
@@ -44,6 +51,19 @@
end
it 'returns the set value' do
+ expect(subject).to eq :value
+ end
+ end
+
+ context 'set_if_empty' do
+ it 'sets the value when none is present' do
+ config.set_if_empty(:key, :value)
+ expect(subject).to eq :value
+ end
+
+ it 'does not overwrite the value' do
+ config.set(:key, :value)
+ config.set_if_empty(:key, :update)
expect(subject).to eq :value
end
end
@@ -138,14 +158,15 @@
describe 'asking' do
let(:question) { stub }
+ let(:options) { Hash.new }
before do
- Configuration::Question.expects(:new).with(config, :branch, :default).
+ Configuration::Question.expects(:new).with(:branch, :default, options).
returns(question)
end
it 'prompts for the value when fetching' do
- config.ask(:branch, :default)
+ config.ask(:branch, :default, options)
expect(config.fetch(:branch)).to eq question
end
end
@@ -159,6 +180,17 @@
config.backend = :test
expect(config.backend).to eq :test
end
+
+ describe "ssh_options for Netssh" do
+ it 'merges them with the :ssh_options variable' do
+ config.set :format, :pretty
+ config.set :log_level, :debug
+ config.set :ssh_options, { user: 'albert' }
+ SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { password: 'einstein' } end
+ config.configure_backend
+ expect(config.backend.config.backend.config.ssh_options).to eq({ user: 'albert', password: 'einstein' })
+ end
+ end
end
end
end
diff --git a/spec/lib/capistrano/dsl/paths_spec.rb b/spec/lib/capistrano/dsl/paths_spec.rb
index b946fd6..330f08f 100644
--- a/spec/lib/capistrano/dsl/paths_spec.rb
+++ b/spec/lib/capistrano/dsl/paths_spec.rb
@@ -1,69 +1,190 @@
require 'spec_helper'
-module Capistrano
- module DSL
+describe Capistrano::DSL::Paths do
- class DummyPaths
- include Paths
+ let(:dsl) { Class.new.extend Capistrano::DSL }
+ let(:parent) { Pathname.new('/var/shared') }
+ let(:paths) { Class.new.extend Capistrano::DSL::Paths }
+
+ let(:linked_dirs) { %w{log public/system} }
+ let(:linked_files) { %w{config/database.yml log/my.log} }
+
+ before do
+ dsl.set(:deploy_to, '/var/www')
+ end
+
+ describe '#linked_dirs' do
+ subject { paths.linked_dirs(parent) }
+
+ before do
+ paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
end
- describe Paths do
- let(:paths) { DummyPaths.new }
- let(:parent) { Pathname.new('/var/shared') }
-
- let(:linked_dirs) { %w{log public/system} }
- let(:linked_files) { %w{config/database.yml log/my.log} }
+ it 'returns the full pathnames' do
+ expect(subject).to eq [Pathname.new('/var/shared/log'), Pathname.new('/var/shared/public/system')]
+ end
+ end
- describe '#linked_dirs' do
- subject { paths.linked_dirs(parent) }
+ describe '#linked_files' do
+ subject { paths.linked_files(parent) }
- before do
- paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
- end
+ before do
+ paths.expects(:fetch).with(:linked_files).returns(linked_files)
+ end
- it 'returns the full pathnames' do
- expect(subject).to eq [Pathname.new('/var/shared/log'), Pathname.new('/var/shared/public/system')]
- end
+ it 'returns the full pathnames' do
+ expect(subject).to eq [Pathname.new('/var/shared/config/database.yml'), Pathname.new('/var/shared/log/my.log')]
+ end
+ end
+
+ describe '#linked_file_dirs' do
+ subject { paths.linked_file_dirs(parent) }
+
+ before do
+ paths.expects(:fetch).with(:linked_files).returns(linked_files)
+ end
+
+ it 'returns the full paths names of the parent dirs' do
+ expect(subject).to eq [Pathname.new('/var/shared/config'), Pathname.new('/var/shared/log')]
+ end
+ end
+
+ describe '#linked_dir_parents' do
+ subject { paths.linked_dir_parents(parent) }
+
+ before do
+ paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
+ end
+
+ it 'returns the full paths names of the parent dirs' do
+ expect(subject).to eq [Pathname.new('/var/shared'), Pathname.new('/var/shared/public')]
+ end
+ end
+
+ describe '#release path' do
+
+ subject { dsl.release_path }
+
+ context 'where no release path has been set' do
+ before do
+ dsl.delete(:release_path)
end
+ it 'returns the `current_path` value' do
+ expect(subject.to_s).to eq '/var/www/current'
+ end
+ end
- describe '#linked_files' do
- subject { paths.linked_files(parent) }
-
- before do
- paths.expects(:fetch).with(:linked_files).returns(linked_files)
- end
-
- it 'returns the full pathnames' do
- expect(subject).to eq [Pathname.new('/var/shared/config/database.yml'), Pathname.new('/var/shared/log/my.log')]
- end
+ context 'where the release path has been set' do
+ before do
+ dsl.set(:release_path,'/var/www/release_path')
end
- describe '#linked_file_dirs' do
- subject { paths.linked_file_dirs(parent) }
+ it 'returns the set `release_path` value' do
+ expect(subject.to_s).to eq '/var/www/release_path'
+ end
+ end
+ end
- before do
- paths.expects(:fetch).with(:linked_files).returns(linked_files)
- end
+ describe '#set_release_path' do
+ let(:now) { Time.parse("Oct 21 16:29:00 2015") }
+ subject { dsl.release_path }
- it 'returns the full paths names of the parent dirs' do
- expect(subject).to eq [Pathname.new('/var/shared/config'), Pathname.new('/var/shared/log')]
- end
+ context 'without a timestamp' do
+ before do
+ dsl.env.expects(:timestamp).returns(now)
+ dsl.set_release_path
end
- describe '#linked_dir_parents' do
- subject { paths.linked_dir_parents(parent) }
+ it 'returns the release path with the current env timestamp' do
+ expect(subject.to_s).to eq '/var/www/releases/20151021162900'
+ end
+ end
- before do
- paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
- end
-
- it 'returns the full paths names of the parent dirs' do
- expect(subject).to eq [Pathname.new('/var/shared'), Pathname.new('/var/shared/public')]
- end
+ context 'with a timestamp' do
+ before do
+ dsl.set_release_path('timestamp')
end
+ it 'returns the release path with the timestamp' do
+ expect(subject.to_s).to eq '/var/www/releases/timestamp'
+ end
+ end
+ end
+
+ describe '#deploy_config_path' do
+ subject { dsl.deploy_config_path.to_s }
+
+ context 'when not specified' do
+ before do
+ dsl.delete(:deploy_config_path)
+ end
+
+ it 'returns "config/deploy.rb"' do
+ expect(subject).to eq 'config/deploy.rb'
+ end
+ end
+
+ context 'when the variable :deploy_config_path is set' do
+ before do
+ dsl.set(:deploy_config_path, 'my/custom/path.rb')
+ end
+
+ it 'returns the custom path' do
+ expect(subject).to eq 'my/custom/path.rb'
+ end
+ end
+ end
+
+ describe '#stage_config_path' do
+ subject { dsl.stage_config_path.to_s }
+
+ context 'when not specified' do
+
+ before do
+ dsl.delete(:stage_config_path)
+ end
+
+ it 'returns "config/deploy"' do
+ expect(subject).to eq 'config/deploy'
+ end
+ end
+
+ context 'when the variable :stage_config_path is set' do
+ before do
+ dsl.set(:stage_config_path, 'my/custom/path')
+ end
+
+ it 'returns the custom path' do
+ expect(subject).to eq 'my/custom/path'
+ end
+ end
+ end
+
+ describe '#repo_path' do
+ subject { dsl.repo_path.to_s }
+
+ context 'when not specified' do
+
+ before do
+ dsl.delete(:repo_path)
+ end
+
+ it 'returns the default #{deploy_to}/repo' do
+ dsl.set(:deploy_to, '/var/www')
+ expect(subject).to eq '/var/www/repo'
+ end
+ end
+
+ context 'when the variable :repo_path is set' do
+ before do
+ dsl.set(:repo_path, 'my/custom/path')
+ end
+
+ it 'returns the custom path' do
+ expect(subject).to eq 'my/custom/path'
+ end
end
end
end
diff --git a/spec/lib/capistrano/dsl/task_enhancements_spec.rb b/spec/lib/capistrano/dsl/task_enhancements_spec.rb
new file mode 100644
index 0000000..2c960fb
--- /dev/null
+++ b/spec/lib/capistrano/dsl/task_enhancements_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+module Capistrano
+ class DummyTaskEnhancements
+ include TaskEnhancements
+ end
+
+ describe TaskEnhancements do
+ let(:task_enhancements) { DummyTaskEnhancements.new }
+
+ describe 'ordering' do
+
+ after do
+ task.clear
+ before_task.clear
+ after_task.clear
+ Rake::Task.clear
+ end
+
+ let(:order) { [] }
+ let!(:task) do
+ Rake::Task.define_task('task', [:order]) do |t, args|
+ args['order'].push 'task'
+ end
+ end
+
+ let!(:before_task) do
+ Rake::Task.define_task('before_task') do
+ order.push 'before_task'
+ end
+ end
+
+ let!(:after_task) do
+ Rake::Task.define_task('after_task') do
+ order.push 'after_task'
+ end
+ end
+
+ it 'invokes in proper order if define after than before' do
+ task_enhancements.after('task', 'after_task')
+ task_enhancements.before('task', 'before_task')
+
+ Rake::Task['task'].invoke order
+
+ expect(order).to eq(['before_task', 'task', 'after_task'])
+ end
+
+ it 'invokes in proper order if define before than after' do
+ task_enhancements.before('task', 'before_task')
+ task_enhancements.after('task', 'after_task')
+
+ Rake::Task['task'].invoke order
+
+ expect(order).to eq(['before_task', 'task', 'after_task'])
+ end
+
+ it 'invokes in proper order and with arguments and block' do
+ task_enhancements.after('task', 'after_task_custom', :order) do |t, args|
+ order.push 'after_task'
+ end
+
+ task_enhancements.before('task', 'before_task_custom', :order) do |t, args|
+ order.push 'before_task'
+ end
+
+ Rake::Task['task'].invoke(order)
+
+ expect(order).to eq(['before_task', 'task', 'after_task'])
+ end
+
+ end
+
+ describe 'remote_file' do
+ subject(:remote_file) { task_enhancements.remote_file('source' => 'destination') }
+
+ it { expect(remote_file.name).to eq('source') }
+ it { is_expected.to be_a(Capistrano::UploadTask) }
+
+ describe 'namespaced' do
+ let(:app) { Rake.application }
+ around { |ex| app.in_namespace('namespace', &ex) }
+
+ it { expect(remote_file.name).to eq('source') }
+ it { is_expected.to be_a(Capistrano::UploadTask) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/capistrano/dsl_spec.rb b/spec/lib/capistrano/dsl_spec.rb
index bd0ec8d..12e4391 100644
--- a/spec/lib/capistrano/dsl_spec.rb
+++ b/spec/lib/capistrano/dsl_spec.rb
@@ -27,14 +27,14 @@
before do
dsl.set(:stage, :sandbox)
end
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'stage is not set' do
before do
dsl.set(:stage, nil)
end
- it { should be_false }
+ it { expect(subject).to be_falsey }
end
end
@@ -48,16 +48,5 @@
dsl.sudo(:my, :command)
end
end
-
- describe '#local_user' do
-
- before do
- Etc.expects(:getlogin)
- end
-
- it 'delegates to Etc#getlogin' do
- dsl.local_user
- end
- end
end
end
diff --git a/spec/lib/capistrano/git_spec.rb b/spec/lib/capistrano/git_spec.rb
index c3b868b..0876038 100644
--- a/spec/lib/capistrano/git_spec.rb
+++ b/spec/lib/capistrano/git_spec.rb
@@ -31,7 +31,7 @@
describe "#check" do
it "should test the repo url" do
context.expects(:repo_url).returns(:url)
- context.expects(:test).with(:git, :'ls-remote -h', :url).returns(true)
+ context.expects(:execute).with(:git, :'ls-remote --heads', :url).returns(true)
subject.check
end
@@ -57,11 +57,22 @@
end
describe "#release" do
- it "should run git archive" do
- context.expects(:fetch).returns(:branch)
+ it "should run git archive without a subtree" do
+ context.expects(:fetch).with(:repo_tree).returns(nil)
+ context.expects(:fetch).with(:branch).returns(:branch)
context.expects(:release_path).returns(:path)
- context.expects(:execute).with(:git, :archive, :branch, '| tar -x -C', :path)
+ context.expects(:execute).with(:git, :archive, :branch, '| tar -x -f - -C', :path)
+
+ subject.release
+ end
+
+ it "should run git archive with a subtree" do
+ context.expects(:fetch).with(:repo_tree).returns('tree')
+ context.expects(:fetch).with(:branch).returns(:branch)
+ context.expects(:release_path).returns(:path)
+
+ context.expects(:execute).with(:git, :archive, :branch, 'tree', '| tar -x --strip-components 1 -f - -C', :path)
subject.release
end
diff --git a/spec/lib/capistrano/hg_spec.rb b/spec/lib/capistrano/hg_spec.rb
index 7519cff..d7d5147 100644
--- a/spec/lib/capistrano/hg_spec.rb
+++ b/spec/lib/capistrano/hg_spec.rb
@@ -57,14 +57,25 @@
end
describe "#release" do
- it "should run hg archive" do
- context.expects(:fetch).returns(:branch)
+ it "should run hg archive without a subtree" do
+ context.expects(:fetch).with(:repo_tree).returns(nil)
+ context.expects(:fetch).with(:branch).returns(:branch)
context.expects(:release_path).returns(:path)
context.expects(:execute).with(:hg, "archive", :path, "--rev", :branch)
subject.release
end
+
+ it "should run hg archive with a subtree" do
+ context.expects(:fetch).with(:repo_tree).returns('tree')
+ context.expects(:fetch).with(:branch).returns(:branch)
+ context.expects(:release_path).returns(:path)
+
+ context.expects(:execute).with(:hg, "archive --type tgz -p . -I", 'tree', "--rev", :branch, '| tar -x --strip-components 1 -f - -C', :path)
+
+ subject.release
+ end
end
end
end
diff --git a/spec/lib/capistrano/scm_spec.rb b/spec/lib/capistrano/scm_spec.rb
index 3e736be..af0f1b2 100644
--- a/spec/lib/capistrano/scm_spec.rb
+++ b/spec/lib/capistrano/scm_spec.rb
@@ -50,21 +50,21 @@
describe "#repo_url" do
it "should return the repo url according to the context" do
context.expects(:repo_url).returns(:url)
- subject.repo_url.should == :url
+ expect(subject.repo_url).to eq(:url)
end
end
describe "#repo_path" do
it "should return the repo path according to the context" do
context.expects(:repo_path).returns(:path)
- subject.repo_path.should == :path
+ expect(subject.repo_path).to eq(:path)
end
end
describe "#release_path" do
it "should return the release path according to the context" do
context.expects(:release_path).returns('/path/to/nowhere')
- subject.release_path.should == '/path/to/nowhere'
+ expect(subject.release_path).to eq('/path/to/nowhere')
end
end
diff --git a/spec/lib/capistrano/svn_spec.rb b/spec/lib/capistrano/svn_spec.rb
index f86681f..b8a9fb9 100644
--- a/spec/lib/capistrano/svn_spec.rb
+++ b/spec/lib/capistrano/svn_spec.rb
@@ -60,10 +60,20 @@
it "should run svn export" do
context.expects(:release_path).returns(:path)
- context.expects(:execute).with(:svn, :export, '.', :path)
+ context.expects(:execute).with(:svn, :export, '--force', '.', :path)
subject.release
end
end
+
+ describe "#fetch_revision" do
+ it "should run fetch revision" do
+ context.expects(:repo_path).returns(:path)
+
+ context.expects(:capture).with(:svnversion, :path)
+
+ subject.fetch_revision
+ end
+ end
end
end
diff --git a/spec/lib/capistrano/upload_task_spec.rb b/spec/lib/capistrano/upload_task_spec.rb
new file mode 100644
index 0000000..353cd4f
--- /dev/null
+++ b/spec/lib/capistrano/upload_task_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Capistrano::UploadTask do
+ let(:app) { Rake.application = Rake::Application.new }
+
+ subject(:upload_task) { described_class.define_task('path/file.yml') }
+
+ it { is_expected.to be_a(Rake::FileCreationTask) }
+ it { is_expected.to be_needed }
+
+ context 'inside namespace' do
+ let(:normal_task) { Rake::Task.define_task('path/other_file.yml') }
+
+ around { |ex| app.in_namespace('namespace', &ex) }
+
+ it { expect(upload_task.name).to eq('path/file.yml') }
+ it { expect(upload_task.scope.path).to eq('namespace') }
+ end
+end
diff --git a/spec/lib/capistrano/version_validator_spec.rb b/spec/lib/capistrano/version_validator_spec.rb
index 6438581..a0a42c0 100644
--- a/spec/lib/capistrano/version_validator_spec.rb
+++ b/spec/lib/capistrano/version_validator_spec.rb
@@ -24,7 +24,7 @@
context 'with exact version' do
context 'valid' do
let(:version) { '3.0.1' }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'invalid - lower' do
@@ -48,7 +48,7 @@
context 'with optimistic versioning' do
context 'valid' do
let(:version) { '>= 3.0.0' }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'invalid - lower' do
@@ -66,7 +66,7 @@
context '2 decimal places' do
context 'valid' do
let(:version) { '~> 3.0.0' }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'invalid' do
@@ -83,7 +83,7 @@
context 'valid' do
let(:version) { '~> 3.1' }
- it { should be_true }
+ it { expect(subject).to be_truthy }
end
context 'invalid' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index dab66ed..9e4e1b3 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,13 +3,14 @@
require 'capistrano/all'
require 'rspec'
require 'mocha/api'
+require 'time'
# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each {|f| require f}
RSpec.configure do |config|
- config.treat_symbols_as_metadata_keys_with_true_values = true
+ config.raise_errors_for_deprecations!
config.mock_framework = :mocha
config.order = 'random'
end
diff --git a/spec/support/Vagrantfile b/spec/support/Vagrantfile
index 831f206..7ef80db 100644
--- a/spec/support/Vagrantfile
+++ b/spec/support/Vagrantfile
@@ -1,13 +1,24 @@
-Vagrant::Config.run do |config|
+require 'open-uri'
+
+Vagrant.configure("2") do |config|
+
+ config.ssh.insert_key = false
[:app].each_with_index do |role, i|
config.vm.define(role, primary: true) do |config|
- config.vm.box = role
- config.vm.box = 'precise64'
- config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
- config.vm.forward_port 22, "222#{i}".to_i
- config.vm.provision :shell, inline: 'yes | sudo apt-get install git-core'
+ config.vm.define role
+ config.vm.box = 'hashicorp/precise64'
+ config.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i
+ config.vm.provision :shell, inline: 'sudo apt-get -y install git-core'
+
+ vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r",&:read)
+
+ config.vm.provision :shell,
+ inline: <<-INLINE
+ install -d -m 700 /root/.ssh
+ echo -e "#{vagrantkey}" > /root/.ssh/authorized_keys
+ chmod 0600 /root/.ssh/authorized_keys
+ INLINE
end
end
-
end
diff --git a/spec/support/tasks/root.rake b/spec/support/tasks/root.rake
new file mode 100644
index 0000000..c82a0f8
--- /dev/null
+++ b/spec/support/tasks/root.rake
@@ -0,0 +1,11 @@
+task :am_i_root do
+ on roles(:all) do |host|
+ host.user = 'root'
+ ident = capture :id, '-a'
+ info "I am #{ident}"
+ end
+ on roles(:all) do |host|
+ ident = capture :id, '-a'
+ info "I am #{ident}"
+ end
+end
diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb
index e6aa848..c5d9eaa 100644
--- a/spec/support/test_app.rb
+++ b/spec/support/test_app.rb
@@ -1,4 +1,6 @@
require 'fileutils'
+require 'pathname'
+
module TestApp
extend self
@@ -11,7 +13,7 @@
set :deploy_to, '#{deploy_to}'
set :repo_url, 'git://github.com/capistrano/capistrano.git'
set :branch, 'master'
- set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key" }
+ set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key", auth_methods: ['publickey'] }
server 'vagrant@localhost:2220', roles: %w{web app}
set :linked_files, #{linked_files}
set :linked_dirs, #{linked_dirs}