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}