diff --git a/capistrano.gemspec b/capistrano.gemspec index 5a6ad8a4..e95bf980 100644 --- a/capistrano.gemspec +++ b/capistrano.gemspec @@ -28,5 +28,8 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rspec' gem.add_development_dependency 'mocha' + gem.add_development_dependency 'vagrant', '~> 1.0.7' + gem.add_development_dependency 'kuroko' + gem.add_development_dependency 'cucumber' end diff --git a/features/deploy.feature b/features/deploy.feature new file mode 100644 index 00000000..5342b3bc --- /dev/null +++ b/features/deploy.feature @@ -0,0 +1,52 @@ +Feature: Deploy + + Background: + Given a test app with the default configuration + And servers with the roles app and web + + Scenario: Creating the repo + When I run cap "git:check" + Then references in the remote repo are listed + + Scenario: Creating the directory structure + When I run cap "deploy:check:directories" + Then the shared path is created + And the releases path is created + + Scenario: Creating linked directories + When I run cap "deploy:check:linked_dirs" + Then directories in :linked_dirs are created in shared + + Scenario: Creating linked directories for linked files + When I run cap "deploy:check:make_linked_dirs" + 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 + When I run cap "deploy:check:linked_files" + Then the task will exit + + Scenario: Checking linked files - file exists + Given a required file + And that file exists + When I run cap "deploy:check:linked_files" + Then the task will be successful + + Scenario: Creating a release + When I run cap "git:create_release" as part of a release + Then the repo is cloned + And the release is created + + Scenario: Symlink linked files + When I run cap "deploy:symlink:linked_files" as part of a release + Then file symlinks are created in the new release + + Scenario: Symlink linked dirs + When I run cap "deploy:symlink:linked_dirs" as part of a release + Then directory symlinks are created in the new release + + Scenario: Publishing + When I run cap "deploy:symlink:release" + Then the current directory will be a symlink to the release + diff --git a/features/installation.feature b/features/installation.feature new file mode 100644 index 00000000..20d23296 --- /dev/null +++ b/features/installation.feature @@ -0,0 +1,16 @@ +Feature: Installation + + Background: + Given a test app with the default configuration + + Scenario: With default stages + When I run cap "install" + Then the deploy.rb file is created + And the default stage files are created + And the tasks folder is created + + Scenario: With specified stages + When I run cap "install STAGES=qa,production" + Then the deploy.rb file is created + And the specified stage files are created + And the tasks folder is created diff --git a/features/remote_file_task.feature b/features/remote_file_task.feature new file mode 100644 index 00000000..6c2ac640 --- /dev/null +++ b/features/remote_file_task.feature @@ -0,0 +1,14 @@ +Feature: Remote file task + + Background: + Given a test app with the default configuration + And a custom task to generate a file + And servers with the roles app and web + + Scenario: Where the file does not exist + When I run cap "deploy:check:linked_files" + Then it creates the file with the remote_task prerequisite + + Scenario: Where the file already exists + When I run cap "deploy:check:linked_files" + Then it will not recreate the file diff --git a/features/step_definitions/assertions.rb b/features/step_definitions/assertions.rb new file mode 100644 index 00000000..efa002cd --- /dev/null +++ b/features/step_definitions/assertions.rb @@ -0,0 +1,90 @@ +Then(/^references in the remote repo are listed$/) do +end + +Then(/^the shared path is created$/) do + run_vagrant_command(test_dir_exists(TestApp.shared_path)) +end + +Then(/^the releases path is created$/) do + run_vagrant_command(test_dir_exists(TestApp.releases_path)) +end + +Then(/^directories in :linked_dirs are created in shared$/) do + TestApp.linked_dirs.each do |dir| + run_vagrant_command(test_dir_exists(TestApp.shared_path.join(dir))) + end +end + +Then(/^directories referenced in :linked_files are created in shared$/) do + dirs = TestApp.linked_files.map { |path| TestApp.shared_path.join(path).dirname } + dirs.each do | dir| + run_vagrant_command(test_dir_exists(dir)) + 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 + +Then(/^the release is created$/) do + run_vagrant_command("ls -g #{TestApp.releases_path}") +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))) + end +end + +Then(/^directory symlinks are created in the new release$/) do + pending + TestApp.linked_dirs.each do |dir| + run_vagrant_command(test_symlink_exists(TestApp.release_path.join(dir))) + end +end + +Then(/^the current directory will be a symlink to the release$/) do + run_vagrant_command(test_symlink_exists(TestApp.current_path)) +end + +Then(/^the deploy\.rb file is created$/) do + file = TestApp.test_app_path.join('config/deploy.rb') + 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 +end + +Then(/^the tasks folder is created$/) do + path = TestApp.test_app_path.join('lib/capistrano/tasks') + 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 +end + +Then(/^it creates the file with the remote_task prerequisite$/) do + TestApp.linked_files.each do |file| + run_vagrant_command(test_file_exists(TestApp.shared_path.join(file))) + end +end + +Then(/^it will not recreate the file$/) do + # +end diff --git a/features/step_definitions/cap_commands.rb b/features/step_definitions/cap_commands.rb new file mode 100644 index 00000000..f5b5c14d --- /dev/null +++ b/features/step_definitions/cap_commands.rb @@ -0,0 +1,8 @@ +When(/^I run cap "(.*?)"$/) do |task| + TestApp.cap(task) +end + +When(/^I run cap "(.*?)" as part of a release$/) do |task| + TestApp.cap("deploy:new_release_path #{task}") +end + diff --git a/features/step_definitions/setup.rb b/features/step_definitions/setup.rb new file mode 100644 index 00000000..4d16620a --- /dev/null +++ b/features/step_definitions/setup.rb @@ -0,0 +1,25 @@ +Given(/^a test app with the default configuration$/) do + TestApp.install +end + +Given(/^servers with the roles app and web$/) do + vagrant_cli_command('up') +end + +Given(/^a required file$/) do +end + +Given(/^that file exists$/) do + run_vagrant_command("touch #{TestApp.linked_file}") +end + +Given(/^the file does not exist$/) do + pending + file = TestApp.linked_file + run_vagrant_command("[ -f #{file} ] && rm #{file}") +end + +Given(/^a custom task to generate a file$/) do + TestApp.copy_task_to_test_app('spec/support/tasks/database.cap') +end + diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 00000000..3aed709c --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,12 @@ +require 'kuroko' + +project_root = File.expand_path('../../../', __FILE__) +vagrant_root = File.join(project_root, 'spec/support') + +Kuroko.configure do |config| + config.vagrant_root = 'spec/support' +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 new file mode 100644 index 00000000..47b56ef8 --- /dev/null +++ b/features/support/remote_command_helpers.rb @@ -0,0 +1,20 @@ +module RemoteCommandHelpers + + def test_dir_exists(path) + exists?('d', path) + end + + def test_symlink_exists(path) + exists?('L', path) + end + + def test_file_exists(path) + exists?('f', path) + end + + def exists?(type, path) + %{[ -#{type} "#{path}" ] && echo "#{path} exists." || echo "Error: #{path} does not exist."} + end +end + +World(RemoteCommandHelpers) diff --git a/spec/integration/deploy_finalize_spec.rb b/spec/integration/deploy_finalize_spec.rb deleted file mode 100644 index 7ef6f0d6..00000000 --- a/spec/integration/deploy_finalize_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:finished', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - } - } - - describe 'log_revision' do - before do - cap 'deploy:started' - cap 'deploy:updating' - cap 'deploy:publishing' - cap 'deploy:finished' - end - - it 'writes the log file' do - expect(deploy_to.join('revisions.log')).to be_a_file - end - end - end -end diff --git a/spec/integration/deploy_finished_spec.rb b/spec/integration/deploy_finished_spec.rb deleted file mode 100644 index 4a5a7d1a..00000000 --- a/spec/integration/deploy_finished_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:finished', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - set :release_path, '#{deploy_to}/releases/1234' - } - } - - describe 'symlink' do - before do - cap 'deploy:started' - cap 'deploy:updating' - cap 'deploy:publishing' - cap 'deploy:finished' - end - - describe 'release' do - it 'symlinks the release to `current`' do - expect(File.symlink?(current_path)).to be_true - expect(File.readlink(current_path)).to eq '/tmp/test_app/deploy_to/releases/1234' - end - end - end - end -end diff --git a/spec/integration/deploy_started_spec.rb b/spec/integration/deploy_started_spec.rb deleted file mode 100644 index 606f0098..00000000 --- a/spec/integration/deploy_started_spec.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:started', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy:check' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - } - } - - describe 'directories' do - before do - cap 'deploy:check:directories' - end - - it 'ensures the directory structure' do - expect(shared_path).to be_a_directory - expect(releases_path).to be_a_directory - end - end - - describe 'linked_dirs' do - before do - cap 'deploy:check:linked_dirs' - end - - it 'ensure directories to be linked in `shared`' do - [ - shared_path.join('bin'), - shared_path.join('log'), - shared_path.join('public/system'), - shared_path.join('vendor/bundle'), - ].each do |dir| - expect(dir).to be_a_directory - end - end - end - - describe 'linked_files' do - - subject { cap 'deploy:check:linked_files' } - - context 'file does not exist' do - it 'fails' do - expect(subject).to match 'config/database.yml does not exist' - end - end - - context 'file exists' do - before do - create_shared_directory('config') - create_shared_file('config/database.yml') - end - - it 'suceeds' do - expect(subject).not_to match 'config/database.yml does not exist' - expect(subject).to match 'successful' - end - - end - end - end -end - diff --git a/spec/integration/deploy_update_spec.rb b/spec/integration/deploy_update_spec.rb deleted file mode 100644 index 60867d5c..00000000 --- a/spec/integration/deploy_update_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:updating', slow: true do - before do - install_test_app_with(config) - end - - describe 'deploy' do - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{bin log public/system vendor/bundle} - } - } - - describe 'symlink' do - before do - create_shared_directory('config') - create_shared_file('config/database.yml') - cap 'deploy' - end - - describe 'linked_dirs' do - it 'symlinks the directories in shared to `current`' do - %w{bin log public/system vendor/bundle}.each do |dir| - expect(current_path.join(dir)).to be_a_symlink_to shared_path.join(dir) - end - end - end - - describe 'linked_files' do - it 'symlinks the files in shared to `current`' do - file = 'config/database.yml' - expect(current_path.join(file)).to be_a_symlink_to shared_path.join(file) - end - end - end - end -end diff --git a/spec/integration/installation_spec.rb b/spec/integration/installation_spec.rb deleted file mode 100644 index d17858f4..00000000 --- a/spec/integration/installation_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap install' do - - context 'with defaults' do - before :all do - create_test_app - Dir.chdir(test_app_path) do - %x[bundle exec cap install] - end - end - - describe 'installation' do - - it 'creates config/deploy' do - path = test_app_path.join('config/deploy') - expect(Dir.exists?(path)).to be_true - end - - it 'creates lib/capistrano/tasks' do - path = test_app_path.join('lib/capistrano/tasks') - expect(Dir.exists?(path)).to be_true - end - - it 'creates the deploy file' do - file = test_app_path.join('config/deploy.rb') - expect(File.exists?(file)).to be_true - end - - it 'creates the stage files' do - staging = test_app_path.join('config/deploy/staging.rb') - production = test_app_path.join('config/deploy/production.rb') - expect(File.exists?(staging)).to be_true - expect(File.exists?(production)).to be_true - end - - end - end - - context 'with STAGES' do - before :all do - create_test_app - Dir.chdir(test_app_path) do - %x[bundle exec cap install STAGES=qa,production] - end - end - - describe 'installation' do - - it 'creates config/deploy' do - path = test_app_path.join('config/deploy') - expect(Dir.exists?(path)).to be_true - end - - it 'creates lib/capistrano/tasks' do - path = test_app_path.join('lib/capistrano/tasks') - expect(Dir.exists?(path)).to be_true - end - - it 'creates the deploy file' do - file = test_app_path.join('config/deploy.rb') - expect(File.exists?(file)).to be_true - end - - it 'creates the stage files specified, not the defaults' do - qa = test_app_path.join('config/deploy/qa.rb') - production = test_app_path.join('config/deploy/production.rb') - staging = test_app_path.join('config/deploy/staging.rb') - expect(File.exists?(qa)).to be_true - expect(File.exists?(production)).to be_true - expect(File.exists?(staging)).to be_false - end - - end - end -end diff --git a/spec/integration/remote_file_task_spec.rb b/spec/integration/remote_file_task_spec.rb deleted file mode 100644 index 25eb8016..00000000 --- a/spec/integration/remote_file_task_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'integration_spec_helper' - -describe 'cap deploy:started', slow: true do - before do - install_test_app_with(config) - copy_task_to_test_app('spec/support/tasks/database.cap') - end - - let(:config) { - %{ - set :stage, :#{stage} - set :deploy_to, '#{deploy_to}' - set :repo_url, 'git://github.com/capistrano/capistrano.git' - set :branch, 'v3' - server 'localhost', roles: %w{web app}, user: '#{current_user}' - set :linked_files, %w{config/database.yml} - set :linked_dirs, %w{config} - } - } - - describe 'linked_files' do - - before do - cap 'deploy:check:linked_dirs' - end - - subject { cap 'deploy:check:linked_files' } - - context 'where the file does not exist' do - it 'creates the file with the remote_task prerequisite' do - expect(subject).to match 'Uploading' - expect(subject).not_to match 'config/database.yml does not exist' - expect(subject).to match 'successful' - end - end - - context 'where the file already exists' do - before do - FileUtils.touch(shared_path.join('config/database.yml')) - end - - it 'will not recreate the file if it already exists' do - expect(subject).not_to match 'Uploading' - expect(subject).not_to match 'config/database.yml does not exist' - expect(subject).to match 'successful' - end - end - - end -end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 609686a7..dab66ed6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,5 +12,4 @@ RSpec.configure do |config| config.treat_symbols_as_metadata_keys_with_true_values = true config.mock_framework = :mocha config.order = 'random' - config.filter_run_excluding :slow end diff --git a/spec/support/.gitignore b/spec/support/.gitignore new file mode 100644 index 00000000..8000dd9d --- /dev/null +++ b/spec/support/.gitignore @@ -0,0 +1 @@ +.vagrant diff --git a/spec/support/Vagrantfile b/spec/support/Vagrantfile new file mode 100644 index 00000000..831f206e --- /dev/null +++ b/spec/support/Vagrantfile @@ -0,0 +1,13 @@ +Vagrant::Config.run do |config| + + [: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' + end + end + +end diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index b186c00b..5f18d1fd 100644 --- a/spec/support/test_app.rb +++ b/spec/support/test_app.rb @@ -1,10 +1,39 @@ require 'fileutils' module TestApp + extend self + + def install + install_test_app_with(default_config) + end + + def default_config + %{ + set :stage, :#{stage} + set :deploy_to, '#{deploy_to}' + set :repo_url, 'git://github.com/capistrano/capistrano.git' + set :branch, 'v3' + set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key" } + server 'vagrant@localhost:2220', roles: %w{web app} + set :linked_files, #{linked_files} + set :linked_dirs, #{linked_dirs} + } + end + + def linked_files + %w{config/database.yml} + end + + def linked_file + shared_path.join(linked_files.first) + end + + def linked_dirs + %w{bin log public/system vendor/bundle} + end + def create_test_app - [test_app_path, deploy_to].each do |path| - FileUtils.rm_rf(path) - FileUtils.mkdir(path) - end + FileUtils.rm_rf(test_app_path) + FileUtils.mkdir(test_app_path) File.open(gemfile, 'w+') do |file| file.write "gem 'capistrano', path: '#{path_to_cap}'" @@ -56,7 +85,7 @@ module TestApp end def deploy_to - Pathname.new('/tmp/test_app/deploy_to') + Pathname.new('/home/vagrant/var/www/deploy') end def shared_path @@ -72,7 +101,15 @@ module TestApp end def release_path - releases_path.join(Dir.entries(releases_path).last) + releases_path.join(timestamp) + end + + def timestamp + Time.now.utc.strftime("%Y%m%d%H%M%S") + end + + def repo_path + deploy_to.join('repo') end def path_to_cap