capistrano-2.12.0/0000755000004100000410000000000011746743503014001 5ustar www-datawww-datacapistrano-2.12.0/.travis.yml0000644000004100000410000000007211746743503016111 0ustar www-datawww-datalanguage: ruby rvm: - 1.8.7 - 1.9.2 - 1.9.3 - ree capistrano-2.12.0/test/0000755000004100000410000000000011746743503014760 5ustar www-datawww-datacapistrano-2.12.0/test/configuration_test.rb0000644000004100000410000000431711746743503021220 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration' # These tests are only for testing the integration of the various components # of the Configuration class. To test specific features, please look at the # tests under test/configuration. class ConfigurationTest < Test::Unit::TestCase def setup @config = Capistrano::Configuration.new end def test_connections_execution_loading_namespaces_roles_and_variables_modules_should_integrate_correctly Capistrano::SSH.expects(:connect).with { |s,c| s.host == "www.capistrano.test" && c == @config }.returns(:session) process_args = Proc.new do |tree, session, opts| tree.fallback.command == "echo 'hello world'" && session == [:session] && opts == { :logger => @config.logger } end Capistrano::Command.expects(:process).with(&process_args) @config.load do role :test, "www.capistrano.test" set :message, "hello world" namespace :testing do task :example, :roles => :test do run "echo '#{message}'" end end end @config.testing.example end def test_tasks_in_nested_namespace_should_be_able_to_call_tasks_in_same_namespace @config.namespace(:outer) do task(:first) { set :called_first, true } namespace(:inner) do task(:first) { set :called_inner_first, true } task(:second) { first } end end @config.outer.inner.second assert !@config[:called_first] assert @config[:called_inner_first] end def test_tasks_in_nested_namespace_should_be_able_to_call_tasks_in_parent_namespace @config.namespace(:outer) do task(:first) { set :called_first, true } namespace(:inner) do task(:second) { first } end end @config.outer.inner.second assert @config[:called_first] end def test_tasks_in_nested_namespace_should_be_able_to_call_shadowed_tasks_in_parent_namespace @config.namespace(:outer) do task(:first) { set :called_first, true } namespace(:inner) do task(:first) { set :called_inner_first, true } task(:second) { parent.first } end end @config.outer.inner.second assert @config[:called_first] assert !@config[:called_inner_first] end end capistrano-2.12.0/test/logger_test.rb0000644000004100000410000000740711746743503017633 0ustar www-datawww-datarequire "utils" require 'capistrano/logger' require 'stringio' class LoggerTest < Test::Unit::TestCase def setup @io = StringIO.new @logger = Capistrano::Logger.new(:output => @io) end def test_logger_should_use_STDERR_by_default logger = Capistrano::Logger.new assert_equal STDERR, logger.device end def test_logger_should_use_output_option_if_output_responds_to_puts logger = Capistrano::Logger.new(:output => STDOUT) assert_equal STDOUT, logger.device end def test_logger_should_open_file_if_output_does_not_respond_to_puts File.expects(:open).with("logs/capistrano.log", "a").returns(:mock) logger = Capistrano::Logger.new(:output => "logs/capistrano.log") assert_equal :mock, logger.device end def test_close_should_not_close_device_if_device_is_default logger = Capistrano::Logger.new logger.device.expects(:close).never logger.close end def test_close_should_not_close_device_is_device_is_explicitly_given logger = Capistrano::Logger.new(:output => STDOUT) STDOUT.expects(:close).never logger.close end def test_close_should_close_device_when_device_was_implicitly_opened f = mock("file", :close => nil) File.expects(:open).with("logs/capistrano.log", "a").returns(f) logger = Capistrano::Logger.new(:output => "logs/capistrano.log") logger.close end def test_log_with_level_greater_than_threshold_should_ignore_message @logger.level = 3 @logger.log(4, "message") assert @io.string.empty? end def test_log_with_level_equal_to_threshold_should_log_message @logger.level = 3 @logger.log(3, "message") assert @io.string.include?("message") end def test_log_with_level_less_than_threshold_should_log_message @logger.level = 3 @logger.log(2, "message") assert @io.string.include?("message") end def test_log_with_multiline_message_should_log_each_line_separately @logger.log(0, "first line\nsecond line") assert @io.string.include?("*** first line") assert @io.string.include?("*** second line") end def test_log_with_line_prefix_should_insert_line_prefix_before_message @logger.log(0, "message", "prefix") assert @io.string.include?("*** [prefix] message") end def test_log_with_level_0_should_have_strong_indent @logger.log(0, "message") assert @io.string.match(/^\*\*\* message/) end def test_log_with_level_1_should_have_weaker_indent @logger.level = 1 @logger.log(1, "message") assert @io.string.match(/^ \*\* message/) end def test_log_with_level_2_should_have_weaker_indent @logger.level = 2 @logger.log(2, "message") assert @io.string.match(/^ \* message/) end def test_log_with_level_3_should_have_weakest_indent @logger.level = 3 @logger.log(3, "message") assert @io.string.match(/^ message/) end def test_important_should_delegate_to_log_with_level_IMPORTANT @logger.expects(:log).with(Capistrano::Logger::IMPORTANT, "message", "prefix") @logger.important("message", "prefix") end def test_info_should_delegate_to_log_with_level_INFO @logger.expects(:log).with(Capistrano::Logger::INFO, "message", "prefix") @logger.info("message", "prefix") end def test_debug_should_delegate_to_log_with_level_DEBUG @logger.expects(:log).with(Capistrano::Logger::DEBUG, "message", "prefix") @logger.debug("message", "prefix") end def test_trace_should_delegate_to_log_with_level_TRACE @logger.expects(:log).with(Capistrano::Logger::TRACE, "message", "prefix") @logger.trace("message", "prefix") end def test_ordering_of_levels assert Capistrano::Logger::IMPORTANT < Capistrano::Logger::INFO assert Capistrano::Logger::INFO < Capistrano::Logger::DEBUG assert Capistrano::Logger::DEBUG < Capistrano::Logger::TRACE end end capistrano-2.12.0/test/task_definition_test.rb0000644000004100000410000000747411746743503021532 0ustar www-datawww-datarequire "utils" require 'capistrano/task_definition' # Silences the wanrnings raised in the two deprecation tests $VERBOSE = nil class TaskDefinitionTest < Test::Unit::TestCase def setup @namespace = namespace end def test_fqn_at_top_level_should_be_task_name task = new_task(:testing) assert_equal "testing", task.fully_qualified_name end def test_fqn_in_namespace_should_include_namespace_fqn ns = namespace("outer:inner") task = new_task(:testing, ns) assert_equal "outer:inner:testing", task.fully_qualified_name end def test_fqn_at_top_level_when_default_should_be_default task = new_task(:default) assert_equal "default", task.fully_qualified_name end def test_name_should_change_task_name task = new_task(:foo) task.name = :bar assert_equal :bar, task.name end def test_raise_an_exception_when_task_names_can_not_be_converted task = new_task('valid task name') assert_raises(ArgumentError) { task.name = ['invalid task name'] } end def test_fqn_in_namespace_when_default_should_be_namespace_fqn ns = namespace("outer:inner") task = new_task(:default, ns) ns.stubs(:default_task => task) assert_equal "outer:inner", task.fully_qualified_name end def test_task_should_require_block assert_raises(ArgumentError) do Capistrano::TaskDefinition.new(:testing, @namespace) end end def test_description_should_return_empty_string_if_not_given assert_equal "", new_task(:testing).description end def test_description_should_return_desc_attribute assert_equal "something", new_task(:testing, @namespace, :desc => "something").description end def test_description_should_strip_leading_and_trailing_whitespace assert_equal "something", new_task(:testing, @namespace, :desc => " something ").description end def test_description_should_normalize_newlines assert_equal "a\nb\nc", new_task(:testing, @namespace, :desc => "a\nb\r\nc").description end def test_description_should_detect_and_remove_indentation desc = <<-DESC Here is some indented text \ and I want all of this to \ run together on a single line, \ without any extraneous spaces. additional indentation will be preserved. DESC task = new_task(:testing, @namespace, :desc => desc) assert_equal "Here is some indented text and I want all of this to run together on a single line, without any extraneous spaces.\n\n additional indentation will\n be preserved.", task.description end def test_description_munging_should_be_sensitive_to_code_blocks desc = <<-DESC Here is a line \ wrapped with spacing in it. foo bar baz bang DESC task = new_task(:testing, @namespace, :desc => desc) assert_equal "Here is a line wrapped with spacing in it.\n\n foo bar\n baz bang", task.description end def test_brief_description_should_return_first_sentence_in_description desc = "This is the task. It does all kinds of things." task = new_task(:testing, @namespace, :desc => desc) assert_equal "This is the task.", task.brief_description end def test_brief_description_should_truncate_if_length_given desc = "This is the task that does all kinds of things. And then some." task = new_task(:testing, @namespace, :desc => desc) assert_equal "This is the task ...", task.brief_description(20) end def test_brief_description_should_not_break_at_period_in_middle_of_sentence task = new_task(:testing, @namespace, :desc => "Take file.txt and copy it.") assert_equal "Take file.txt and copy it.", task.brief_description task = new_task(:testing, @namespace, :desc => "Take file.txt and copy it. Then do something else.") assert_equal "Take file.txt and copy it.", task.brief_description end end capistrano-2.12.0/test/deploy/0000755000004100000410000000000011746743503016254 5ustar www-datawww-datacapistrano-2.12.0/test/deploy/strategy/0000755000004100000410000000000011746743503020116 5ustar www-datawww-datacapistrano-2.12.0/test/deploy/strategy/copy_test.rb0000644000004100000410000003206011746743503022455 0ustar www-datawww-datarequire "utils" require 'capistrano/logger' require 'capistrano/recipes/deploy/strategy/copy' require 'stringio' class DeployStrategyCopyTest < Test::Unit::TestCase def setup @config = { :application => "captest", :logger => Capistrano::Logger.new(:output => StringIO.new), :releases_path => "/u/apps/test/releases", :release_path => "/u/apps/test/releases/1234567890", :real_revision => "154" } @source = mock("source") @config.stubs(:source).returns(@source) @strategy = Capistrano::Deploy::Strategy::Copy.new(@config) end def test_deploy_with_defaults_should_use_remote_gtar @config[:copy_remote_tar] = 'gtar' Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) Dir.expects(:chdir).with("/temp/dir").yields @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") @strategy.expects(:upload).with("/temp/dir/1234567890.tar.gz", "/tmp/1234567890.tar.gz") @strategy.expects(:run).with("cd /u/apps/test/releases && gtar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") @strategy.deploy! end def test_deploy_with_defaults_should_use_local_gtar @config[:copy_local_tar] = 'gtar' Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) Dir.expects(:chdir).with("/temp/dir").yields @strategy.expects(:system).with("gtar czf 1234567890.tar.gz 1234567890") @strategy.expects(:upload).with("/temp/dir/1234567890.tar.gz", "/tmp/1234567890.tar.gz") @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") @strategy.deploy! end def test_deploy_with_defaults_should_use_tar_gz_and_checkout Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) prepare_standard_compress_and_copy! @strategy.deploy! end def test_deploy_with_exclusions_should_remove_patterns_from_destination @config[:copy_exclude] = ".git" Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) Dir.expects(:glob).with("/temp/dir/1234567890/.git", File::FNM_DOTMATCH).returns(%w(/temp/dir/1234567890/.git)) FileUtils.expects(:rm_rf).with(%w(/temp/dir/1234567890/.git)) prepare_standard_compress_and_copy! @strategy.deploy! end def test_deploy_with_exclusions_should_remove_glob_patterns_from_destination @config[:copy_exclude] = ".gi*" Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) Dir.expects(:glob).with("/temp/dir/1234567890/.gi*", File::FNM_DOTMATCH).returns(%w(/temp/dir/1234567890/.git)) FileUtils.expects(:rm_rf).with(%w(/temp/dir/1234567890/.git)) prepare_standard_compress_and_copy! @strategy.deploy! end def test_deploy_with_export_should_use_tar_gz_and_export Dir.expects(:tmpdir).returns("/temp/dir") @config[:copy_strategy] = :export @source.expects(:export).with("154", "/temp/dir/1234567890").returns(:local_export) @strategy.expects(:system).with(:local_export) prepare_standard_compress_and_copy! @strategy.deploy! end def test_deploy_with_zip_should_use_zip_and_checkout Dir.expects(:tmpdir).returns("/temp/dir") Dir.expects(:chdir).with("/temp/dir").yields @config[:copy_compression] = :zip @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) @strategy.expects(:system).with("zip -qyr 1234567890.zip 1234567890") @strategy.expects(:upload).with("/temp/dir/1234567890.zip", "/tmp/1234567890.zip") @strategy.expects(:run).with("cd /u/apps/test/releases && unzip -q /tmp/1234567890.zip && rm /tmp/1234567890.zip") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/temp/dir/1234567890.zip") FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") @strategy.deploy! end def test_deploy_with_bzip2_should_use_bz2_and_checkout Dir.expects(:tmpdir).returns("/temp/dir") Dir.expects(:chdir).with("/temp/dir").yields @config[:copy_compression] = :bzip2 @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) @strategy.expects(:system).with("tar cjf 1234567890.tar.bz2 1234567890") @strategy.expects(:upload).with("/temp/dir/1234567890.tar.bz2", "/tmp/1234567890.tar.bz2") @strategy.expects(:run).with("cd /u/apps/test/releases && tar xjf /tmp/1234567890.tar.bz2 && rm /tmp/1234567890.tar.bz2") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.bz2") FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") @strategy.deploy! end def test_deploy_with_unknown_compression_type_should_error @config[:copy_compression] = :bogus Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.stubs(:system) File.stubs(:open) assert_raises(ArgumentError) { @strategy.deploy! } end def test_deploy_with_custom_copy_dir_should_use_that_as_tmpdir Dir.expects(:tmpdir).never Dir.expects(:chdir).with("/other/path").yields @config[:copy_dir] = "/other/path" @source.expects(:checkout).with("154", "/other/path/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") @strategy.expects(:upload).with("/other/path/1234567890.tar.gz", "/tmp/1234567890.tar.gz") @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/other/path/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/other/path/1234567890.tar.gz") FileUtils.expects(:rm_rf).with("/other/path/1234567890") @strategy.deploy! end def test_deploy_with_copy_remote_dir_should_copy_to_that_dir @config[:copy_remote_dir] = "/somewhere/else" Dir.expects(:tmpdir).returns("/temp/dir") Dir.expects(:chdir).yields @source.expects(:checkout).returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") @strategy.expects(:upload).with("/temp/dir/1234567890.tar.gz", "/somewhere/else/1234567890.tar.gz") @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /somewhere/else/1234567890.tar.gz && rm /somewhere/else/1234567890.tar.gz") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") @strategy.deploy! end def test_with_copy_cache_should_checkout_to_cache_if_cache_does_not_exist_and_then_copy @config[:copy_cache] = true Dir.stubs(:tmpdir).returns("/temp/dir") File.expects(:exists?).with("/temp/dir/captest").returns(false) Dir.expects(:chdir).with("/temp/dir/captest").yields @source.expects(:checkout).with("154", "/temp/dir/captest").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") prepare_directory_tree!("/temp/dir/captest") prepare_standard_compress_and_copy! @strategy.deploy! end def test_with_copy_cache_should_update_cache_if_cache_exists_and_then_copy @config[:copy_cache] = true Dir.stubs(:tmpdir).returns("/temp/dir") File.expects(:exists?).with("/temp/dir/captest").returns(true) Dir.expects(:chdir).with("/temp/dir/captest").yields @source.expects(:sync).with("154", "/temp/dir/captest").returns(:local_sync) @strategy.expects(:system).with(:local_sync) FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") prepare_directory_tree!("/temp/dir/captest") prepare_standard_compress_and_copy! @strategy.deploy! end def test_with_copy_cache_with_custom_absolute_cache_dir_path_should_use_specified_cache_dir @config[:copy_cache] = "/u/caches/captest" Dir.stubs(:tmpdir).returns("/temp/dir") File.expects(:exists?).with("/u/caches/captest").returns(true) Dir.expects(:chdir).with("/u/caches/captest").yields @source.expects(:sync).with("154", "/u/caches/captest").returns(:local_sync) @strategy.expects(:system).with(:local_sync) FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") prepare_directory_tree!("/u/caches/captest") prepare_standard_compress_and_copy! @strategy.deploy! end def test_with_copy_cache_with_custom_relative_cache_dir_path_should_use_specified_cache_dir @config[:copy_cache] = "caches/captest" Dir.stubs(:pwd).returns("/u") Dir.stubs(:tmpdir).returns("/temp/dir") File.expects(:exists?).with("/u/caches/captest").returns(true) Dir.expects(:chdir).with("/u/caches/captest").yields @source.expects(:sync).with("154", "/u/caches/captest").returns(:local_sync) @strategy.expects(:system).with(:local_sync) FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") prepare_directory_tree!("/u/caches/captest") prepare_standard_compress_and_copy! @strategy.deploy! end def test_with_copy_cache_with_excludes_should_not_copy_excluded_files @config[:copy_cache] = true @config[:copy_exclude] = "*/bar.txt" Dir.stubs(:tmpdir).returns("/temp/dir") File.expects(:exists?).with("/temp/dir/captest").returns(true) Dir.expects(:chdir).with("/temp/dir/captest").yields @source.expects(:sync).with("154", "/temp/dir/captest").returns(:local_sync) @strategy.expects(:system).with(:local_sync) FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") prepare_directory_tree!("/temp/dir/captest", true) prepare_standard_compress_and_copy! @strategy.deploy! end def test_with_build_script_should_run_script @config[:build_script] = "mkdir bin" Dir.expects(:tmpdir).returns("/temp/dir") @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) @strategy.expects(:system).with(:local_checkout) Dir.expects(:chdir).with("/temp/dir/1234567890").yields @strategy.expects(:system).with("mkdir bin") prepare_standard_compress_and_copy! @strategy.deploy! end private def prepare_directory_tree!(cache, exclude=false) Dir.expects(:glob).with("*", File::FNM_DOTMATCH).returns([".", "..", "app", "foo.txt"]) File.expects(:ftype).with("app").returns("directory") FileUtils.expects(:mkdir).with("/temp/dir/1234567890/app") File.expects(:ftype).with("foo.txt").returns("file") FileUtils.expects(:ln).with("foo.txt", "/temp/dir/1234567890/foo.txt") Dir.expects(:glob).with("app/*", File::FNM_DOTMATCH).returns(["app/.", "app/..", "app/bar.txt"]) unless exclude File.expects(:ftype).with("app/bar.txt").returns("file") FileUtils.expects(:ln).with("app/bar.txt", "/temp/dir/1234567890/app/bar.txt") end end def prepare_standard_compress_and_copy! Dir.expects(:chdir).with("/temp/dir").yields @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") @strategy.expects(:upload).with("/temp/dir/1234567890.tar.gz", "/tmp/1234567890.tar.gz") @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") mock_file = mock("file") mock_file.expects(:puts).with("154") File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") end end capistrano-2.12.0/test/deploy/scm/0000755000004100000410000000000011746743503017036 5ustar www-datawww-datacapistrano-2.12.0/test/deploy/scm/none_test.rb0000644000004100000410000000161711746743503021366 0ustar www-datawww-datarequire 'utils' require 'capistrano/recipes/deploy/scm/none' class DeploySCMNoneTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::None default_command 'none' end def setup @config = {} def @config.exists?(name); key?(name); end @source = TestSCM.new(@config) end def test_the_truth assert true end def test_checkout_on_linux Capistrano::Deploy::LocalDependency.stubs(:on_windows?).returns(false) @config[:repository] = '.' rev = '' dest = '/var/www' assert_equal "cp -R . /var/www", @source.checkout(rev, dest) end def test_checkout_on_windows Capistrano::Deploy::LocalDependency.stubs(:on_windows?).returns(true) @config[:repository] = '.' rev = '' dest = 'c:/Documents and settings/admin/tmp' assert_equal "xcopy . \"c:/Documents and settings/admin/tmp\" /S/I/Y/Q/E", @source.checkout(rev, dest) end end capistrano-2.12.0/test/deploy/scm/base_test.rb0000644000004100000410000000352311746743503021337 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/base' class DeploySCMBaseTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Base default_command "floopy" end def setup @config = { } def @config.exists?(name); key?(name); end @source = TestSCM.new(@config) end def test_command_should_default_to_default_command assert_equal "floopy", @source.command @source.local { assert_equal "floopy", @source.command } end def test_command_should_use_scm_command_if_available @config[:scm_command] = "/opt/local/bin/floopy" assert_equal "/opt/local/bin/floopy", @source.command end def test_command_should_use_scm_command_in_local_mode_if_local_scm_command_not_set @config[:scm_command] = "/opt/local/bin/floopy" @source.local { assert_equal "/opt/local/bin/floopy", @source.command } end def test_command_should_use_local_scm_command_in_local_mode_if_local_scm_command_is_set @config[:scm_command] = "/opt/local/bin/floopy" @config[:local_scm_command] = "/usr/local/bin/floopy" assert_equal "/opt/local/bin/floopy", @source.command @source.local { assert_equal "/usr/local/bin/floopy", @source.command } end def test_command_should_use_default_if_scm_command_is_default @config[:scm_command] = :default assert_equal "floopy", @source.command end def test_command_should_use_default_in_local_mode_if_local_scm_command_is_default @config[:scm_command] = "/foo/bar/floopy" @config[:local_scm_command] = :default @source.local { assert_equal "floopy", @source.command } end def test_local_mode_proxy_should_treat_messages_as_being_in_local_mode @config[:scm_command] = "/foo/bar/floopy" @config[:local_scm_command] = :default assert_equal "floopy", @source.local.command assert_equal "/foo/bar/floopy", @source.command end end capistrano-2.12.0/test/deploy/scm/git_test.rb0000644000004100000410000002122711746743503021211 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/git' class DeploySCMGitTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Git default_command "git" end def setup @config = { :repository => "." } def @config.exists?(name); key?(name); end @source = TestSCM.new(@config) end def test_head assert_equal "HEAD", @source.head # With :branch @config[:branch] = "master" assert_equal "master", @source.head end def test_origin assert_equal "origin", @source.origin @config[:remote] = "username" assert_equal "username", @source.origin end def test_checkout @config[:repository] = "git@somehost.com:project.git" dest = "/var/www" rev = 'c2d9e79' assert_equal "git clone -q git@somehost.com:project.git /var/www && cd /var/www && git checkout -q -b deploy #{rev}", @source.checkout(rev, dest) # With :scm_command git = "/opt/local/bin/git" @config[:scm_command] = git assert_equal "#{git} clone -q git@somehost.com:project.git /var/www && cd /var/www && #{git} checkout -q -b deploy #{rev}", @source.checkout(rev, dest).gsub(/\s+/, ' ') # with submodules @config[:git_enable_submodules] = true assert_equal "#{git} clone -q git@somehost.com:project.git /var/www && cd /var/www && #{git} checkout -q -b deploy #{rev} && #{git} submodule -q init && #{git} submodule -q sync && export GIT_RECURSIVE=$([ ! \"`#{git} --version`\" \\< \"git version 1.6.5\" ] && echo --recursive) && #{git} submodule -q update --init $GIT_RECURSIVE", @source.checkout(rev, dest).gsub(/\s+/, ' ') end def test_checkout_submodules_without_recursive @config[:repository] = "git@somehost.com:project.git" dest = "/var/www" rev = 'c2d9e79' @config[:git_enable_submodules] = true @config[:git_submodules_recursive] = false assert_equal "git clone -q git@somehost.com:project.git /var/www && cd /var/www && git checkout -q -b deploy #{rev} && git submodule -q init && git submodule -q sync && git submodule -q update --init", @source.checkout(rev, dest).gsub(/\s+/, ' ') end def test_checkout_with_verbose_should_not_use_q_switch @config[:repository] = "git@somehost.com:project.git" @config[:scm_verbose] = true dest = "/var/www" rev = 'c2d9e79' assert_equal "git clone git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy #{rev}", @source.checkout(rev, dest) end def test_checkout_with_verbose_off_should_use_q_switch @config[:repository] = "git@somehost.com:project.git" @config[:scm_verbose] = false dest = "/var/www" rev = 'c2d9e79' assert_equal "git clone -q git@somehost.com:project.git /var/www && cd /var/www && git checkout -q -b deploy #{rev}", @source.checkout(rev, dest) end def test_diff assert_equal "git diff master", @source.diff('master') assert_equal "git diff master..branch", @source.diff('master', 'branch') end def test_log assert_equal "git log master..", @source.log('master') assert_equal "git log master..branch", @source.log('master', 'branch') end def test_query_revision_from_remote revision = @source.query_revision('HEAD') do |o| assert_equal "git ls-remote . HEAD", o "d11006102c07c94e5d54dd0ee63dca825c93ed61\tHEAD" end assert_equal "d11006102c07c94e5d54dd0ee63dca825c93ed61", revision end def test_query_revision_falls_back_to_local revision = @source.query_revision('d11006') do |o| return nil if o == "git ls-remote . d11006" assert_equal "git rev-parse --revs-only d11006", o "d11006102c07c94e5d54dd0ee63dca825c93ed61" end assert_equal "d11006102c07c94e5d54dd0ee63dca825c93ed61", revision end def test_query_revision_has_whitespace revision = @source.query_revision('HEAD') do |o| assert_equal "git ls-remote . HEAD", o "d11006102c07c94e5d54dd0ee63dca825c93ed61\tHEAD\r" end assert_equal "d11006102c07c94e5d54dd0ee63dca825c93ed61", revision end def test_query_revision_deprecation_error assert_raise(ArgumentError) do revision = @source.query_revision('origin/release') {} end end def test_command_should_be_backwards_compatible # 1.x version of this module used ":git", not ":scm_command" @config[:git] = "/srv/bin/git" assert_equal "/srv/bin/git", @source.command end def test_sync dest = "/var/www" rev = 'c2d9e79' assert_equal "cd #{dest} && git fetch -q origin && git fetch --tags -q origin && git reset -q --hard #{rev} && git clean -q -d -x -f", @source.sync(rev, dest) # With :scm_command git = "/opt/local/bin/git" @config[:scm_command] = git assert_equal "cd #{dest} && #{git} fetch -q origin && #{git} fetch --tags -q origin && #{git} reset -q --hard #{rev} && #{git} clean -q -d -x -f", @source.sync(rev, dest) # with submodules @config[:git_enable_submodules] = true assert_equal "cd #{dest} && #{git} fetch -q origin && #{git} fetch --tags -q origin && #{git} reset -q --hard #{rev} && #{git} submodule -q init && for mod in `#{git} submodule status | awk '{ print $2 }'`; do #{git} config -f .git/config submodule.${mod}.url `#{git} config -f .gitmodules --get submodule.${mod}.url` && echo Synced $mod; done && #{git} submodule -q sync && export GIT_RECURSIVE=$([ ! \"`#{git} --version`\" \\< \"git version 1.6.5\" ] && echo --recursive) && #{git} submodule -q update --init $GIT_RECURSIVE && #{git} clean -q -d -x -f", @source.sync(rev, dest) end def test_sync_with_remote dest = "/var/www" rev = 'c2d9e79' remote = "username" repository = "git@somehost.com:project.git" @config[:repository] = repository @config[:remote] = remote assert_equal "cd #{dest} && git config remote.#{remote}.url #{repository} && git config remote.#{remote}.fetch +refs/heads/*:refs/remotes/#{remote}/* && git fetch -q #{remote} && git fetch --tags -q username && git reset -q --hard #{rev} && git clean -q -d -x -f", @source.sync(rev, dest) end def test_shallow_clone @config[:repository] = "git@somehost.com:project.git" @config[:git_shallow_clone] = 1 dest = "/var/www" rev = 'c2d9e79' assert_equal "git clone -q --depth 1 git@somehost.com:project.git /var/www && cd /var/www && git checkout -q -b deploy #{rev}", @source.checkout(rev, dest) end def test_remote_clone @config[:repository] = "git@somehost.com:project.git" @config[:remote] = "username" dest = "/var/www" rev = 'c2d9e79' assert_equal "git clone -q -o username git@somehost.com:project.git /var/www && cd /var/www && git checkout -q -b deploy #{rev}", @source.checkout(rev, dest) end def test_remote_clone_with_submodules @config[:repository] = "git@somehost.com:project.git" @config[:remote] = "username" @config[:git_enable_submodules] = true dest = "/var/www" rev = 'c2d9e79' assert_equal "git clone -q -o username git@somehost.com:project.git /var/www && cd /var/www && git checkout -q -b deploy #{rev} && git submodule -q init && git submodule -q sync && export GIT_RECURSIVE=$([ ! \"`git --version`\" \\< \"git version 1.6.5\" ] && echo --recursive) && git submodule -q update --init $GIT_RECURSIVE", @source.checkout(rev, dest) end # Tests from base_test.rb, makin' sure we didn't break anything up there! def test_command_should_default_to_default_command assert_equal "git", @source.command @source.local { assert_equal "git", @source.command } end def test_command_should_use_scm_command_if_available @config[:scm_command] = "/opt/local/bin/git" assert_equal "/opt/local/bin/git", @source.command end def test_command_should_use_scm_command_in_local_mode_if_local_scm_command_not_set @config[:scm_command] = "/opt/local/bin/git" @source.local { assert_equal "/opt/local/bin/git", @source.command } end def test_command_should_use_local_scm_command_in_local_mode_if_local_scm_command_is_set @config[:scm_command] = "/opt/local/bin/git" @config[:local_scm_command] = "/usr/local/bin/git" assert_equal "/opt/local/bin/git", @source.command @source.local { assert_equal "/usr/local/bin/git", @source.command } end def test_command_should_use_default_if_scm_command_is_default @config[:scm_command] = :default assert_equal "git", @source.command end def test_command_should_use_default_in_local_mode_if_local_scm_command_is_default @config[:scm_command] = "/foo/bar/git" @config[:local_scm_command] = :default @source.local { assert_equal "git", @source.command } end def test_local_mode_proxy_should_treat_messages_as_being_in_local_mode @config[:scm_command] = "/foo/bar/git" @config[:local_scm_command] = :default assert_equal "git", @source.local.command assert_equal "/foo/bar/git", @source.command end end capistrano-2.12.0/test/deploy/scm/darcs_test.rb0000644000004100000410000000234311746743503021520 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/darcs' class DeploySCMDarcsTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Darcs default_command "darcs" end def setup @config = { :repository => "." } # def @config.exists?(name); key?(name); end @source = TestSCM.new(@config) end # We should be able to pick a specific hash. def test_checkout_hash hsh = "*version_hash*" assert_match(%r{--to-match=.hash #{Regexp.quote(hsh)}}, @source.checkout(hsh, "*foo_location*"), "Specifying a revision hash got the --to-match option wrong.") end # Picking the head revision should leave out the hash, because head is the # default and we don't have a HEAD pseudotag def test_checkout_head hsh = @source.head assert_no_match(%r{--to-match}, @source.checkout(hsh, "*foo_location*"), "Selecting the head revision incorrectly produced a --to-match option.") end # Leaving the revision as nil shouldn't break anything. def test_checkout_nil assert_no_match(%r{--to-match}, @source.checkout(nil, "*foo_location*"), "Leaving the revision as nil incorrectly produced a --to-match option.") end end capistrano-2.12.0/test/deploy/scm/mercurial_test.rb0000644000004100000410000001062611746743503022412 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/mercurial' class DeploySCMMercurialTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Mercurial default_command "hg" end def setup @config = { } def @config.exists?(name); key?(name); end @source = TestSCM.new(@config) end def test_head assert_equal "tip", @source.head end def test_different_head @config[:branch] = "staging" assert_equal "staging", @source.head end def test_checkout @config[:repository] = "http://example.com/project-hg" dest = "/var/www" assert_equal "hg clone --noupdate http://example.com/project-hg /var/www && hg update --repository /var/www --clean 8a8e00b8f11b", @source.checkout('8a8e00b8f11b', dest) end def test_diff assert_equal "hg diff --rev tip", @source.diff('tip') assert_equal "hg diff --rev 1 --rev 2", @source.diff('1', '2') end def test_log assert_equal "hg log --rev 8a8e00b8f11b", @source.log('8a8e00b8f11b') assert_equal "hg log --rev 0:3", @source.log('0', '3') end def test_query_revision assert_equal "hg log -r 8a8e00b8f11b --template \"{node|short}\"", @source.query_revision('8a8e00b8f11b') { |o| o } end def test_username_should_be_backwards_compatible # older versions of this module required :scm_user var instead # of the currently preferred :scm_username require 'capistrano/logger' @config[:scm_user] = "fred" text = "user:" assert_equal "fred\n", @source.handle_data(mock_state, :test_stream, text) # :scm_username takes priority @config[:scm_username] = "wilma" assert_equal "wilma\n", @source.handle_data(mock_state, :test_stream, text) end def test_sync dest = "/var/www" assert_equal "hg pull --repository /var/www && hg update --repository /var/www --clean 8a8e00b8f11b", @source.sync('8a8e00b8f11b', dest) # With :scm_command @config[:scm_command] = "/opt/local/bin/hg" assert_equal "/opt/local/bin/hg pull --repository /var/www && /opt/local/bin/hg update --repository /var/www --clean 8a8e00b8f11b", @source.sync('8a8e00b8f11b', dest) end def test_export dest = "/var/www" assert_raise(NotImplementedError) { @source.export('8a8e00b8f11b', dest) } end def test_sends_password_if_set require 'capistrano/logger' text = "password:" @config[:scm_password] = "opensesame" assert_equal "opensesame\n", @source.handle_data(mock_state, :test_stream, text) end def test_prompts_for_password_if_preferred require 'capistrano/logger' require 'capistrano/cli' Capistrano::CLI.stubs(:password_prompt).with("hg password: ").returns("opensesame") @config[:scm_prefer_prompt] = true text = "password:" assert_equal "opensesame\n", @source.handle_data(mock_state, :test_stream, text) end # Tests from base_test.rb, makin' sure we didn't break anything up there! def test_command_should_default_to_default_command assert_equal "hg", @source.command @source.local { assert_equal "hg", @source.command } end def test_command_should_use_scm_command_if_available @config[:scm_command] = "/opt/local/bin/hg" assert_equal "/opt/local/bin/hg", @source.command end def test_command_should_use_scm_command_in_local_mode_if_local_scm_command_not_set @config[:scm_command] = "/opt/local/bin/hg" @source.local { assert_equal "/opt/local/bin/hg", @source.command } end def test_command_should_use_local_scm_command_in_local_mode_if_local_scm_command_is_set @config[:scm_command] = "/opt/local/bin/hg" @config[:local_scm_command] = "/usr/local/bin/hg" assert_equal "/opt/local/bin/hg", @source.command @source.local { assert_equal "/usr/local/bin/hg", @source.command } end def test_command_should_use_default_if_scm_command_is_default @config[:scm_command] = :default assert_equal "hg", @source.command end def test_command_should_use_default_in_local_mode_if_local_scm_command_is_default @config[:scm_command] = "/foo/bar/hg" @config[:local_scm_command] = :default @source.local { assert_equal "hg", @source.command } end def test_local_mode_proxy_should_treat_messages_as_being_in_local_mode @config[:scm_command] = "/foo/bar/hg" @config[:local_scm_command] = :default assert_equal "hg", @source.local.command assert_equal "/foo/bar/hg", @source.command end private def mock_state { :channel => { :host => "abc" } } end end capistrano-2.12.0/test/deploy/scm/bzr_test.rb0000644000004100000410000000355411746743503021226 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/bzr' class DeploySCMBzrTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Bzr default_command "bzr" end def setup @config = { :repository => "." } def @config.exists?(name); key?(name); end # is this actually needed? @source = TestSCM.new(@config) end # The bzr scm does not support pseudo-ids. The bzr adapter uses symbol :head # to refer to the recently committed revision. def test_head_revision assert_equal(:head, @source.head, "Since bzr doesn't know a real head revision, symbol :head is used instead.") end # The bzr scm does support many different ways to specify a revision. Only # symbol :head triggers the bzr command 'revno'. def test_query_revision assert_equal("bzr revno #{@config[:repository]}", @source.query_revision(:head) { |o| o }, "Query for :head revision should call bzr command 'revno' in repository directory.") # Many valid revision specifications, some invalid on the last line revision_samples = [ 5, -7, '2', '-4', 'revid:revid:aaaa@bbbb-123456789', 'submit:', 'ancestor:/path/to/branch', 'date:yesterday', 'branch:/path/to/branch', 'tag:trunk', 'revno:3:/path/to/branch', 'before:revid:aaaa@bbbb-1234567890', 'last:3', nil, {}, [], true, false, 1.34, ] revision_samples.each do |revivsion_spec| assert_equal(revivsion_spec, @source.query_revision(revivsion_spec), "Any revision specification other than symbol :head should simply by returned.") end end end capistrano-2.12.0/test/deploy/scm/accurev_test.rb0000644000004100000410000000136611746743503022060 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/accurev' class AccurevTest < Test::Unit::TestCase include Capistrano::Deploy::SCM def test_internal_revision_to_s assert_equal 'foo/1', Accurev::InternalRevision.new('foo', 1).to_s assert_equal 'foo/highest', Accurev::InternalRevision.new('foo', 'highest').to_s end def test_internal_revision_parse revision = Accurev::InternalRevision.parse('foo') assert_equal 'foo', revision.stream assert_equal 'highest', revision.transaction_id assert_equal 'foo/highest', revision.to_s revision = Accurev::InternalRevision.parse('foo/1') assert_equal 'foo', revision.stream assert_equal '1', revision.transaction_id assert_equal 'foo/1', revision.to_s end end capistrano-2.12.0/test/deploy/scm/subversion_test.rb0000644000004100000410000000214111746743503022617 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/subversion' class DeploySCMSubversionTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Subversion default_command "svn" end def setup @config = { :repository => "." } def @config.exists?(name); key?(name); end @source = TestSCM.new(@config) end def test_query_revision revision = @source.query_revision('HEAD') do |o| assert_equal "svn info . -rHEAD", o %Q{Path: rails_2_3 URL: svn+ssh://example.com/var/repos/project/branches/rails_2_3 Repository Root: svn+ssh://example.com/var/repos Repository UUID: 2d86388d-c40f-0410-ad6a-a69da6a65d20 Revision: 2095 Node Kind: directory Last Changed Author: sw Last Changed Rev: 2064 Last Changed Date: 2009-03-11 11:04:25 -0700 (Wed, 11 Mar 2009) } end assert_equal 2095, revision end def test_sync @config[:repository] = "http://svn.github.com/capistrano/capistrano.git" rev = '602' dest = "/var/www" assert_equal "svn switch -q -r602 http://svn.github.com/capistrano/capistrano.git /var/www", @source.sync(rev, dest) end end capistrano-2.12.0/test/deploy/scm/perforce_test.rb0000644000004100000410000000110111746743503022220 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/scm/perforce' class DeploySCMPerforceTest < Test::Unit::TestCase class TestSCM < Capistrano::Deploy::SCM::Perforce default_command "perforce" end def setup @config = { :repository => "." } @source = TestSCM.new(@config) end def test_p4_label @config[:p4_label] = "some_p4_label" assert_equal "@some_p4_label", @source.send(:rev_no, 'foo') end def test_p4_label_with_symbol @config[:p4_label] = "@some_p4_label" assert_equal "@some_p4_label", @source.send(:rev_no, 'foo') end endcapistrano-2.12.0/test/deploy/local_dependency_test.rb0000644000004100000410000000535311746743503023136 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/local_dependency' class LocalDependencyTest < Test::Unit::TestCase def setup @config = { } @dependency = Capistrano::Deploy::LocalDependency.new(@config) end def test_should_use_standard_error_message setup_for_one_path_entry(false) @dependency.command("cat") assert_equal "`cat' could not be found in the path on the local host", @dependency.message end def test_should_use_alternative_message_if_provided setup_for_one_path_entry(false) @dependency.command("cat").or("Sorry") assert_equal "Sorry", @dependency.message end def test_env_with_no_path_should_never_find_command ENV.expects(:[]).with("PATH").returns(nil) assert !@dependency.command("cat").pass? end def test_env_with_one_path_entry_should_fail_if_command_not_found setup_for_one_path_entry(false) assert !@dependency.command("cat").pass? end def test_env_with_one_path_entry_should_pass_if_command_found setup_for_one_path_entry(true) assert @dependency.command("cat").pass? end def test_env_with_three_path_entries_should_fail_if_command_not_found setup_for_three_path_entries(false) assert !@dependency.command("cat").pass? end def test_env_with_three_path_entries_should_pass_if_command_found setup_for_three_path_entries(true) assert @dependency.command("cat").pass? end def test_env_with_one_path_entry_on_windows_should_pass_if_command_found_with_extension setup_for_one_path_entry_on_windows(true) assert @dependency.command("cat").pass? end private def setup_for_one_path_entry(command_found) Capistrano::Deploy::LocalDependency.expects(:on_windows?).returns(false) ENV.expects(:[]).with("PATH").returns("/bin") File.expects(:executable?).with("/bin/cat").returns(command_found) end def setup_for_three_path_entries(command_found) Capistrano::Deploy::LocalDependency.expects(:on_windows?).returns(false) path = %w(/bin /usr/bin /usr/local/bin).join(File::PATH_SEPARATOR) ENV.expects(:[]).with("PATH").returns(path) File.expects(:executable?).with("/usr/bin/cat").returns(command_found) File.expects(:executable?).at_most(1).with("/bin/cat").returns(false) File.expects(:executable?).at_most(1).with("/usr/local/bin/cat").returns(false) end def setup_for_one_path_entry_on_windows(command_found) Capistrano::Deploy::LocalDependency.expects(:on_windows?).returns(true) ENV.expects(:[]).with("PATH").returns("/cygwin/bin") File.stubs(:executable?).returns(false) first_executable_extension = Capistrano::Deploy::LocalDependency.windows_executable_extensions.first File.expects(:executable?).with("/cygwin/bin/cat#{first_executable_extension}").returns(command_found) end end capistrano-2.12.0/test/deploy/remote_dependency_test.rb0000644000004100000410000001175111746743503023336 0ustar www-datawww-datarequire "utils" require 'capistrano/recipes/deploy/remote_dependency' class RemoteDependencyTest < Test::Unit::TestCase def setup @config = { } @dependency = Capistrano::Deploy::RemoteDependency.new(@config) end def test_should_use_standard_error_message_for_directory setup_for_a_configuration_run("test -d /data", false) @dependency.directory("/data") assert_equal "`/data' is not a directory (host)", @dependency.message end def test_should_use_standard_error_message_for_file setup_for_a_configuration_run("test -f /data/foo.txt", false) @dependency.file("/data/foo.txt") assert_equal "`/data/foo.txt' is not a file (host)", @dependency.message end def test_should_use_standard_error_message_for_writable setup_for_a_configuration_run("test -w /data/foo.txt", false) @dependency.writable("/data/foo.txt") assert_equal "`/data/foo.txt' is not writable (host)", @dependency.message end def test_should_use_standard_error_message_for_command setup_for_a_configuration_run("which cat", false) @dependency.command("cat") assert_equal "`cat' could not be found in the path (host)", @dependency.message end def test_should_use_standard_error_message_for_gem setup_for_a_configuration_gem_run("capistrano", "9.9", false) @dependency.gem("capistrano", 9.9) assert_equal "gem `capistrano' 9.9 could not be found (host)", @dependency.message end def test_should_use_standard_error_message_for_deb setup_for_a_configuration_deb_run("dpkg", "1.15", false) @dependency.deb("dpkg", "1.15") assert_equal "package `dpkg' 1.15 could not be found (host)", @dependency.message end def test_should_use_standard_error_message_for_rpm setup_for_a_configuration_rpm_run("rpm", "4.8", false) @dependency.rpm("rpm", "4.8") assert_equal "package `rpm' 4.8 could not be found (host)", @dependency.message end def test_should_fail_if_directory_not_found setup_for_a_configuration_run("test -d /data", false) assert !@dependency.directory("/data").pass? end def test_should_pass_if_directory_found setup_for_a_configuration_run("test -d /data", true) assert @dependency.directory("/data").pass? end def test_should_fail_if_file_not_found setup_for_a_configuration_run("test -f /data/foo.txt", false) assert !@dependency.file("/data/foo.txt").pass? end def test_should_pass_if_file_found setup_for_a_configuration_run("test -f /data/foo.txt", true) assert @dependency.file("/data/foo.txt").pass? end def test_should_fail_if_writable_not_found setup_for_a_configuration_run("test -w /data/foo.txt", false) assert !@dependency.writable("/data/foo.txt").pass? end def test_should_pass_if_writable_found setup_for_a_configuration_run("test -w /data/foo.txt", true) assert @dependency.writable("/data/foo.txt").pass? end def test_should_fail_if_command_not_found setup_for_a_configuration_run("which cat", false) assert !@dependency.command("cat").pass? end def test_should_pass_if_command_found setup_for_a_configuration_run("which cat", true) assert @dependency.command("cat").pass? end def test_should_fail_if_gem_not_found setup_for_a_configuration_gem_run("capistrano", "9.9", false) assert !@dependency.gem("capistrano", 9.9).pass? end def test_should_pass_if_gem_found setup_for_a_configuration_gem_run("capistrano", "9.9", true) assert @dependency.gem("capistrano", 9.9).pass? end def test_should_pass_if_deb_found setup_for_a_configuration_deb_run("dpkg", "1.15", true) assert @dependency.deb("dpkg", "1.15").pass? end def test_should_fail_if_deb_not_found setup_for_a_configuration_deb_run("dpkg", "1.15", false) assert !@dependency.deb("dpkg", "1.15").pass? end def test_should_use_alternative_message_if_provided setup_for_a_configuration_run("which cat", false) @dependency.command("cat").or("Sorry") assert_equal "Sorry (host)", @dependency.message end private def setup_for_a_configuration_run(command, passing) expectation = @config.expects(:invoke_command).with(command, {}) if passing expectation.returns(true) else error = Capistrano::CommandError.new error.expects(:hosts).returns(["host"]) expectation.raises(error) end end def setup_for_a_configuration_gem_run(name, version, passing) @config.expects(:fetch).with(:gem_command, "gem").returns("gem") find_gem_cmd = "gem specification --version '#{version}' #{name} 2>&1 | awk 'BEGIN { s = 0 } /^name:/ { s = 1; exit }; END { if(s == 0) exit 1 }'" setup_for_a_configuration_run(find_gem_cmd, passing) end def setup_for_a_configuration_deb_run(name, version, passing) find_deb_cmd = "dpkg -s #{name} | grep '^Version: #{version}'" setup_for_a_configuration_run(find_deb_cmd, passing) end def setup_for_a_configuration_rpm_run(name, version, passing) find_rpm_cmd = "rpm -q #{name} | grep '#{version}'" setup_for_a_configuration_run(find_rpm_cmd, passing) end end capistrano-2.12.0/test/command_test.rb0000644000004100000410000002433011746743503017764 0ustar www-datawww-datarequire "utils" require 'capistrano/command' require 'capistrano/configuration' class CommandTest < Test::Unit::TestCase def test_command_should_open_channels_on_all_sessions s1, s2, s3 = mock_session, mock_session, mock_session assert_equal "ls", Capistrano::Command.new("ls", [s1, s2, s3]).tree.fallback.command end def test_command_with_newlines_should_be_properly_escaped cmd = Capistrano::Command.new("ls\necho", [mock_session]) assert_equal "ls\\\necho", cmd.tree.fallback.command end def test_command_with_windows_newlines_should_be_properly_escaped cmd = Capistrano::Command.new("ls\r\necho", [mock_session]) assert_equal "ls\\\necho", cmd.tree.fallback.command end def test_command_with_pty_should_request_pty_and_register_success_callback session = setup_for_extracting_channel_action(:request_pty, true) do |ch| ch.expects(:exec).with(%(sh -c 'ls')) end Capistrano::Command.new("ls", [session], :pty => true) end def test_command_with_env_key_should_have_environment_constructed_and_prepended session = setup_for_extracting_channel_action do |ch| ch.expects(:request_pty).never ch.expects(:exec).with(%(env FOO=bar sh -c 'ls')) end Capistrano::Command.new("ls", [session], :env => { "FOO" => "bar" }) end def test_env_with_symbolic_key_should_be_accepted_as_a_string session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(env FOO=bar sh -c 'ls')) end Capistrano::Command.new("ls", [session], :env => { :FOO => "bar" }) end def test_env_as_string_should_be_substituted_in_directly session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(env HOWDY=there sh -c 'ls')) end Capistrano::Command.new("ls", [session], :env => "HOWDY=there") end def test_env_with_symbolic_value_should_be_accepted_as_string session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(env FOO=bar sh -c 'ls')) end Capistrano::Command.new("ls", [session], :env => { "FOO" => :bar }) end def test_env_value_should_be_escaped session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(env FOO=(\\ \\\"bar\\\"\\ ) sh -c 'ls')) end Capistrano::Command.new("ls", [session], :env => { "FOO" => '( "bar" )' }) end def test_env_with_multiple_keys_should_chain_the_entries_together session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with do |command| command =~ /^env / && command =~ /\ba=b\b/ && command =~ /\bc=d\b/ && command =~ /\be=f\b/ && command =~ / sh -c 'ls'$/ end end Capistrano::Command.new("ls", [session], :env => { :a => :b, :c => :d, :e => :f }) end def test_open_channel_should_set_host_key_on_channel channel = nil session = setup_for_extracting_channel_action { |ch| channel = ch } Capistrano::Command.new("ls", [session]) assert_equal "capistrano", channel[:host] end def test_open_channel_should_set_options_key_on_channel channel = nil session = setup_for_extracting_channel_action { |ch| channel = ch } Capistrano::Command.new("ls", [session], :data => "here we go") assert_equal({ :data => 'here we go' }, channel[:options]) end def test_successful_channel_should_send_command session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(sh -c 'ls')) end Capistrano::Command.new("ls", [session]) end def test_successful_channel_with_shell_option_should_send_command_via_specified_shell session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(/bin/bash -c 'ls')) end Capistrano::Command.new("ls", [session], :shell => "/bin/bash") end def test_successful_channel_with_shell_false_should_send_command_without_shell session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(echo `hostname`)) end Capistrano::Command.new("echo `hostname`", [session], :shell => false) end def test_successful_channel_should_send_data_if_data_key_is_present session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(sh -c 'ls')) ch.expects(:send_data).with("here we go") end Capistrano::Command.new("ls", [session], :data => "here we go") end def test_unsuccessful_pty_request_should_close_channel session = setup_for_extracting_channel_action(:request_pty, false) do |ch| ch.expects(:close) end Capistrano::Command.new("ls", [session], :pty => true) end def test_on_data_should_invoke_callback_as_stdout session = setup_for_extracting_channel_action(:on_data, "hello") called = false Capistrano::Command.new("ls", [session]) do |ch, stream, data| called = true assert_equal :out, stream assert_equal "hello", data end assert called end def test_on_extended_data_should_invoke_callback_as_stderr session = setup_for_extracting_channel_action(:on_extended_data, 2, "hello") called = false Capistrano::Command.new("ls", [session]) do |ch, stream, data| called = true assert_equal :err, stream assert_equal "hello", data end assert called end def test_on_request_should_record_exit_status data = mock(:read_long => 5) channel = nil session = setup_for_extracting_channel_action([:on_request, "exit-status"], data) { |ch| channel = ch } Capistrano::Command.new("ls", [session]) assert_equal 5, channel[:status] end def test_on_close_should_set_channel_closed channel = nil session = setup_for_extracting_channel_action(:on_close) { |ch| channel = ch } Capistrano::Command.new("ls", [session]) assert channel[:closed] end def test_stop_should_close_all_open_channels sessions = [mock_session(new_channel(false)), mock_session(new_channel(true)), mock_session(new_channel(false))] cmd = Capistrano::Command.new("ls", sessions) cmd.stop! end def test_process_should_return_cleanly_if_all_channels_have_zero_exit_status sessions = [mock_session(new_channel(true, 0)), mock_session(new_channel(true, 0)), mock_session(new_channel(true, 0))] cmd = Capistrano::Command.new("ls", sessions) assert_nothing_raised { cmd.process! } end def test_process_should_raise_error_if_any_channel_has_non_zero_exit_status sessions = [mock_session(new_channel(true, 0)), mock_session(new_channel(true, 0)), mock_session(new_channel(true, 1))] cmd = Capistrano::Command.new("ls", sessions) assert_raises(Capistrano::CommandError) { cmd.process! } end def test_command_error_should_include_accessor_with_host_array sessions = [mock_session(new_channel(true, 0)), mock_session(new_channel(true, 0)), mock_session(new_channel(true, 1))] cmd = Capistrano::Command.new("ls", sessions) begin cmd.process! flunk "expected an exception to be raised" rescue Capistrano::CommandError => e assert e.respond_to?(:hosts) assert_equal %w(capistrano), e.hosts.map { |h| h.to_s } end end def test_process_should_loop_until_all_channels_are_closed new_channel = Proc.new do |times| ch = mock("channel") returns = [false] * (times-1) ch.stubs(:to_ary) ch.stubs(:[]).with(:closed).returns(*(returns + [true])) ch.expects(:[]).with(:status).returns(0) ch end sessions = [mock_session(new_channel[5]), mock_session(new_channel[10]), mock_session(new_channel[7])] cmd = Capistrano::Command.new("ls", sessions) assert_nothing_raised do cmd.process! end end def test_process_should_instantiate_command_and_process! cmd = mock("command", :process! => nil) Capistrano::Command.expects(:new).with("ls -l", %w(a b c), {:foo => "bar"}).returns(cmd) Capistrano::Command.process("ls -l", %w(a b c), :foo => "bar") end def test_process_with_host_placeholder_should_substitute_host_placeholder_with_each_host session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(sh -c 'echo capistrano')) end Capistrano::Command.new("echo $CAPISTRANO:HOST$", [session]) end class MockConfig include Capistrano::Configuration::Roles end def test_hostroles_substitution @config = MockConfig.new @config.server "capistrano", :db, :worker server = @config.roles[:db].servers.first channel = {:server => server, :host => 'capistrano'} tree = Capistrano::Command::Tree.new(@config) { |t| t.else("echo $CAPISTRANO:HOSTROLES$") } result = Capistrano::Command.new(tree, []).send(:replace_placeholders, "echo $CAPISTRANO:HOSTROLES$", channel) assert result == "echo db,worker" || result == "echo worker,db" end def test_process_with_unknown_placeholder_should_not_replace_placeholder session = setup_for_extracting_channel_action do |ch| ch.expects(:exec).with(%(sh -c 'echo $CAPISTRANO:OTHER$')) end Capistrano::Command.new("echo $CAPISTRANO:OTHER$", [session]) end private def mock_session(channel=nil) stub('session', :open_channel => channel, :preprocess => true, :postprocess => true, :listeners => {}, :xserver => server("capistrano")) end class MockChannel < Hash def close end end def new_channel(closed, status=nil) ch = MockChannel.new ch.update({ :closed => closed, :host => "capistrano", :server => server("capistrano") }) ch[:status] = status if status ch.expects(:close) unless closed ch end def setup_for_extracting_channel_action(action=nil, *args) s = server("capistrano") session = mock("session", :xserver => s) channel = {} session.expects(:open_channel).yields(channel) channel.stubs(:on_data) channel.stubs(:on_extended_data) channel.stubs(:on_request) channel.stubs(:on_close) channel.stubs(:exec) channel.stubs(:send_data) if action action = Array(action) channel.expects(action.first).with(*action[1..-1]).yields(channel, *args) end yield channel if block_given? session end end capistrano-2.12.0/test/cli/0000755000004100000410000000000011746743503015527 5ustar www-datawww-datacapistrano-2.12.0/test/cli/help_test.rb0000644000004100000410000001266211746743503020052 0ustar www-datawww-datarequire "utils" require 'capistrano/cli/help' class CLIHelpTest < Test::Unit::TestCase class MockCLI attr_reader :options, :called_original def initialize @options = {} @called_original = false end def execute_requested_actions(config) @called_original = config end include Capistrano::CLI::Help end def setup @cli = MockCLI.new @cli.options[:verbose] = 0 @ui = stub("ui", :output_cols => 80, :output_rows => 20, :page_at= => nil) MockCLI.stubs(:ui).returns(@ui) end def test_execute_requested_actions_without_tasks_or_explain_should_call_original @cli.execute_requested_actions(:config) @cli.expects(:task_list).never @cli.expects(:explain_task).never assert_equal :config, @cli.called_original end def test_execute_requested_actions_with_tasks_should_call_task_list @cli.options[:tasks] = true @cli.expects(:task_list).with(:config, true) @cli.expects(:explain_task).never @cli.execute_requested_actions(:config) assert !@cli.called_original end def test_execute_requested_actions_with_explain_should_call_explain_task @cli.options[:explain] = "deploy_with_niftiness" @cli.expects(:task_list).never @cli.expects(:explain_task).with(:config, "deploy_with_niftiness") @cli.execute_requested_actions(:config) assert !@cli.called_original end def test_task_list_with_no_tasks_should_emit_warning config = mock("config", :task_list => []) @cli.expects(:warn) @cli.task_list(config) end def test_task_list_should_query_all_tasks_in_all_namespaces expected_max_len = 80 - 3 - MockCLI::LINE_PADDING task_list = [task("c"), task("g", "c:g"), task("b", "c:b"), task("a")] task_list.each { |t| t.expects(:brief_description).with(expected_max_len).returns(t.fully_qualified_name) } config = mock("config") config.expects(:task_list).with(:all).returns(task_list) @cli.stubs(:puts) @cli.task_list(config) end def test_task_list_should_query_tasks_with_pattern expected_max_len = 80 - 3 - MockCLI::LINE_PADDING task_list = [task("g", "c:g"), task("b", "c:b")] task_list.each { |t| t.expects(:brief_description).with(expected_max_len).returns(t.fully_qualified_name)} config = mock("config") config.expects(:task_list).with(:all).once.returns(task_list) @cli.stubs(:puts) @cli.task_list(config, "c") end def test_task_list_should_query_for_all_tasks_when_pattern_doesnt_match expected_max_len = 80 - 3 - MockCLI::LINE_PADDING task_list = [task("g", "c:g"), task("b", "c:b")] task_list.each { |t| t.expects(:brief_description).with(expected_max_len).returns(t.fully_qualified_name) } config = mock("config") config.expects(:task_list).with(:all).times(2).returns(task_list) @cli.stubs(:warn) @cli.stubs(:puts) @cli.task_list(config, "z") end def test_task_list_should_never_use_less_than_MIN_MAX_LEN_chars_for_descriptions @ui.stubs(:output_cols).returns(20) t = task("c") t.expects(:brief_description).with(30).returns("hello") config = mock("config", :task_list => [t]) @cli.stubs(:puts) @cli.task_list(config) end def test_task_list_should_not_include_tasks_with_blank_description_or_internal_by_default t1 = task("c") t1.expects(:brief_description).returns("hello") t2 = task("d", "d", "[internal] howdy") t2.expects(:brief_description).never t3 = task("e", "e", "") t3.expects(:brief_description).never config = mock("config", :task_list => [t1, t2, t3]) @cli.stubs(:puts) @cli.expects(:puts).never.with { |s,| (s || "").include?("[internal]") || s =~ /#\s*$/ } @cli.task_list(config) end def test_task_list_should_include_tasks_with_blank_descriptions_and_internal_when_verbose t1 = task("c") t1.expects(:brief_description).returns("hello") t2 = task("d", "d", "[internal] howdy") t2.expects(:brief_description).returns("[internal] howdy") t3 = task("e", "e", "") t3.expects(:brief_description).returns("") config = mock("config", :task_list => [t1, t2, t3]) @cli.options[:verbose] = 1 @cli.stubs(:puts) @cli.expects(:puts).with { |s,| (s || "").include?("[internal]") || s =~ /#\s*$/ }.at_least_once @cli.task_list(config) end def test_explain_task_should_warn_if_task_does_not_exist config = mock("config", :find_task => nil) @cli.expects(:warn).with { |s,| s =~ /`deploy_with_niftiness'/ } @cli.explain_task(config, "deploy_with_niftiness") end def test_explain_task_with_task_that_has_no_description_should_emit_stub t = mock("task", :description => "") config = mock("config") config.expects(:find_task).with("deploy_with_niftiness").returns(t) @cli.stubs(:puts) @cli.expects(:puts).with("There is no description for this task.") @cli.explain_task(config, "deploy_with_niftiness") end def test_explain_task_with_task_should_format_description t = stub("task", :description => "line1\nline2\n\nline3") config = mock("config", :find_task => t) @cli.stubs(:puts) @cli.explain_task(config, "deploy_with_niftiness") end def test_long_help_should_load_and_format_help_txt_file File.expects(:dirname).returns "a/b/c" File.expects(:read).with("a/b/c/help.txt").returns("text") @ui.expects(:say).with("text\n") @cli.long_help end private def task(name, fqn=name, desc="a description") stub("task", :name => name, :fully_qualified_name => fqn, :description => desc) end end capistrano-2.12.0/test/cli/options_test.rb0000644000004100000410000002232411746743503020611 0ustar www-datawww-datarequire "utils" require 'capistrano/cli/options' class CLIOptionsTest < Test::Unit::TestCase class ExitException < Exception; end class MockCLI def initialize @args = [] end attr_reader :args include Capistrano::CLI::Options end def setup @cli = MockCLI.new end def test_parse_options_should_require_non_empty_args_list @cli.stubs(:warn) @cli.expects(:exit).raises(ExitException) assert_raises(ExitException) { @cli.parse_options! } end def test_parse_options_with_d_should_set_debug_option @cli.args << "-d" @cli.parse_options! assert @cli.options[:debug] end def test_parse_options_with_n_should_set_dry_run_option @cli.args << "-n" @cli.parse_options! assert @cli.options[:dry_run] end def test_parse_options_with_dry_run_should_set_dry_run_option @cli.args << "--dry-run" @cli.parse_options! assert @cli.options[:dry_run] end def test_parse_options_with_e_should_set_explain_option @cli.args << "-e" << "sample" @cli.parse_options! assert_equal "sample", @cli.options[:explain] end def test_parse_options_with_f_should_add_recipe_file @cli.args << "-f" << "deploy" @cli.parse_options! assert_equal %w(deploy), @cli.options[:recipes] end def test_parse_options_with_multiple_f_should_add_each_as_recipe_file @cli.args << "-f" << "deploy" << "-f" << "monitor" @cli.parse_options! assert_equal %w(deploy monitor), @cli.options[:recipes] end def test_parse_options_with_H_should_show_verbose_help_and_exit @cli.expects(:exit).raises(ExitException) @cli.expects(:long_help) @cli.args << "-H" assert_raises(ExitException) { @cli.parse_options! } end def test_parse_options_with_h_should_show_options_and_exit @cli.expects(:puts).with(@cli.option_parser) @cli.expects(:exit).raises(ExitException) @cli.args << "-h" assert_raises(ExitException) { @cli.parse_options! } end def test_parse_options_with_p_should_prompt_for_password MockCLI.expects(:password_prompt).returns(:the_password) @cli.args << "-p" @cli.parse_options! assert_equal :the_password, @cli.options[:password] end def test_parse_options_without_p_should_set_proc_for_password @cli.args << "-e" << "sample" @cli.parse_options! assert_instance_of Proc, @cli.options[:password] end def test_parse_options_with_q_should_set_verbose_to_0 @cli.args << "-q" @cli.parse_options! assert_equal 0, @cli.options[:verbose] end def test_parse_options_with_r_should_set_preserve_roles_option @cli.args << "-r" @cli.parse_options! assert @cli.options[:preserve_roles] end def test_parse_options_with_preserve_roles_should_set_preserve_roles_option @cli.args << "--preserve-roles" @cli.parse_options! assert @cli.options[:preserve_roles] end def test_parse_options_with_S_should_set_pre_vars @cli.args << "-S" << "foo=bar" @cli.parse_options! assert_equal "bar", @cli.options[:pre_vars][:foo] end def test_S_should_coerce_digits_to_integers @cli.args << "-S" << "foo=1234" @cli.parse_options! assert_equal 1234, @cli.options[:pre_vars][:foo] end def test_S_should_treat_quoted_integers_as_string @cli.args << "-S" << "foo=\"1234\"" @cli.parse_options! assert_equal "1234", @cli.options[:pre_vars][:foo] end def test_S_should_treat_digits_with_dot_as_floating_point @cli.args << "-S" << "foo=3.1415" @cli.parse_options! assert_equal 3.1415, @cli.options[:pre_vars][:foo] end def test_S_should_treat_true_as_boolean_true @cli.args << "-S" << "foo=true" @cli.parse_options! assert_equal true, @cli.options[:pre_vars][:foo] end def test_S_should_treat_false_as_boolean_false @cli.args << "-S" << "foo=false" @cli.parse_options! assert_equal false, @cli.options[:pre_vars][:foo] end def test_S_should_treat_nil_as_nil @cli.args << "-S" << "foo=nil" @cli.parse_options! assert_equal nil, @cli.options[:pre_vars][:foo] end def test_parse_options_with_s_should_set_vars @cli.args << "-s" << "foo=bar" @cli.parse_options! assert_equal "bar", @cli.options[:vars][:foo] end def test_s_should_coerce_digits_to_integers @cli.args << "-s" << "foo=1234" @cli.parse_options! assert_equal 1234, @cli.options[:vars][:foo] end def test_s_should_treat_quoted_integers_as_string @cli.args << "-s" << "foo=\"1234\"" @cli.parse_options! assert_equal "1234", @cli.options[:vars][:foo] end def test_s_should_treat_digits_with_dot_as_floating_point @cli.args << "-s" << "foo=3.1415" @cli.parse_options! assert_equal 3.1415, @cli.options[:vars][:foo] end def test_s_should_treat_true_as_boolean_true @cli.args << "-s" << "foo=true" @cli.parse_options! assert_equal true, @cli.options[:vars][:foo] end def test_s_should_treat_false_as_boolean_false @cli.args << "-s" << "foo=false" @cli.parse_options! assert_equal false, @cli.options[:vars][:foo] end def test_s_should_treat_nil_as_nil @cli.args << "-s" << "foo=nil" @cli.parse_options! assert_equal nil, @cli.options[:vars][:foo] end def test_parse_options_with_T_should_set_tasks_option_and_set_verbose_off @cli.args << "-T" @cli.parse_options! assert @cli.options[:tasks] assert_equal 0, @cli.options[:verbose] end def test_parse_options_with_V_should_show_version_and_exit @cli.args << "-V" @cli.expects(:puts).with { |s| s.include?(Capistrano::Version.to_s) } @cli.expects(:exit).raises(ExitException) assert_raises(ExitException) { @cli.parse_options! } end def test_parse_options_with_v_should_set_verbose_to_1 @cli.args << "-v" @cli.parse_options! assert_equal 1, @cli.options[:verbose] end def test_parse_options_with_multiple_v_should_set_verbose_accordingly @cli.args << "-vvvvvvv" @cli.parse_options! assert_equal 7, @cli.options[:verbose] end def test_parse_options_without_X_should_set_sysconf @cli.args << "-v" @cli.parse_options! assert @cli.options.key?(:sysconf) end def test_parse_options_with_X_should_unset_sysconf @cli.args << "-X" @cli.parse_options! assert !@cli.options.key?(:sysconf) end def test_parse_options_without_x_should_set_dotfile @cli.args << "-v" @cli.parse_options! assert @cli.options.key?(:dotfile) end def test_parse_options_with_x_should_unset_dotfile @cli.args << "-x" @cli.parse_options! assert !@cli.options.key?(:dotfile) end def test_parse_options_without_q_or_v_should_set_verbose_to_3 @cli.args << "-x" @cli.parse_options! assert_equal 3, @cli.options[:verbose] end def test_should_search_for_default_recipes_if_f_not_given @cli.expects(:look_for_default_recipe_file!) @cli.args << "-v" @cli.parse_options! end def test_should_not_search_for_default_recipes_if_f_given @cli.expects(:look_for_default_recipe_file!).never @cli.args << "-f" << "hello" @cli.parse_options! end def test_F_should_search_for_default_recipes_even_if_f_is_given @cli.expects(:look_for_default_recipe_file!) @cli.args << "-Ff" << "hello" @cli.parse_options! end def test_should_extract_env_vars_from_command_line assert_nil ENV["HELLO"] assert_nil ENV["ANOTHER"] @cli.args << "HELLO=world" << "hello" << "ANOTHER=value" @cli.parse_options! assert_equal "world", ENV["HELLO"] assert_equal "value", ENV["ANOTHER"] ensure ENV.delete("HELLO") ENV.delete("ANOTHER") end def test_remaining_args_should_be_added_to_actions_list @cli.args << "-v" << "HELLO=world" << "-f" << "foo" << "something" << "else" @cli.parse_options! assert_equal %w(something else), @cli.args ensure ENV.delete("HELLO") end def test_search_for_default_recipe_file_should_look_for_Capfile File.stubs(:file?).returns(false) File.expects(:file?).with("Capfile").returns(true) @cli.args << "-v" @cli.parse_options! assert_equal %w(Capfile), @cli.options[:recipes] end def test_search_for_default_recipe_file_should_look_for_capfile File.stubs(:file?).returns(false) File.expects(:file?).with("capfile").returns(true) @cli.args << "-v" @cli.parse_options! assert_equal %w(capfile), @cli.options[:recipes] end def test_search_for_default_recipe_should_hike_up_the_directory_tree_until_it_finds_default_recipe File.stubs(:file?).returns(false) File.expects(:file?).with("capfile").times(2).returns(false,true) Dir.expects(:pwd).times(3).returns(*%w(/bar/baz /bar/baz /bar)) Dir.expects(:chdir).with("..") @cli.args << "-v" @cli.parse_options! assert_equal %w(capfile), @cli.options[:recipes] end def test_search_for_default_recipe_should_halt_at_root_directory File.stubs(:file?).returns(false) Dir.expects(:pwd).times(7).returns(*%w(/bar/baz /bar/baz /bar /bar / / /)) Dir.expects(:chdir).with("..").times(3) Dir.expects(:chdir).with("/bar/baz") @cli.args << "-v" @cli.parse_options! assert @cli.options[:recipes].empty? end def test_parse_should_instantiate_new_cli_and_call_parse_options cli = mock("cli", :parse_options! => nil) MockCLI.expects(:new).with(%w(a b c)).returns(cli) assert_equal cli, MockCLI.parse(%w(a b c)) end end capistrano-2.12.0/test/cli/ui_test.rb0000644000004100000410000000151111746743503017526 0ustar www-datawww-datarequire "utils" require 'capistrano/cli/ui' class CLIUITest < Test::Unit::TestCase class MockCLI include Capistrano::CLI::UI end def test_ui_should_return_highline_instance assert_instance_of HighLine, MockCLI.ui end def test_password_prompt_should_have_default_prompt_and_set_echo_false q = mock("question") q.expects(:echo=).with(false) ui = mock("ui") ui.expects(:ask).with("Password: ").yields(q).returns("sayuncle") MockCLI.expects(:ui).returns(ui) assert_equal "sayuncle", MockCLI.password_prompt end def test_password_prompt_with_custom_prompt_should_use_custom_prompt ui = mock("ui") ui.expects(:ask).with("Give the passphrase: ").returns("sayuncle") MockCLI.expects(:ui).returns(ui) assert_equal "sayuncle", MockCLI.password_prompt("Give the passphrase: ") end endcapistrano-2.12.0/test/cli/execute_test.rb0000644000004100000410000001056211746743503020561 0ustar www-datawww-datarequire "utils" require 'capistrano/cli/execute' class CLIExecuteTest < Test::Unit::TestCase class MockCLI attr_reader :options def initialize @options = {} end include Capistrano::CLI::Execute end def setup @cli = MockCLI.new @logger = stub_everything @config = stub(:logger => @logger, :debug= => nil, :dry_run= => nil, :preserve_roles= => nil) @config.stubs(:set) @config.stubs(:load) @config.stubs(:trigger) @cli.stubs(:instantiate_configuration).returns(@config) end def test_execute_should_set_logger_verbosity @cli.options[:verbose] = 7 @logger.expects(:level=).with(7) @cli.execute! end def test_execute_should_set_password @cli.options[:password] = "nosoup4u" @config.expects(:set).with(:password, "nosoup4u") @cli.execute! end def test_execute_should_set_prevars_before_loading @config.expects(:load).never @config.expects(:set).with(:stage, "foobar") @config.expects(:load).with("standard") @cli.options[:pre_vars] = { :stage => "foobar" } @cli.execute! end def test_execute_should_load_sysconf_if_sysconf_set_and_exists @cli.options[:sysconf] = "/etc/capistrano.conf" @config.expects(:load).with("/etc/capistrano.conf") File.expects(:file?).with("/etc/capistrano.conf").returns(true) @cli.execute! end def test_execute_should_not_load_sysconf_when_sysconf_set_and_not_exists @cli.options[:sysconf] = "/etc/capistrano.conf" File.expects(:file?).with("/etc/capistrano.conf").returns(false) @cli.execute! end def test_execute_should_load_dotfile_if_dotfile_set_and_exists @cli.options[:dotfile] = "/home/jamis/.caprc" @config.expects(:load).with("/home/jamis/.caprc") File.expects(:file?).with("/home/jamis/.caprc").returns(true) @cli.execute! end def test_execute_should_not_load_dotfile_when_dotfile_set_and_not_exists @cli.options[:dotfile] = "/home/jamis/.caprc" File.expects(:file?).with("/home/jamis/.caprc").returns(false) @cli.execute! end def test_execute_should_load_recipes_when_recipes_are_given @cli.options[:recipes] = %w(config/deploy path/to/extra) @config.expects(:load).with("config/deploy") @config.expects(:load).with("path/to/extra") @cli.execute! end def test_execute_should_set_vars_and_execute_tasks @cli.options[:vars] = { :foo => "bar", :baz => "bang" } @cli.options[:actions] = %w(first second) @config.expects(:set).with(:foo, "bar") @config.expects(:set).with(:baz, "bang") @config.expects(:find_and_execute_task).with("first", :before => :start, :after => :finish) @config.expects(:find_and_execute_task).with("second", :before => :start, :after => :finish) @cli.execute! end def test_execute_should_call_load_and_exit_triggers @cli.options[:actions] = %w(first second) @config.expects(:find_and_execute_task).with("first", :before => :start, :after => :finish) @config.expects(:find_and_execute_task).with("second", :before => :start, :after => :finish) @config.expects(:trigger).never @config.expects(:trigger).with(:load) @config.expects(:trigger).with(:exit) @cli.execute! end def test_execute_should_call_handle_error_when_exceptions_occur @config.expects(:load).raises(Exception, "boom") @cli.expects(:handle_error).with { |e,| Exception === e } @cli.execute! end def test_execute_should_return_config_instance assert_equal @config, @cli.execute! end def test_instantiate_configuration_should_return_new_configuration_instance assert_instance_of Capistrano::Configuration, MockCLI.new.instantiate_configuration end def test_handle_error_with_auth_error_should_abort_with_message_including_user_name @cli.expects(:abort).with { |s| s.include?("jamis") } @cli.handle_error(Net::SSH::AuthenticationFailed.new("jamis")) end def test_handle_error_with_cap_error_should_abort_with_message @cli.expects(:abort).with("Wish you were here") @cli.handle_error(Capistrano::Error.new("Wish you were here")) end def test_handle_error_with_other_errors_should_reraise_error other_error = Class.new(RuntimeError) assert_raises(other_error) { @cli.handle_error(other_error.new("boom")) } end def test_class_execute_method_should_call_parse_and_execute_with_ARGV cli = mock(:execute! => nil) MockCLI.expects(:parse).with(ARGV).returns(cli) MockCLI.execute end end capistrano-2.12.0/test/configuration/0000755000004100000410000000000011746743503017627 5ustar www-datawww-datacapistrano-2.12.0/test/configuration/roles_test.rb0000644000004100000410000001171011746743503022337 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/roles' require 'capistrano/server_definition' class ConfigurationRolesTest < Test::Unit::TestCase class MockConfig attr_reader :original_initialize_called def initialize @original_initialize_called = true end include Capistrano::Configuration::Roles end def setup @config = MockConfig.new end def test_initialize_should_initialize_roles_collection assert @config.original_initialize_called assert @config.roles.empty? end def test_roles_for_host_with_one_role @config.role :app, "app1.capistrano.test" @config.role :not_app, "not-app.capistrano.test" app_server = @config.roles[:app].servers.first assert @config.role_names_for_host(app_server)==[ :app ] end def test_roles_for_host_with_multiple_roles @config.server "www.capistrano.test", :db, :worker db_server = @config.roles[:db].servers.first assert_equal @config.role_names_for_host(db_server).map(&:to_s).sort, [ 'db', 'worker' ] end def test_role_should_allow_empty_list @config.role :app assert @config.roles.keys.include?(:app) assert @config.roles[:app].empty? end def test_role_with_one_argument_should_add_to_roles_collection @config.role :app, "app1.capistrano.test" assert_equal [:app], @config.roles.keys assert_role_equals %w(app1.capistrano.test) end def test_role_block_returning_single_string_is_added_to_roles_collection @config.role :app do 'app1.capistrano.test' end assert_role_equals %w(app1.capistrano.test) end def test_role_with_multiple_arguments_should_add_each_to_roles_collection @config.role :app, "app1.capistrano.test", "app2.capistrano.test" assert_equal [:app], @config.roles.keys assert_role_equals %w(app1.capistrano.test app2.capistrano.test) end def test_role_with_block_and_strings_should_add_both_to_roles_collection @config.role :app, 'app1.capistrano.test' do 'app2.capistrano.test' end assert_role_equals %w(app1.capistrano.test app2.capistrano.test) end def test_role_block_returning_array_should_add_each_to_roles_collection @config.role :app do ['app1.capistrano.test', 'app2.capistrano.test'] end assert_role_equals %w(app1.capistrano.test app2.capistrano.test) end def test_role_with_options_should_apply_options_to_each_argument @config.role :app, "app1.capistrano.test", "app2.capistrano.test", :extra => :value @config.roles[:app].each do |server| assert_equal({:extra => :value}, server.options) end end def test_role_with_options_should_apply_options_to_block_results @config.role :app, :extra => :value do ['app1.capistrano.test', 'app2.capistrano.test'] end @config.roles[:app].each do |server| assert_equal({:extra => :value}, server.options) end end def test_options_should_apply_only_to_this_argument_set @config.role :app, 'app1.capistrano.test', 'app2.capistrano.test' do ['app3.capistrano.test', 'app4.capistrano.test'] end @config.role :app, 'app5.capistrano.test', 'app6.capistrano.test', :extra => :value do ['app7.capistrano.test', 'app8.capistrano.test'] end @config.role :app, 'app9.capistrano.test' option_hosts = ['app5.capistrano.test', 'app6.capistrano.test', 'app7.capistrano.test', 'app8.capistrano.test'] @config.roles[:app].each do |server| if (option_hosts.include? server.host) assert_equal({:extra => :value}, server.options) else assert_not_equal({:extra => :value}, server.options) end end end # Here, the source should be more readable than the method name def test_role_block_returns_options_hash_is_merged_with_role_options_argument @config.role :app, :first => :one, :second => :two do ['app1.capistrano.test', 'app2.capistrano.test', {:second => :please, :third => :three}] end @config.roles[:app].each do |server| assert_equal({:first => :one, :second => :please, :third => :three}, server.options) end end def test_role_block_can_override_role_options_argument @config.role :app, :value => :wrong do Capistrano::ServerDefinition.new('app.capistrano.test') end @config.roles[:app].servers @config.roles[:app].servers.each do |server| assert_not_equal({:value => :wrong}, server.options) end end def test_role_block_can_return_nil @config.role :app do nil end assert_role_equals ([]) end def test_role_block_can_return_empty_array @config.role :app do [] end assert_role_equals ([]) end def test_role_definitions_via_server_should_associate_server_with_roles @config.server "www.capistrano.test", :web, :app assert_equal %w(www.capistrano.test), @config.roles[:app].map { |s| s.host } assert_equal %w(www.capistrano.test), @config.roles[:web].map { |s| s.host } end private def assert_role_equals(list) assert_equal list, @config.roles[:app].map { |s| s.host } end end capistrano-2.12.0/test/configuration/namespace_dsl_test.rb0000644000004100000410000002404111746743503024012 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/namespaces' class ConfigurationNamespacesDSLTest < Test::Unit::TestCase class MockConfig attr_reader :original_initialize_called, :options def initialize @original_initialize_called = true @options = {} end include Capistrano::Configuration::Namespaces end def setup @config = MockConfig.new end def test_initialize_should_initialize_collections assert @config.original_initialize_called assert @config.tasks.empty? assert @config.namespaces.empty? end def test_unqualified_task_should_define_task_at_top_namespace assert !@config.tasks.key?(:testing) @config.task(:testing) { puts "something" } assert @config.tasks.key?(:testing) end def test_qualification_should_define_task_within_namespace @config.namespace(:testing) do task(:nested) { puts "nested" } end assert !@config.tasks.key?(:nested) assert @config.namespaces.key?(:testing) assert @config.namespaces[:testing].tasks.key?(:nested) end def test_namespace_within_namespace_should_define_task_within_nested_namespace @config.namespace :outer do namespace :inner do task :nested do puts "nested" end end end assert !@config.tasks.key?(:nested) assert @config.namespaces.key?(:outer) assert @config.namespaces[:outer].namespaces.key?(:inner) assert @config.namespaces[:outer].namespaces[:inner].tasks.key?(:nested) end def test_pending_desc_should_apply_only_to_immediately_subsequent_task @config.desc "A description" @config.task(:testing) { puts "foo" } @config.task(:another) { puts "bar" } assert_equal "A description", @config.tasks[:testing].desc assert_nil @config.tasks[:another].desc end def test_pending_desc_should_apply_only_to_next_task_in_any_namespace @config.desc "A description" @config.namespace(:outer) { task(:testing) { puts "foo" } } assert_equal "A description", @config.namespaces[:outer].tasks[:testing].desc end def test_defining_task_without_block_should_raise_error assert_raises(ArgumentError) do @config.task(:testing) end end def test_defining_task_that_shadows_existing_method_should_raise_error assert_raises(ArgumentError) do @config.task(:sprintf) { puts "foo" } end end def test_defining_task_that_shadows_existing_namespace_should_raise_error @config.namespace(:outer) {} assert_raises(ArgumentError) do @config.task(:outer) { puts "foo" } end end def test_defining_namespace_that_shadows_existing_method_should_raise_error assert_raises(ArgumentError) do @config.namespace(:sprintf) {} end end def test_defining_namespace_that_shadows_existing_task_should_raise_error @config.task(:testing) { puts "foo" } assert_raises(ArgumentError) do @config.namespace(:testing) {} end end def test_defining_task_that_shadows_existing_task_should_not_raise_error @config.task(:original) { puts "foo" } assert_nothing_raised do @config.task(:original) { puts "bar" } end end def test_defining_ask_should_add_task_as_method assert !@config.methods.any? { |m| m.to_sym == :original } @config.task(:original) { puts "foo" } assert @config.methods.any? { |m| m.to_sym == :original } end def test_calling_defined_task_should_delegate_to_execute_task @config.task(:original) { puts "foo" } @config.expects(:execute_task).with(@config.tasks[:original]) @config.original end def test_role_inside_namespace_should_raise_error assert_raises(NotImplementedError) do @config.namespace(:outer) do role :app, "hello" end end end def test_name_for_top_level_should_be_nil assert_nil @config.name end def test_parent_for_top_level_should_be_nil assert_nil @config.parent end def test_fqn_for_top_level_should_be_nil assert_nil @config.fully_qualified_name end def test_fqn_for_namespace_should_be_the_name_of_the_namespace @config.namespace(:outer) {} assert_equal "outer", @config.namespaces[:outer].fully_qualified_name end def test_parent_for_namespace_should_be_the_top_level @config.namespace(:outer) {} assert_equal @config, @config.namespaces[:outer].parent end def test_fqn_for_nested_namespace_should_be_color_delimited @config.namespace(:outer) { namespace(:inner) {} } assert_equal "outer:inner", @config.namespaces[:outer].namespaces[:inner].fully_qualified_name end def test_parent_for_nested_namespace_should_be_the_nesting_namespace @config.namespace(:outer) { namespace(:inner) {} } assert_equal @config.namespaces[:outer], @config.namespaces[:outer].namespaces[:inner].parent end def test_find_task_should_dereference_nested_tasks @config.namespace(:outer) do namespace(:inner) { task(:nested) { puts "nested" } } end task = @config.find_task("outer:inner:nested") assert_not_nil task assert_equal "outer:inner:nested", task.fully_qualified_name end def test_find_task_should_return_nil_if_no_task_matches assert_nil @config.find_task("outer:inner:nested") end def test_find_task_should_return_default_if_deferences_to_namespace_and_namespace_has_default @config.namespace(:outer) do namespace(:inner) { task(:default) { puts "nested" } } end task = @config.find_task("outer:inner") assert_not_nil task assert_equal :default, task.name assert_equal "outer:inner", task.namespace.fully_qualified_name end def test_find_task_should_return_nil_if_deferences_to_namespace_and_namespace_has_no_default @config.namespace(:outer) do namespace(:inner) { task(:nested) { puts "nested" } } end assert_nil @config.find_task("outer:inner") end def test_default_task_should_return_nil_for_top_level @config.task(:default) {} assert_nil @config.default_task end def test_default_task_should_return_nil_for_namespace_without_default @config.namespace(:outer) { task(:nested) { puts "nested" } } assert_nil @config.namespaces[:outer].default_task end def test_default_task_should_return_task_for_namespace_with_default @config.namespace(:outer) { task(:default) { puts "nested" } } task = @config.namespaces[:outer].default_task assert_not_nil task assert_equal :default, task.name end def test_task_list_should_return_only_tasks_immediately_within_namespace @config.task(:first) { puts "here" } @config.namespace(:outer) do task(:second) { puts "here" } namespace(:inner) do task(:third) { puts "here" } end end assert_equal %w(first), @config.task_list.map { |t| t.fully_qualified_name } end def test_task_list_with_all_should_return_all_tasks_under_this_namespace_recursively @config.task(:first) { puts "here" } @config.namespace(:outer) do task(:second) { puts "here" } namespace(:inner) do task(:third) { puts "here" } end end assert_equal %w(first outer:inner:third outer:second), @config.task_list(:all).map { |t| t.fully_qualified_name }.sort end def test_namespace_should_respond_to_its_parents_methods @config.namespace(:outer) {} ns = @config.namespaces[:outer] assert ns.respond_to?(:original_initialize_called) end def test_namespace_should_accept_respond_to_with_include_priv_parameter @config.namespace(:outer) {} ns = @config.namespaces[:outer] assert ns.respond_to?(:original_initialize_called, true) end def test_namespace_should_delegate_unknown_messages_to_its_parent @config.namespace(:outer) {} ns = @config.namespaces[:outer] assert ns.original_initialize_called end def test_namespace_should_not_understand_messages_that_neither_it_nor_its_parent_understands @config.namespace(:outer) {} ns = @config.namespaces[:outer] assert_raises(NoMethodError) { ns.alskdfjlsf } end def test_search_task_should_find_tasks_in_current_namespace @config.namespace(:outer) do namespace(:inner) do task(:third) { puts "here" } end end inner = @config.namespaces[:outer].namespaces[:inner] assert_equal inner.tasks[:third], inner.search_task(:third) end def test_search_task_should_find_tasks_in_parent_namespace @config.task(:first) { puts "here" } @config.namespace(:outer) do task(:second) { puts "here" } namespace(:inner) do task(:third) { puts "here" } end end inner = @config.namespaces[:outer].namespaces[:inner] assert_equal @config.tasks[:first], inner.search_task(:first) end def test_search_task_should_return_nil_if_no_tasks_are_found @config.namespace(:outer) { namespace(:inner) {} } inner = @config.namespaces[:outer].namespaces[:inner] assert_nil inner.search_task(:first) end def test_top_should_return_self_if_self_is_top assert_equal @config, @config.top end def test_top_should_return_parent_if_parent_is_top @config.namespace(:outer) {} assert_equal @config, @config.namespaces[:outer].top end def test_top_should_return_topmost_parent_if_self_is_deeply_nested @config.namespace(:outer) { namespace(:middle) { namespace(:inner) {} } } assert_equal @config, @config.namespaces[:outer].namespaces[:middle].namespaces[:inner].top end def test_find_task_should_return_nil_when_empty_inner_task @config.namespace :outer do namespace :inner do end end assert_nil @config.find_task("outer::inner") end def test_kernel_method_clashing_should_not_affect_method_delegation_to_parent @config.class.class_eval do def some_weird_method() 'config' end end @config.namespace(:clash) {} namespace = @config.namespaces[:clash] assert_equal 'config', namespace.some_weird_method Kernel.module_eval do def some_weird_method() 'kernel' end end @config.namespace(:clash2) {} namespace = @config.namespaces[:clash2] assert_equal 'config', namespace.some_weird_method Kernel.send :remove_method, :some_weird_method @config.class.send :remove_method, :some_weird_method end endcapistrano-2.12.0/test/configuration/actions/0000755000004100000410000000000011746743503021267 5ustar www-datawww-datacapistrano-2.12.0/test/configuration/actions/inspect_test.rb0000644000004100000410000000421311746743503024320 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/actions/inspect' class ConfigurationActionsInspectTest < Test::Unit::TestCase class MockConfig include Capistrano::Configuration::Actions::Inspect end def setup @config = MockConfig.new @config.stubs(:logger).returns(stub_everything) end def test_stream_should_pass_options_through_to_run @config.expects(:invoke_command).with("tail -f foo.log", :once => true) @config.stream("tail -f foo.log", :once => true) end def test_stream_should_emit_stdout_via_puts @config.expects(:invoke_command).yields(mock("channel"), :out, "something streamed") @config.expects(:puts).with("something streamed") @config.expects(:warn).never @config.stream("tail -f foo.log") end def test_stream_should_emit_stderr_via_warn ch = mock("channel") ch.expects(:[]).with(:server).returns(server("capistrano")) @config.expects(:invoke_command).yields(ch, :err, "something streamed") @config.expects(:puts).never @config.expects(:warn).with("[err :: capistrano] something streamed") @config.stream("tail -f foo.log") end def test_capture_should_pass_options_merged_with_once_to_run @config.expects(:invoke_command).with("hostname", :foo => "bar", :once => true) @config.capture("hostname", :foo => "bar") end def test_capture_with_stderr_should_emit_stderr_via_warn ch = mock("channel") ch.expects(:[]).with(:server).returns(server("capistrano")) @config.expects(:invoke_command).yields(ch, :err, "boom") @config.expects(:warn).with("[err :: capistrano] boom") @config.capture("hostname") end def test_capture_with_stdout_should_aggregate_and_return_stdout config_expects_invoke_command_to_loop_with(mock("channel"), "foo", "bar", "baz") assert_equal "foobarbaz", @config.capture("hostname") end private def config_expects_invoke_command_to_loop_with(channel, *output) class <<@config attr_accessor :script, :channel def invoke_command(*args) script.each { |item| yield channel, :out, item } end end @config.channel = channel @config.script = output end end capistrano-2.12.0/test/configuration/actions/invocation_test.rb0000644000004100000410000002016411746743503025027 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/actions/invocation' require 'capistrano/configuration/actions/file_transfer' class ConfigurationActionsInvocationTest < Test::Unit::TestCase class MockConfig attr_reader :options attr_accessor :debug attr_accessor :dry_run attr_accessor :preserve_roles attr_accessor :servers def initialize @options = {} @servers = [] end def [](*args) @options[*args] end def set(name, value) @options[name] = value end def fetch(*args) @options.fetch(*args) end def execute_on_servers(options = {}) yield @servers end include Capistrano::Configuration::Actions::Invocation include Capistrano::Configuration::Actions::FileTransfer end def setup @config = make_config @original_io_proc = MockConfig.default_io_proc end def teardown MockConfig.default_io_proc = @original_io_proc end def test_run_options_should_be_passed_to_execute_on_servers @config.expects(:execute_on_servers).with(:foo => "bar") @config.run "ls", :foo => "bar" end def test_run_will_return_if_dry_run @config.expects(:dry_run).returns(true) @config.expects(:execute_on_servers).never @config.run "ls", :foo => "bar" end def test_put_wont_transfer_if_dry_run config = make_config config.dry_run = true config.servers = %w[ foo ] config.expects(:execute_on_servers).never ::Capistrano::Transfer.expects(:process).never config.put "foo", "bar", :mode => 0644 end def test_add_default_command_options_should_return_bare_options_if_there_is_no_env_or_shell_specified assert_equal({:foo => "bar"}, @config.add_default_command_options(:foo => "bar")) end def test_add_default_command_options_should_merge_default_environment_as_env @config[:default_environment][:bang] = "baz" assert_equal({:foo => "bar", :env => { :bang => "baz" }}, @config.add_default_command_options(:foo => "bar")) end def test_add_default_command_options_should_merge_env_with_default_environment @config[:default_environment][:bang] = "baz" @config[:default_environment][:bacon] = "crunchy" assert_equal({:foo => "bar", :env => { :bang => "baz", :bacon => "chunky", :flip => "flop" }}, @config.add_default_command_options(:foo => "bar", :env => {:bacon => "chunky", :flip => "flop"})) end def test_add_default_command_options_should_use_default_shell_if_present @config.set :default_shell, "/bin/bash" assert_equal({:foo => "bar", :shell => "/bin/bash"}, @config.add_default_command_options(:foo => "bar")) end def test_add_default_command_options_should_use_default_shell_of_false_if_present @config.set :default_shell, false assert_equal({:foo => "bar", :shell => false}, @config.add_default_command_options(:foo => "bar")) end def test_add_default_command_options_should_use_shell_in_preference_of_default_shell @config.set :default_shell, "/bin/bash" assert_equal({:foo => "bar", :shell => "/bin/sh"}, @config.add_default_command_options(:foo => "bar", :shell => "/bin/sh")) end def test_default_io_proc_should_log_stdout_arguments_as_info ch = { :host => "capistrano", :server => server("capistrano"), :options => { :logger => mock("logger") } } ch[:options][:logger].expects(:info).with("data stuff", "out :: capistrano") MockConfig.default_io_proc[ch, :out, "data stuff"] end def test_default_io_proc_should_log_stderr_arguments_as_important ch = { :host => "capistrano", :server => server("capistrano"), :options => { :logger => mock("logger") } } ch[:options][:logger].expects(:important).with("data stuff", "err :: capistrano") MockConfig.default_io_proc[ch, :err, "data stuff"] end def test_sudo_should_default_to_sudo @config.expects(:run).with("sudo -p 'sudo password: ' ls", {}) @config.sudo "ls" end def test_sudo_should_use_sudo_variable_definition @config.expects(:run).with("/opt/local/bin/sudo -p 'sudo password: ' ls", {}) @config.options[:sudo] = "/opt/local/bin/sudo" @config.sudo "ls" end def test_sudo_should_interpret_as_option_as_user @config.expects(:run).with("sudo -p 'sudo password: ' -u app ls", {}) @config.sudo "ls", :as => "app" end def test_sudo_should_pass_options_through_to_run @config.expects(:run).with("sudo -p 'sudo password: ' ls", :foo => "bar") @config.sudo "ls", :foo => "bar" end def test_sudo_should_avoid_minus_p_when_sudo_prompt_is_empty @config.set :sudo_prompt, "" @config.expects(:run).with("sudo ls", {}) @config.sudo "ls" end def test_sudo_should_interpret_sudo_prompt_variable_as_custom_prompt @config.set :sudo_prompt, "give it to me: " @config.expects(:run).with("sudo -p 'give it to me: ' ls", {}) @config.sudo "ls" end def test_sudo_behavior_callback_should_send_password_when_prompted_with_default_sudo_prompt ch = mock("channel") ch.expects(:send_data).with("g00b3r\n") @config.options[:password] = "g00b3r" @config.sudo_behavior_callback(nil)[ch, nil, "sudo password: "] end def test_sudo_behavior_callback_should_send_password_when_prompted_with_custom_sudo_prompt ch = mock("channel") ch.expects(:send_data).with("g00b3r\n") @config.set :sudo_prompt, "give it to me: " @config.options[:password] = "g00b3r" @config.sudo_behavior_callback(nil)[ch, nil, "give it to me: "] end def test_sudo_behavior_callback_with_incorrect_password_on_first_prompt ch = mock("channel") ch.stubs(:[]).with(:host).returns("capistrano") ch.stubs(:[]).with(:server).returns(server("capistrano")) @config.expects(:reset!).with(:password) @config.sudo_behavior_callback(nil)[ch, nil, "Sorry, try again."] end def test_sudo_behavior_callback_with_incorrect_password_on_subsequent_prompts callback = @config.sudo_behavior_callback(nil) ch = mock("channel") ch.stubs(:[]).with(:host).returns("capistrano") ch.stubs(:[]).with(:server).returns(server("capistrano")) ch2 = mock("channel") ch2.stubs(:[]).with(:host).returns("cap2") ch2.stubs(:[]).with(:server).returns(server("cap2")) @config.expects(:reset!).with(:password).times(2) callback[ch, nil, "Sorry, try again."] callback[ch2, nil, "Sorry, try again."] # shouldn't call reset! callback[ch, nil, "Sorry, try again."] end def test_sudo_behavior_callback_should_reset_password_and_prompt_again_if_output_includes_both_cues ch = mock("channel") ch.stubs(:[]).with(:host).returns("capistrano") ch.stubs(:[]).with(:server).returns(server("capistrano")) ch.expects(:send_data, "password!\n").times(2) @config.set(:password, "password!") @config.expects(:reset!).with(:password) callback = @config.sudo_behavior_callback(nil) callback[ch, :out, "sudo password: "] callback[ch, :out, "Sorry, try again.\nsudo password: "] end def test_sudo_behavior_callback_should_defer_to_fallback_for_other_output callback = @config.sudo_behavior_callback(inspectable_proc) a = mock("channel", :called => true) b = mock("stream", :called => true) c = mock("data", :called => true) callback[a, b, c] end def test_invoke_command_should_default_to_run @config.expects(:run).with("ls", :once => true) @config.invoke_command("ls", :once => true) end def test_invoke_command_should_delegate_to_method_identified_by_via @config.expects(:foobar).with("ls", :once => true) @config.invoke_command("ls", :once => true, :via => :foobar) end private def make_config config = MockConfig.new config.stubs(:logger).returns(stub_everything) config end def inspectable_proc Proc.new do |ch, stream, data| ch.called stream.called data.called end end def prepare_command(command, sessions, options) a = mock("channel", :called => true) b = mock("stream", :called => true) c = mock("data", :called => true) compare_args = Proc.new do |tree, sess, opts| tree.fallback.command == command && sess == sessions && opts == options end Capistrano::Command.expects(:process).with(&compare_args) end end capistrano-2.12.0/test/configuration/actions/file_transfer_test.rb0000644000004100000410000000464711746743503025511 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/actions/file_transfer' class ConfigurationActionsFileTransferTest < Test::Unit::TestCase class MockConfig include Capistrano::Configuration::Actions::FileTransfer attr_accessor :sessions, :dry_run end def setup @config = MockConfig.new @config.stubs(:logger).returns(stub_everything) end def test_put_should_delegate_to_upload @config.expects(:upload).with { |from, to, opts| from.string == "some data" && to == "test.txt" && opts == { :mode => 0777 } } @config.expects(:run).never @config.put("some data", "test.txt", :mode => 0777) end def test_get_should_delegate_to_download_with_once @config.expects(:download).with("testr.txt", "testl.txt", :foo => "bar", :once => true) @config.get("testr.txt", "testl.txt", :foo => "bar") end def test_upload_should_delegate_to_transfer @config.expects(:transfer).with(:up, "testl.txt", "testr.txt", :foo => "bar") @config.upload("testl.txt", "testr.txt", :foo => "bar") end def test_upload_without_mode_should_not_try_to_chmod @config.expects(:transfer).with(:up, "testl.txt", "testr.txt", :foo => "bar") @config.expects(:run).never @config.upload("testl.txt", "testr.txt", :foo => "bar") end def test_upload_with_mode_should_try_to_chmod @config.expects(:transfer).with(:up, "testl.txt", "testr.txt", :foo => "bar") @config.expects(:run).with("chmod 775 testr.txt", {:foo => "bar"}) @config.upload("testl.txt", "testr.txt", :mode => 0775, :foo => "bar") end def test_upload_with_symbolic_mode_should_try_to_chmod @config.expects(:transfer).with(:up, "testl.txt", "testr.txt", :foo => "bar") @config.expects(:run).with("chmod g+w testr.txt", {:foo => "bar"}) @config.upload("testl.txt", "testr.txt", :mode => "g+w", :foo => "bar") end def test_download_should_delegate_to_transfer @config.expects(:transfer).with(:down, "testr.txt", "testl.txt", :foo => "bar") @config.download("testr.txt", "testl.txt", :foo => "bar") end def test_transfer_should_invoke_transfer_on_matching_servers @config.sessions = { :a => 1, :b => 2, :c => 3, :d => 4 } @config.expects(:execute_on_servers).with(:foo => "bar").yields([:a, :b, :c]) Capistrano::Transfer.expects(:process).with(:up, "testl.txt", "testr.txt", [1,2,3], {:foo => "bar", :logger => @config.logger}) @config.transfer(:up, "testl.txt", "testr.txt", :foo => "bar") end end capistrano-2.12.0/test/configuration/variables_test.rb0000644000004100000410000001247111746743503023170 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/variables' class ConfigurationVariablesTest < Test::Unit::TestCase class MockConfig attr_reader :original_initialize_called def initialize @original_initialize_called = true end include Capistrano::Configuration::Variables end def setup MockConfig.any_instance.stubs(:logger).returns(stub_everything) @config = MockConfig.new end def test_initialize_should_initialize_variables_hash assert @config.original_initialize_called assert_equal({:ssh_options => {}, :logger => @config.logger}, @config.variables) end def test_set_should_add_variable_to_hash @config.set :sample, :value assert_equal :value, @config.variables[:sample] end def test_set_should_convert_variable_name_to_symbol @config.set "sample", :value assert_equal :value, @config.variables[:sample] end def test_set_should_be_aliased_to_square_brackets @config[:sample] = :value assert_equal :value, @config.variables[:sample] end def test_variables_should_be_accessible_as_read_accessors @config[:sample] = :value assert_equal :value, @config.sample end def test_method_missing_should_raise_error_if_no_variable_matches assert_raises(NoMethodError) do @config.sample end end def test_respond_to_should_look_for_variables assert !@config.respond_to?(:sample) @config[:sample] = :value assert @config.respond_to?(:sample) end def test_respond_to_should_be_true_when_passed_a_string assert !@config.respond_to?('sample') @config[:sample] = :value assert @config.respond_to?('sample') end def test_respond_to_with_include_priv_paramter assert !@config.respond_to?(:sample, true) end def test_set_should_require_value assert_raises(ArgumentError) do @config.set(:sample) end end def test_set_should_allow_value_to_be_omitted_if_block_is_given assert_nothing_raised do @config.set(:sample) { :value } end assert_instance_of Proc, @config.variables[:sample] end def test_set_should_not_allow_multiple_values assert_raises(ArgumentError) do @config.set(:sample, :value, :another) end end def test_set_should_not_allow_both_a_value_and_a_block assert_raises(ArgumentError) do @config.set(:sample, :value) { :block } end end def test_set_should_not_allow_capitalized_variables assert_raises(ArgumentError) do @config.set :Sample, :value end end def test_unset_should_remove_variable_from_hash @config.set :sample, :value assert @config.variables.key?(:sample) @config.unset :sample assert !@config.variables.key?(:sample) end def test_unset_should_clear_memory_of_original_proc @config.set(:sample) { :value } @config.fetch(:sample) @config.unset(:sample) assert_equal false, @config.reset!(:sample) end def test_exists_should_report_existance_of_variable_in_hash assert !@config.exists?(:sample) @config[:sample] = :value assert @config.exists?(:sample) end def test_reset_should_do_nothing_if_variable_does_not_exist assert_equal false, @config.reset!(:sample) assert !@config.variables.key?(:sample) end def test_reset_should_do_nothing_if_variable_is_not_a_proc @config.set(:sample, :value) assert_equal false, @config.reset!(:sample) assert_equal :value, @config.variables[:sample] end def test_reset_should_do_nothing_if_proc_variable_has_not_been_dereferenced @config.set(:sample) { :value } assert_equal false, @config.reset!(:sample) assert_instance_of Proc, @config.variables[:sample] end def test_reset_should_restore_variable_to_original_proc_value @config.set(:sample) { :value } assert_instance_of Proc, @config.variables[:sample] @config.fetch(:sample) assert_instance_of Symbol, @config.variables[:sample] assert @config.reset!(:sample) assert_instance_of Proc, @config.variables[:sample] end def test_fetch_should_return_stored_non_proc_value @config.set(:sample, :value) assert_equal :value, @config.fetch(:sample) end def test_fetch_should_raise_index_error_if_variable_does_not_exist assert_raises(IndexError) do @config.fetch(:sample) end end def test_fetch_should_return_default_if_variable_does_not_exist_and_default_is_given assert_nothing_raised do assert_equal :default_value, @config.fetch(:sample, :default_value) end end def test_fetch_should_invoke_block_if_variable_does_not_exist_and_block_is_given assert_nothing_raised do assert_equal :default_value, @config.fetch(:sample) { :default_value } end end def test_fetch_should_raise_argument_error_if_both_default_and_block_are_given assert_raises(ArgumentError) do @config.fetch(:sample, :default1) { :default2 } end end def test_fetch_should_dereference_proc_values @config.set(:sample) { :value } assert_instance_of Proc, @config.variables[:sample] assert_equal :value, @config.fetch(:sample) assert_instance_of Symbol, @config.variables[:sample] end def test_square_brackets_should_alias_fetch @config.set(:sample, :value) assert_equal :value, @config[:sample] end def test_square_brackets_should_return_nil_for_non_existant_variable assert_nothing_raised do assert_nil @config[:sample] end end endcapistrano-2.12.0/test/configuration/servers_test.rb0000644000004100000410000001624411746743503022713 0ustar www-datawww-datarequire "utils" require 'capistrano/task_definition' require 'capistrano/configuration/servers' class ConfigurationServersTest < Test::Unit::TestCase class MockConfig attr_reader :roles attr_accessor :preserve_roles def initialize @roles = {} @preserve_roles = false end include Capistrano::Configuration::Servers end def setup @config = MockConfig.new role(@config, :app, "app1", :primary => true) role(@config, :app, "app2", "app3") role(@config, :web, "web1", "web2") role(@config, :report, "app2", :no_deploy => true) role(@config, :file, "file", :no_deploy => true) end def test_task_without_roles_should_apply_to_all_defined_hosts task = new_task(:testing) assert_equal %w(app1 app2 app3 web1 web2 file).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort end def test_task_with_explicit_role_list_should_apply_only_to_those_roles task = new_task(:testing, @config, :roles => %w(app web)) assert_equal %w(app1 app2 app3 web1 web2).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort end def test_task_with_single_role_should_apply_only_to_that_role task = new_task(:testing, @config, :roles => :web) assert_equal %w(web1 web2).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort end # NOTE Rather than throw an error, as it used to, we return an # empty array so that if a task is okay with a missing role it can continue on def test_task_with_unknown_role_should_return_empty_array task = new_task(:testing, @config, :roles => :bogus) assert_equal [], @config.find_servers_for_task(task) end def test_task_with_hosts_option_should_apply_only_to_those_hosts task = new_task(:testing, @config, :hosts => %w(foo bar)) assert_equal %w(foo bar).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort end def test_task_with_single_hosts_option_should_apply_only_to_that_host task = new_task(:testing, @config, :hosts => "foo") assert_equal %w(foo).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort end def test_task_with_roles_as_environment_variable_should_apply_only_to_that_role ENV['ROLES'] = "app,file" task = new_task(:testing) assert_equal %w(app1 app2 app3 file).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('ROLES') end def test_task_with_roles_as_environment_variable_and_preserve_roles_should_apply_only_to_existant_task_role ENV['ROLES'] = "app,file" @config.preserve_roles = true task = new_task(:testing,@config, :roles => :app) assert_equal %w(app1 app2 app3).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('ROLES') end def test_task_with_roles_as_environment_variable_and_preserve_roles_should_apply_only_to_existant_task_roles ENV['ROLES'] = "app,file,web" @config.preserve_roles = true task = new_task(:testing,@config, :roles => [ :app,:file ]) assert_equal %w(app1 app2 app3 file).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('ROLES') end def test_task_with_roles_as_environment_variable_and_preserve_roles_should_not_apply_if_not_exists_those_task_roles ENV['ROLES'] = "file,web" @config.preserve_roles = true task = new_task(:testing,@config, :roles => [ :app ]) assert_equal [], @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('ROLES') end def test_task_with_hosts_as_environment_variable_should_apply_only_to_those_hosts ENV['HOSTS'] = "foo,bar" task = new_task(:testing) assert_equal %w(foo bar).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('HOSTS') end def test_task_with_hosts_as_environment_variable_should_not_inspect_roles_at_all ENV['HOSTS'] = "foo,bar" task = new_task(:testing, @config, :roles => :bogus) assert_equal %w(foo bar).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('HOSTS') end def test_task_with_hostfilter_environment_variable_should_apply_only_to_those_hosts ENV['HOSTFILTER'] = "app1,web1" task = new_task(:testing) assert_equal %w(app1 web1).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('HOSTFILTER') end def test_task_with_hostfilter_environment_variable_should_filter_hosts_option ENV['HOSTFILTER'] = "foo" task = new_task(:testing, @config, :hosts => %w(foo bar)) assert_equal %w(foo).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('HOSTFILTER') end def test_task_with_hostfilter_environment_variable_and_skip_hostfilter_should_not_filter_hosts_option ENV['HOSTFILTER'] = "foo" task = new_task(:testing, @config, :hosts => %w(foo bar), :skip_hostfilter => true) assert_equal %w(foo bar).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('HOSTFILTER') end def test_task_with_hostrolefilter_environment_variable_should_apply_only_to_those_hosts ENV['HOSTROLEFILTER'] = "web" task = new_task(:testing) assert_equal %w(web1 web2).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort ensure ENV.delete('HOSTROLEFILTER') end def test_task_with_only_should_apply_only_to_matching_tasks task = new_task(:testing, @config, :roles => :app, :only => { :primary => true }) assert_equal %w(app1), @config.find_servers_for_task(task).map { |s| s.host } end def test_task_with_except_should_apply_only_to_matching_tasks task = new_task(:testing, @config, :except => { :no_deploy => true }) assert_equal %w(app1 app2 app3 web1 web2).sort, @config.find_servers_for_task(task).map { |s| s.host }.sort end def test_options_to_find_servers_for_task_should_override_options_in_task task = new_task(:testing, @config, :roles => :web) assert_equal %w(app1 app2 app3).sort, @config.find_servers_for_task(task, :roles => :app).map { |s| s.host }.sort end def test_find_servers_with_lambda_for_hosts_should_be_evaluated assert_equal %w(foo), @config.find_servers(:hosts => lambda { "foo" }).map { |s| s.host }.sort assert_equal %w(bar foo), @config.find_servers(:hosts => lambda { %w(foo bar) }).map { |s| s.host }.sort end def test_find_servers_with_lambda_for_roles_should_be_evaluated assert_equal %w(app1 app2 app3), @config.find_servers(:roles => lambda { :app }).map { |s| s.host }.sort assert_equal %w(app2 file), @config.find_servers(:roles => lambda { [:report, :file] }).map { |s| s.host }.sort end def test_find_servers_with_hosts_nil_or_empty assert_equal [], @config.find_servers(:hosts => nil) assert_equal [], @config.find_servers(:hosts => []) result = @config.find_servers(:hosts => @config.find_servers(:roles => :report)[0]) assert_equal 1, result.size result = @config.find_servers(:hosts => "app1") assert_equal 1, result.size end def test_find_servers_with_rolees_nil_or_empty assert_equal [], @config.find_servers(:roles => nil) assert_equal [], @config.find_servers(:roles => []) result = @config.find_servers(:roles => :report) assert_equal 1, result.size end end capistrano-2.12.0/test/configuration/alias_task_test.rb0000644000004100000410000000651011746743503023330 0ustar www-datawww-datarequire 'utils' require 'capistrano/configuration/alias_task' require 'capistrano/configuration/execution' require 'capistrano/configuration/namespaces' require 'capistrano/task_definition' class AliasTaskTest < Test::Unit::TestCase class MockConfig attr_reader :options attr_accessor :logger def initialize(options={}) @options = {} @logger = options.delete(:logger) end include Capistrano::Configuration::AliasTask include Capistrano::Configuration::Execution include Capistrano::Configuration::Namespaces end def setup @config = MockConfig.new( :logger => stub(:debug => nil, :info => nil, :important => nil) ) end def test_makes_a_copy_of_the_task @config.task(:foo) { 42 } @config.alias_task 'new_foo', 'foo' assert @config.tasks.key?(:new_foo) end def test_original_task_remain_with_same_name @config.task(:foo) { 42 } @config.alias_task 'new_foo', 'foo' assert_equal :foo, @config.tasks[:foo].name assert_equal :new_foo, @config.tasks[:new_foo].name end def test_aliased_task_do_the_same @config.task(:foo) { 42 } @config.alias_task 'new_foo', 'foo' assert_equal 42, @config.find_and_execute_task('new_foo') end def test_aliased_task_should_preserve_description @config.task(:foo, :desc => "the Ultimate Question of Life, the Universe, and Everything" ) { 42 } @config.alias_task 'new_foo', 'foo' task = @config.find_task('foo') new_task = @config.find_task('new_foo') assert_equal task.description, new_task.description end def test_aliased_task_should_preserve_on_error @config.task(:foo, :on_error => :continue) { 42 } @config.alias_task 'new_foo', 'foo' task = @config.find_task('foo') new_task = @config.find_task('new_foo') assert_equal task.on_error, new_task.on_error end def test_aliased_task_should_preserve_max_hosts @config.task(:foo, :max_hosts => 5) { 42 } @config.alias_task 'new_foo', 'foo' task = @config.find_task('foo') new_task = @config.find_task('new_foo') assert_equal task.max_hosts, new_task.max_hosts end def test_raise_exception_when_task_doesnt_exist assert_raises(Capistrano::NoSuchTaskError) { @config.alias_task 'non_existant_task', 'fail_miserably' } end def test_convert_task_names_using_to_str @config.task(:foo, :role => :app) { 42 } @config.alias_task 'one', 'foo' @config.alias_task :two, 'foo' @config.alias_task 'three', :foo @config.alias_task :four, :foo assert @config.tasks.key?(:one) assert @config.tasks.key?(:two) assert @config.tasks.key?(:three) assert @config.tasks.key?(:four) end def test_raise_an_exception_when_task_names_can_not_be_converted @config.task(:foo, :role => :app) { 42 } assert_raises(ArgumentError) { @config.alias_task mock('x'), :foo } end def test_should_include_namespace @config.namespace(:outer) do task(:foo) { 42 } alias_task 'new_foo', 'foo' namespace(:inner) do task(:foo) { 43 } alias_task 'new_foo', 'foo' end end assert_equal 42, @config.find_and_execute_task('outer:new_foo') assert_equal 42, @config.find_and_execute_task('outer:foo') assert_equal 43, @config.find_and_execute_task('outer:inner:new_foo') assert_equal 43, @config.find_and_execute_task('outer:inner:foo') end end capistrano-2.12.0/test/configuration/connections_test.rb0000644000004100000410000004524311746743503023545 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/connections' class ConfigurationConnectionsTest < Test::Unit::TestCase class MockConfig attr_reader :original_initialize_called attr_reader :values attr_accessor :current_task def initialize @original_initialize_called = true @values = {} end def fetch(*args) @values.fetch(*args) end def [](key) @values[key] end def exists?(key) @values.key?(key) end include Capistrano::Configuration::Connections end def setup @config = MockConfig.new @config.stubs(:logger).returns(stub_everything) Net::SSH.stubs(:configuration_for).returns({}) @ssh_options = { :user => "user", :port => 8080, :password => "g00b3r", :ssh_options => { :debug => :verbose } } end def test_initialize_should_initialize_collections_and_call_original_initialize assert @config.original_initialize_called assert @config.sessions.empty? end def test_connection_factory_should_return_default_connection_factory_instance factory = @config.connection_factory assert_instance_of Capistrano::Configuration::Connections::DefaultConnectionFactory, factory end def test_connection_factory_instance_should_be_cached assert_same @config.connection_factory, @config.connection_factory end def test_default_connection_factory_honors_config_options server = server("capistrano") Capistrano::SSH.expects(:connect).with(server, @config).returns(:session) assert_equal :session, @config.connection_factory.connect_to(server) end def test_should_connect_through_gateway_if_gateway_variable_is_set @config.values[:gateway] = "j@gateway" Net::SSH::Gateway.expects(:new).with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory end def test_connection_factory_as_gateway_should_honor_config_options @config.values[:gateway] = "gateway" @config.values.update(@ssh_options) Net::SSH::Gateway.expects(:new).with("gateway", "user", :debug => :verbose, :port => 8080, :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory end def test_connection_factory_as_gateway_should_chain_gateways_if_gateway_variable_is_an_array @config.values[:gateway] = ["j@gateway1", "k@gateway2"] gateway1 = mock Net::SSH::Gateway.expects(:new).with("gateway1", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(gateway1) gateway1.expects(:open).returns(65535) Net::SSH::Gateway.expects(:new).with("127.0.0.1", "k", :port => 65535, :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory end def test_connection_factory_as_gateway_should_chain_gateways_if_gateway_variable_is_a_hash @config.values[:gateway] = { ["j@gateway1", "k@gateway2"] => :default } gateway1 = mock Net::SSH::Gateway.expects(:new).with("gateway1", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(gateway1) gateway1.expects(:open).returns(65535) Net::SSH::Gateway.expects(:new).with("127.0.0.1", "k", :port => 65535, :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory end def test_connection_factory_as_gateway_should_share_gateway_between_connections @config.values[:gateway] = "j@gateway" Net::SSH::Gateway.expects(:new).once.with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) Capistrano::SSH.stubs(:connect).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory @config.establish_connections_to(server("capistrano")) @config.establish_connections_to(server("another")) end def test_connection_factory_as_gateway_should_share_gateway_between_like_connections_if_gateway_variable_is_a_hash @config.values[:gateway] = { "j@gateway" => [ "capistrano", "another"] } Net::SSH::Gateway.expects(:new).once.with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) Capistrano::SSH.stubs(:connect).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory @config.establish_connections_to(server("capistrano")) @config.establish_connections_to(server("another")) end def test_connection_factory_as_gateways_should_not_share_gateway_between_unlike_connections_if_gateway_variable_is_a_hash @config.values[:gateway] = { "j@gateway" => [ "capistrano", "another"], "k@gateway2" => "yafhost" } Net::SSH::Gateway.expects(:new).once.with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) Net::SSH::Gateway.expects(:new).once.with("gateway2", "k", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything) Capistrano::SSH.stubs(:connect).returns(stub_everything) assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory @config.establish_connections_to(server("capistrano")) @config.establish_connections_to(server("another")) @config.establish_connections_to(server("yafhost")) end def test_establish_connections_to_should_accept_a_single_nonarray_parameter Capistrano::SSH.expects(:connect).with { |s,| s.host == "capistrano" }.returns(:success) assert @config.sessions.empty? @config.establish_connections_to(server("capistrano")) assert_equal ["capistrano"], @config.sessions.keys.map(&:host) end def test_establish_connections_to_should_accept_an_array Capistrano::SSH.expects(:connect).times(3).returns(:success) assert @config.sessions.empty? @config.establish_connections_to(%w(cap1 cap2 cap3).map { |s| server(s) }) assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map(&:host) end def test_establish_connections_to_should_not_attempt_to_reestablish_existing_connections Capistrano::SSH.expects(:connect).times(2).returns(:success) @config.sessions[server("cap1")] = :ok @config.establish_connections_to(%w(cap1 cap2 cap3).map { |s| server(s) }) assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map(&:host) end def test_establish_connections_to_should_raise_one_connection_error_on_failure Capistrano::SSH.expects(:connect).times(2).raises(Exception) assert_raises(Capistrano::ConnectionError) { @config.establish_connections_to(%w(cap1 cap2).map { |s| server(s) }) } end def test_connection_error_should_include_accessor_with_host_array Capistrano::SSH.expects(:connect).times(2).raises(Exception) begin @config.establish_connections_to(%w(cap1 cap2).map { |s| server(s) }) flunk "expected an exception to be raised" rescue Capistrano::ConnectionError => e assert e.respond_to?(:hosts) assert_equal %w(cap1 cap2), e.hosts.map { |h| h.to_s }.sort end end def test_connection_error_should_only_include_failed_hosts Capistrano::SSH.expects(:connect).with(server('cap1'), anything).raises(Exception) Capistrano::SSH.expects(:connect).with(server('cap2'), anything).returns(:success) begin @config.establish_connections_to(%w(cap1 cap2).map { |s| server(s) }) flunk "expected an exception to be raised" rescue Capistrano::ConnectionError => e assert_equal %w(cap1), e.hosts.map { |h| h.to_s } end end def test_execute_on_servers_should_require_a_block assert_raises(ArgumentError) { @config.execute_on_servers } end def test_execute_on_servers_without_current_task_should_call_find_servers list = [server("first"), server("second")] @config.expects(:find_servers).with(:a => :b, :c => :d).returns(list) @config.expects(:establish_connections_to).with(list).returns(:done) @config.execute_on_servers(:a => :b, :c => :d) do |result| assert_equal list, result end end def test_execute_on_servers_without_current_task_should_raise_error_if_no_matching_servers @config.expects(:find_servers).with(:a => :b, :c => :d).returns([]) assert_raises(Capistrano::NoMatchingServersError) { @config.execute_on_servers(:a => :b, :c => :d) { |list| } } end def test_execute_on_servers_without_current_task_should_not_raise_error_if_no_matching_servers_and_continue_on_no_matching_servers @config.expects(:find_servers).with(:a => :b, :c => :d, :on_no_matching_servers => :continue).returns([]) assert_nothing_raised { @config.execute_on_servers(:a => :b, :c => :d, :on_no_matching_servers => :continue) { |list| } } end def test_execute_on_servers_should_raise_an_error_if_the_current_task_has_no_matching_servers_by_default @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([]) assert_raises(Capistrano::NoMatchingServersError) do @config.execute_on_servers do flunk "should not get here" end end end def test_execute_on_servers_should_not_raise_an_error_if_the_current_task_has_no_matching_servers_by_default_and_continue_on_no_matching_servers @config.current_task = mock_task(:on_no_matching_servers => :continue) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([]) assert_nothing_raised do @config.execute_on_servers do flunk "should not get here" end end end def test_execute_on_servers_should_not_raise_an_error_if_the_current_task_has_no_matching_servers_by_default_and_command_continues_on_no_matching_servers @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, :on_no_matching_servers => :continue).returns([]) assert_nothing_raised do @config.execute_on_servers(:on_no_matching_servers => :continue) do flunk "should not get here" end end end def test_execute_on_servers_should_determine_server_list_from_active_task assert @config.sessions.empty? @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2"), server("cap3")]) Capistrano::SSH.expects(:connect).times(3).returns(:success) @config.execute_on_servers {} assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map { |s| s.host } end def test_execute_on_servers_should_yield_server_list_to_block assert @config.sessions.empty? @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2"), server("cap3")]) Capistrano::SSH.expects(:connect).times(3).returns(:success) block_called = false @config.execute_on_servers do |servers| block_called = true assert servers.detect { |s| s.host == "cap1" } assert servers.detect { |s| s.host == "cap2" } assert servers.detect { |s| s.host == "cap3" } assert servers.all? { |s| @config.sessions[s] } end assert block_called end def test_execute_on_servers_with_once_option_should_establish_connection_to_and_yield_only_the_first_server assert @config.sessions.empty? @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, :once => true).returns([server("cap1"), server("cap2"), server("cap3")]) Capistrano::SSH.expects(:connect).returns(:success) block_called = false @config.execute_on_servers(:once => true) do |servers| block_called = true assert_equal %w(cap1), servers.map { |s| s.host } end assert block_called assert_equal %w(cap1), @config.sessions.keys.sort.map { |s| s.host } end def test_execute_servers_should_raise_connection_error_on_failure_by_default @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1")]) Capistrano::SSH.expects(:connect).raises(Exception) assert_raises(Capistrano::ConnectionError) do @config.execute_on_servers do flunk "expected an exception to be raised" end end end def test_execute_servers_should_not_raise_connection_error_on_failure_with_on_errors_continue @config.current_task = mock_task(:on_error => :continue) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2")]) Capistrano::SSH.expects(:connect).with(server('cap1'), anything).raises(Exception) Capistrano::SSH.expects(:connect).with(server('cap2'), anything).returns(:success) assert_nothing_raised { @config.execute_on_servers do |servers| assert_equal %w(cap2), servers.map { |s| s.host } end } end def test_execute_on_servers_should_not_try_to_connect_to_hosts_with_connection_errors_with_on_errors_continue list = [server("cap1"), server("cap2")] @config.current_task = mock_task(:on_error => :continue) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns(list) Capistrano::SSH.expects(:connect).with(server('cap1'), anything).raises(Exception) Capistrano::SSH.expects(:connect).with(server('cap2'), anything).returns(:success) @config.execute_on_servers do |servers| assert_equal %w(cap2), servers.map { |s| s.host } end @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns(list) @config.execute_on_servers do |servers| assert_equal %w(cap2), servers.map { |s| s.host } end end def test_execute_on_servers_should_not_try_to_connect_to_hosts_with_command_errors_with_on_errors_continue cap1 = server("cap1") cap2 = server("cap2") @config.current_task = mock_task(:on_error => :continue) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2]) Capistrano::SSH.expects(:connect).times(2).returns(:success) @config.execute_on_servers do |servers| error = Capistrano::CommandError.new error.hosts = [cap1] raise error end @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2]) @config.execute_on_servers do |servers| assert_equal %w(cap2), servers.map { |s| s.host } end end def test_execute_on_servers_should_not_try_to_connect_to_hosts_with_transfer_errors_with_on_errors_continue cap1 = server("cap1") cap2 = server("cap2") @config.current_task = mock_task(:on_error => :continue) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2]) Capistrano::SSH.expects(:connect).times(2).returns(:success) @config.execute_on_servers do |servers| error = Capistrano::TransferError.new error.hosts = [cap1] raise error end @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2]) @config.execute_on_servers do |servers| assert_equal %w(cap2), servers.map { |s| s.host } end end def test_connect_should_establish_connections_to_all_servers_in_scope assert @config.sessions.empty? @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2"), server("cap3")]) Capistrano::SSH.expects(:connect).times(3).returns(:success) @config.connect! assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map { |s| s.host } end def test_execute_on_servers_should_only_run_on_tasks_max_hosts_hosts_at_once cap1 = server("cap1") cap2 = server("cap2") connection1 = mock() connection2 = mock() connection1.expects(:close) connection2.expects(:close) @config.current_task = mock_task(:max_hosts => 1) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2]) Capistrano::SSH.expects(:connect).times(2).returns(connection1).then.returns(connection2) block_called = 0 @config.execute_on_servers do |servers| block_called += 1 assert_equal 1, servers.size end assert_equal 2, block_called end def test_execute_on_servers_should_only_run_on_max_hosts_hosts_at_once cap1 = server("cap1") cap2 = server("cap2") connection1 = mock() connection2 = mock() connection1.expects(:close) connection2.expects(:close) @config.current_task = mock_task(:max_hosts => 1) @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2]) Capistrano::SSH.expects(:connect).times(2).returns(connection1).then.returns(connection2) block_called = 0 @config.execute_on_servers do |servers| block_called += 1 assert_equal 1, servers.size end assert_equal 2, block_called end def test_execute_on_servers_should_cope_with_already_dropped_connections_when_attempting_to_close_them cap1 = server("cap1") cap2 = server("cap2") connection1 = mock() connection2 = mock() connection3 = mock() connection4 = mock() connection1.expects(:close).raises(IOError) connection2.expects(:close) connection3.expects(:close) connection4.expects(:close) @config.current_task = mock_task(:max_hosts => 1) @config.expects(:find_servers_for_task).times(2).with(@config.current_task, {}).returns([cap1, cap2]) Capistrano::SSH.expects(:connect).times(4).returns(connection1).then.returns(connection2).then.returns(connection3).then.returns(connection4) @config.execute_on_servers {} @config.execute_on_servers {} end def test_connect_should_honor_once_option assert @config.sessions.empty? @config.current_task = mock_task @config.expects(:find_servers_for_task).with(@config.current_task, :once => true).returns([server("cap1"), server("cap2"), server("cap3")]) Capistrano::SSH.expects(:connect).returns(:success) @config.connect! :once => true assert_equal %w(cap1), @config.sessions.keys.sort.map { |s| s.host } end private def mock_task(options={}) continue_on_error = options[:on_error] == :continue stub("task", :fully_qualified_name => "name", :options => options, :continue_on_error? => continue_on_error, :max_hosts => options[:max_hosts] ) end end capistrano-2.12.0/test/configuration/execution_test.rb0000644000004100000410000001536611746743503023231 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/execution' require 'capistrano/task_definition' class ConfigurationExecutionTest < Test::Unit::TestCase class MockConfig attr_reader :tasks, :namespaces, :fully_qualified_name, :parent attr_reader :state, :original_initialize_called attr_accessor :logger, :default_task def initialize(options={}) @original_initialize_called = true @tasks = {} @namespaces = {} @state = {} @fully_qualified_name = options[:fqn] @parent = options[:parent] @logger = options.delete(:logger) end include Capistrano::Configuration::Execution end def setup @config = MockConfig.new(:logger => stub(:debug => nil, :info => nil, :important => nil)) @config.stubs(:search_task).returns(nil) end def test_initialize_should_initialize_collections assert_nil @config.rollback_requests assert @config.original_initialize_called assert @config.task_call_frames.empty? end def test_execute_task_should_populate_call_stack task = new_task @config, :testing assert_nothing_raised { @config.execute_task(task) } assert_equal %w(testing), @config.state[:testing][:stack] assert_nil @config.state[:testing][:history] assert @config.task_call_frames.empty? end def test_nested_execute_task_should_add_to_call_stack testing = new_task @config, :testing outer = new_task(@config, :outer) { execute_task(testing) } assert_nothing_raised { @config.execute_task(outer) } assert_equal %w(outer testing), @config.state[:testing][:stack] assert_nil @config.state[:testing][:history] assert @config.task_call_frames.empty? end def test_execute_task_should_execute_in_scope_of_tasks_parent ns = stub("namespace", :tasks => {}, :default_task => nil, :fully_qualified_name => "ns") ns.expects(:instance_eval) testing = new_task ns, :testing @config.execute_task(testing) end def test_transaction_outside_of_task_should_raise_exception assert_raises(ScriptError) { @config.transaction {} } end def test_transaction_without_block_should_raise_argument_error testing = new_task(@config, :testing) { transaction } assert_raises(ArgumentError) { @config.execute_task(testing) } end def test_transaction_should_initialize_transaction_history @config.state[:inspector] = stack_inspector testing = new_task(@config, :testing) { transaction { instance_eval(&state[:inspector]) } } @config.execute_task(testing) assert_equal [], @config.state[:testing][:history] end def test_transaction_from_within_transaction_should_not_start_new_transaction third = new_task(@config, :third, &stack_inspector) second = new_task(@config, :second) { transaction { execute_task(third) } } first = new_task(@config, :first) { transaction { execute_task(second) } } # kind of fragile...not sure how else to check that transaction was only # really run twice...but if the transaction was REALLY run, logger.info # will be called once when it starts, and once when it finishes. @config.logger = mock() @config.logger.stubs(:debug) @config.logger.expects(:info).times(2) @config.execute_task(first) end def test_on_rollback_should_have_no_effect_outside_of_transaction aaa = new_task(@config, :aaa) { on_rollback { state[:rollback] = true }; raise "boom" } assert_raises(RuntimeError) { @config.execute_task(aaa) } assert_nil @config.state[:rollback] end def test_exception_raised_in_transaction_should_call_all_registered_rollback_handlers_in_reverse_order aaa = new_task(@config, :aaa) { on_rollback { (state[:rollback] ||= []) << :aaa } } bbb = new_task(@config, :bbb) { on_rollback { (state[:rollback] ||= []) << :bbb } } ccc = new_task(@config, :ccc) {} ddd = new_task(@config, :ddd) { on_rollback { (state[:rollback] ||= []) << :ddd }; execute_task(bbb); execute_task(ccc) } eee = new_task(@config, :eee) { transaction { execute_task(ddd); execute_task(aaa); raise "boom" } } assert_raises(RuntimeError) do @config.execute_task(eee) end assert_equal [:aaa, :bbb, :ddd], @config.state[:rollback] assert_nil @config.rollback_requests assert @config.task_call_frames.empty? end def test_exception_during_rollback_should_simply_be_logged_and_ignored aaa = new_task(@config, :aaa) { on_rollback { state[:aaa] = true; raise LoadError, "ouch" }; execute_task(bbb) } bbb = new_task(@config, :bbb) { raise MadError, "boom" } ccc = new_task(@config, :ccc) { transaction { execute_task(aaa) } } assert_raises(NameError) do @config.execute_task(ccc) end assert @config.state[:aaa] end def test_on_rollback_called_twice_should_result_in_last_rollback_block_being_effective aaa = new_task(@config, :aaa) do transaction do on_rollback { (state[:rollback] ||= []) << :first } on_rollback { (state[:rollback] ||= []) << :second } raise "boom" end end assert_raises(RuntimeError) do @config.execute_task(aaa) end assert_equal [:second], @config.state[:rollback] end def test_find_and_execute_task_should_raise_error_when_task_cannot_be_found @config.expects(:find_task).with("path:to:task").returns(nil) assert_raises(Capistrano::NoSuchTaskError) { @config.find_and_execute_task("path:to:task") } end def test_find_and_execute_task_should_execute_task_when_task_is_found @config.expects(:find_task).with("path:to:task").returns(:found) @config.expects(:execute_task).with(:found) assert_nothing_raised { @config.find_and_execute_task("path:to:task") } end def test_find_and_execute_task_with_before_option_should_trigger_callback @config.expects(:find_task).with("path:to:task").returns(:found) @config.expects(:trigger).with(:incoming, :found) @config.expects(:execute_task).with(:found) @config.find_and_execute_task("path:to:task", :before => :incoming) end def test_find_and_execute_task_with_after_option_should_trigger_callback @config.expects(:find_task).with("path:to:task").returns(:found) @config.expects(:trigger).with(:outgoing, :found) @config.expects(:execute_task).with(:found) @config.find_and_execute_task("path:to:task", :after => :outgoing) end private def stack_inspector Proc.new do (state[:trail] ||= []) << current_task.fully_qualified_name data = state[current_task.name] = {} data[:stack] = task_call_frames.map { |frame| frame.task.fully_qualified_name } data[:history] = rollback_requests && rollback_requests.map { |frame| frame.task.fully_qualified_name } end end def new_task(namespace, name, options={}, &block) block ||= stack_inspector namespace.tasks[name] = Capistrano::TaskDefinition.new(name, namespace, &block) end endcapistrano-2.12.0/test/configuration/callbacks_test.rb0000644000004100000410000001560111746743503023135 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration/callbacks' class ConfigurationCallbacksTest < Test::Unit::TestCase class MockConfig attr_reader :original_initialize_called attr_reader :called def initialize @original_initialize_called = true @called = [] end def execute_task(task) invoke_task_directly(task) end protected def invoke_task_directly(task) @called << task end include Capistrano::Configuration::Callbacks end def setup @config = MockConfig.new @config.stubs(:logger).returns(stub_everything("logger")) end def test_initialize_should_initialize_callbacks_collection assert @config.original_initialize_called assert @config.callbacks.empty? end def test_before_should_delegate_to_on @config.expects(:on).with(:before, :foo, "bing:blang", {:only => :bar, :zip => :zing}) @config.before :bar, :foo, "bing:blang", :zip => :zing end def test_after_should_delegate_to_on @config.expects(:on).with(:after, :foo, "bing:blang", {:only => :bar, :zip => :zing}) @config.after :bar, :foo, "bing:blang", :zip => :zing end def test_on_with_single_reference_should_add_task_callback @config.on :before, :a_test assert_equal 1, @config.callbacks[:before].length assert_equal :a_test, @config.callbacks[:before][0].source @config.expects(:find_and_execute_task).with(:a_test) @config.callbacks[:before][0].call end def test_on_with_multi_reference_should_add_all_as_task_callback @config.on :before, :first, :second, :third assert_equal 3, @config.callbacks[:before].length assert_equal %w(first second third), @config.callbacks[:before].map { |c| c.source.to_s } end def test_on_with_block_should_add_block_as_proc_callback called = false @config.on(:before) { called = true } assert_equal 1, @config.callbacks[:before].length assert_instance_of Proc, @config.callbacks[:before][0].source @config.callbacks[:before][0].call assert called end def test_on_with_single_only_should_set_only_as_string_array_on_all_references @config.on :before, :first, "second:third", :only => :primary assert_equal 2, @config.callbacks[:before].length assert @config.callbacks[:before].all? { |c| c.only == %w(primary) } end def test_on_with_multi_only_should_set_only_as_string_array_on_all_references @config.on :before, :first, "second:third", :only => [:primary, "other:one"] assert_equal 2, @config.callbacks[:before].length assert @config.callbacks[:before].all? { |c| c.only == %w(primary other:one) } end def test_on_with_single_except_should_set_except_as_string_array_on_all_references @config.on :before, :first, "second:third", :except => :primary assert_equal 2, @config.callbacks[:before].length assert @config.callbacks[:before].all? { |c| c.except == %w(primary) } end def test_on_with_multi_except_should_set_except_as_string_array_on_all_references @config.on :before, :first, "second:third", :except => [:primary, "other:one"] assert_equal 2, @config.callbacks[:before].length assert @config.callbacks[:before].all? { |c| c.except == %w(primary other:one) } end def test_on_with_only_and_block_should_set_only_as_string_array @config.on(:before, :only => :primary) { blah } assert_equal 1, @config.callbacks[:before].length assert_equal %w(primary), @config.callbacks[:before].first.only end def test_on_with_except_and_block_should_set_except_as_string_array @config.on(:before, :except => :primary) { blah } assert_equal 1, @config.callbacks[:before].length assert_equal %w(primary), @config.callbacks[:before].first.except end def test_on_without_tasks_or_block_should_raise_error assert_raises(ArgumentError) { @config.on(:before) } end def test_on_with_both_tasks_and_block_should_raise_error assert_raises(ArgumentError) { @config.on(:before, :first) { blah } } end def test_trigger_without_constraints_should_invoke_all_callbacks task = stub(:fully_qualified_name => "any:old:thing") @config.on(:before, :first, "second:third") @config.on(:after, :another, "and:another") @config.expects(:find_and_execute_task).with(:first) @config.expects(:find_and_execute_task).with("second:third") @config.expects(:find_and_execute_task).with(:another).never @config.expects(:find_and_execute_task).with("and:another").never @config.trigger(:before, task) end def test_trigger_with_only_constraint_should_invoke_only_matching_callbacks task = stub(:fully_qualified_name => "any:old:thing") @config.on(:before, :first) @config.on(:before, "second:third", :only => "any:old:thing") @config.on(:before, "this:too", :only => "any:other:thing") @config.on(:after, :another, "and:another") @config.expects(:find_and_execute_task).with(:first) @config.expects(:find_and_execute_task).with("second:third") @config.expects(:find_and_execute_task).with("this:too").never @config.expects(:find_and_execute_task).with(:another).never @config.expects(:find_and_execute_task).with("and:another").never @config.trigger(:before, task) end def test_trigger_with_except_constraint_should_invoke_anything_but_matching_callbacks task = stub(:fully_qualified_name => "any:old:thing") @config.on(:before, :first) @config.on(:before, "second:third", :except => "any:old:thing") @config.on(:before, "this:too", :except => "any:other:thing") @config.on(:after, :another, "and:another") @config.expects(:find_and_execute_task).with(:first) @config.expects(:find_and_execute_task).with("second:third").never @config.expects(:find_and_execute_task).with("this:too") @config.expects(:find_and_execute_task).with(:another).never @config.expects(:find_and_execute_task).with("and:another").never @config.trigger(:before, task) end def test_trigger_without_task_should_invoke_all_callbacks_for_that_event task = stub(:fully_qualified_name => "any:old:thing") @config.on(:before, :first) @config.on(:before, "second:third", :except => "any:old:thing") @config.on(:before, "this:too", :except => "any:other:thing") @config.on(:after, :another, "and:another") @config.expects(:find_and_execute_task).with(:first) @config.expects(:find_and_execute_task).with("second:third") @config.expects(:find_and_execute_task).with("this:too") @config.expects(:find_and_execute_task).with(:another).never @config.expects(:find_and_execute_task).with("and:another").never @config.trigger(:before) end def test_execute_task_without_named_hooks_should_just_call_task ns = stub("namespace", :default_task => nil, :name => "old", :fully_qualified_name => "any:old") task = stub(:fully_qualified_name => "any:old:thing", :name => "thing", :namespace => ns) ns.stubs(:search_task).returns(nil) @config.execute_task(task) assert_equal [task], @config.called end end capistrano-2.12.0/test/configuration/loading_test.rb0000644000004100000410000001006511746743503022632 0ustar www-datawww-datarequire 'utils' require 'capistrano/configuration/loading' class ConfigurationLoadingTest < Test::Unit::TestCase class MockConfig attr_accessor :ping attr_reader :original_initialize_called def initialize @original_initialize_called = true end def ping!(value) @ping = value end include Capistrano::Configuration::Loading end def setup @config = MockConfig.new end def teardown MockConfig.instance = nil $LOADED_FEATURES.delete_if { |a| a =~ /fixtures\/custom\.rb$/ } end def test_initialize_should_init_collections assert @config.original_initialize_called assert @config.load_paths.include?(".") assert @config.load_paths.detect { |v| v =~ /capistrano\/recipes$/ } end def test_load_with_options_and_block_should_raise_argument_error assert_raises(ArgumentError) do @config.load(:string => "foo") { something } end end def test_load_with_arguments_and_block_should_raise_argument_error assert_raises(ArgumentError) do @config.load("foo") { something } end end def test_load_from_string_should_eval_in_config_scope @config.load :string => "ping! :here" assert_equal :here, @config.ping end def test_load_from_file_shoudld_respect_load_path File.stubs(:file?).returns(false) File.stubs(:file?).with("custom/path/for/file.rb").returns(true) File.stubs(:read).with("custom/path/for/file.rb").returns("ping! :here") @config.load_paths << "custom/path/for" @config.load :file => "file.rb" assert_equal :here, @config.ping end def test_load_from_file_should_respect_load_path_and_appends_rb File.stubs(:file?).returns(false) File.stubs(:file?).with("custom/path/for/file.rb").returns(true) File.stubs(:read).with("custom/path/for/file.rb").returns("ping! :here") @config.load_paths << "custom/path/for" @config.load :file => "file" assert_equal :here, @config.ping end def test_load_from_file_should_raise_load_error_if_file_cannot_be_found File.stubs(:file?).returns(false) assert_raises(LoadError) do @config.load :file => "file" end end def test_load_from_proc_should_eval_proc_in_config_scope @config.load :proc => Proc.new { ping! :here } assert_equal :here, @config.ping end def test_load_with_block_should_treat_block_as_proc_parameter @config.load { ping! :here } assert_equal :here, @config.ping end def test_load_with_unrecognized_option_should_raise_argument_error assert_raises(ArgumentError) do @config.load :url => "http://www.load-this.test" end end def test_load_with_arguments_should_treat_arguments_as_files File.stubs(:file?).returns(false) File.stubs(:file?).with("./first.rb").returns(true) File.stubs(:file?).with("./second.rb").returns(true) File.stubs(:read).with("./first.rb").returns("ping! 'this'") File.stubs(:read).with("./second.rb").returns("ping << 'that'") assert_nothing_raised { @config.load "first", "second" } assert_equal "thisthat", @config.ping end def test_require_from_config_should_load_file_in_config_scope assert_nothing_raised do @config.require "#{File.expand_path(File.dirname(__FILE__))}/../fixtures/custom" end assert_equal :custom, @config.ping end def test_require_without_config_should_raise_load_error assert_raises(LoadError) do require "#{File.dirname(__FILE__)}/../fixtures/custom" end end def test_require_from_config_should_return_false_when_called_a_second_time_with_same_args assert @config.require("#{File.expand_path(File.dirname(__FILE__))}/../fixtures/custom") assert_equal false, @config.require("#{File.expand_path(File.dirname(__FILE__))}/../fixtures/custom") end def test_require_in_multiple_instances_should_load_recipes_in_each_instance config2 = MockConfig.new @config.require "#{File.expand_path(File.dirname(__FILE__))}/../fixtures/custom" config2.require "#{File.expand_path(File.dirname(__FILE__))}/../fixtures/custom" assert_equal :custom, @config.ping assert_equal :custom, config2.ping end end capistrano-2.12.0/test/utils.rb0000644000004100000410000000155611746743503016454 0ustar www-datawww-datarequire 'rubygems' require 'bundler/setup' require 'test/unit' require 'mocha' require 'capistrano/server_definition' module TestExtensions def server(host, options={}) Capistrano::ServerDefinition.new(host, options) end def namespace(fqn=nil) space = stub(:roles => {}, :fully_qualified_name => fqn, :default_task => nil) yield(space) if block_given? space end def role(space, name, *args) opts = args.last.is_a?(Hash) ? args.pop : {} space.roles[name] ||= [] space.roles[name].concat(args.map { |h| Capistrano::ServerDefinition.new(h, opts) }) end def new_task(name, namespace=@namespace, options={}, &block) block ||= Proc.new {} task = Capistrano::TaskDefinition.new(name, namespace, options, &block) assert_equal block, task.body return task end end class Test::Unit::TestCase include TestExtensions end capistrano-2.12.0/test/shell_test.rb0000644000004100000410000000530511746743503017456 0ustar www-datawww-datarequire "utils" require 'capistrano/configuration' require 'capistrano/shell' class ShellTest < Test::Unit::TestCase def setup @config = Capistrano::Configuration.new @shell = Capistrano::Shell.new(@config) @shell.stubs(:puts) end def test_readline_fallback_prompt_should_write_to_stdout_and_read_from_stdin STDOUT.expects(:print).with("prompt> ") STDOUT.expects(:flush) STDIN.expects(:gets).returns("hi\n") assert_equal "hi\n", Capistrano::Shell::ReadlineFallback.readline("prompt> ") end def test_question_mark_as_input_should_trigger_help @shell.expects(:read_line).returns("?") @shell.expects(:help) assert @shell.read_and_execute end def test_help_as_input_should_trigger_help @shell.expects(:read_line).returns("help") @shell.expects(:help) assert @shell.read_and_execute end def test_quit_as_input_should_cause_read_and_execute_to_return_false @shell.expects(:read_line).returns("quit") assert !@shell.read_and_execute end def test_exit_as_input_should_cause_read_and_execute_to_return_false @shell.expects(:read_line).returns("exit") assert !@shell.read_and_execute end def test_set_should_parse_flag_and_value_and_call_set_option @shell.expects(:read_line).returns("set -v 5") @shell.expects(:set_option).with("v", "5") assert @shell.read_and_execute end def test_text_without_with_or_on_gets_processed_verbatim @shell.expects(:read_line).returns("hello world") @shell.expects(:process_command).with(nil, nil, "hello world") assert @shell.read_and_execute end def test_text_with_with_gets_processed_with_with # lol @shell.expects(:read_line).returns("with app,db hello world") @shell.expects(:process_command).with("with", "app,db", "hello world") assert @shell.read_and_execute end def test_text_with_on_gets_processed_with_on @shell.expects(:read_line).returns("on app,db hello world") @shell.expects(:process_command).with("on", "app,db", "hello world") assert @shell.read_and_execute end def test_task_command_with_bang_gets_processed_by_exec_tasks while_testing_post_exec_commands do @shell.expects(:read_line).returns("!deploy") @shell.expects(:exec_tasks).with(["deploy"]) assert @shell.read_and_execute end end def test_normal_command_gets_processed_by_exec_command while_testing_post_exec_commands do @shell.expects(:read_line).returns("uptime") @shell.expects(:exec_command).with("uptime",nil) @shell.expects(:connect) assert @shell.read_and_execute end end private def while_testing_post_exec_commands(&block) @shell.instance_variable_set(:@mutex,Mutex.new) yield end end capistrano-2.12.0/test/recipes_test.rb0000644000004100000410000000101311746743503017771 0ustar www-datawww-datarequire 'utils' require 'capistrano/configuration' class RecipesTest < Test::Unit::TestCase def setup @config = Capistrano::Configuration.new @config.stubs(:logger).returns(stub_everything) end def test_current_releases_does_not_cause_error_on_dry_run @config.dry_run = true @config.load 'deploy' @config.load do set :application, "foo" task :dry_run_test do fetch :current_release end end assert_nothing_raised do @config.dry_run_test end end endcapistrano-2.12.0/test/role_test.rb0000644000004100000410000000051311746743503017304 0ustar www-datawww-datarequire "utils" require 'capistrano/role' class RoleTest < Test::Unit::TestCase def test_clearing_a_populated_role_should_yield_no_servers role = Capistrano::Role.new("app1.capistrano.test", lambda { |o| "app2.capistrano.test" }) assert_equal 2, role.servers.size role.clear assert role.servers.empty? end end capistrano-2.12.0/test/ssh_test.rb0000644000004100000410000001517111746743503017146 0ustar www-datawww-datarequire "utils" require 'capistrano/ssh' class SSHTest < Test::Unit::TestCase def setup Capistrano::ServerDefinition.stubs(:default_user).returns("default-user") @options = { :password => nil, :auth_methods => %w(publickey hostbased), :config => false } @server = server("capistrano") Net::SSH.stubs(:configuration_for).returns({}) end def test_connect_with_bare_server_without_options_or_config_with_public_key_succeeding_should_only_loop_once Net::SSH.expects(:start).with(@server.host, "default-user", @options).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server) end def test_connect_with_bare_server_without_options_with_public_key_failing_should_try_password Net::SSH.expects(:start).with(@server.host, "default-user", @options).raises(Net::SSH::AuthenticationFailed) Net::SSH.expects(:start).with(@server.host, "default-user", @options.merge(:password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :password => "f4b13n") end def test_connect_with_bare_server_without_options_public_key_and_password_failing_should_raise_error Net::SSH.expects(:start).with(@server.host, "default-user", @options).raises(Net::SSH::AuthenticationFailed) Net::SSH.expects(:start).with(@server.host, "default-user", @options.merge(:password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).raises(Net::SSH::AuthenticationFailed) assert_raises(Net::SSH::AuthenticationFailed) do Capistrano::SSH.connect(@server, :password => "f4b13n") end end def test_connect_with_bare_server_and_user_via_public_key_should_pass_user_to_net_ssh Net::SSH.expects(:start).with(@server.host, "jamis", @options).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :user => "jamis") end def test_connect_with_bare_server_and_user_via_password_should_pass_user_to_net_ssh Net::SSH.expects(:start).with(@server.host, "jamis", @options).raises(Net::SSH::AuthenticationFailed) Net::SSH.expects(:start).with(@server.host, "jamis", @options.merge(:password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :user => "jamis", :password => "f4b13n") end def test_connect_with_bare_server_with_explicit_port_should_pass_port_to_net_ssh Net::SSH.expects(:start).with(@server.host, "default-user", @options.merge(:port => 1234)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :port => 1234) end def test_connect_with_server_with_user_should_pass_user_to_net_ssh server = server("jamis@capistrano") Net::SSH.expects(:start).with(server.host, "jamis", @options).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_server_with_port_should_pass_port_to_net_ssh server = server("capistrano:1235") Net::SSH.expects(:start).with(server.host, "default-user", @options.merge(:port => 1235)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_server_with_user_and_port_should_pass_user_and_port_to_net_ssh server = server("jamis@capistrano:1235") Net::SSH.expects(:start).with(server.host, "jamis", @options.merge(:port => 1235)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_server_with_other_ssh_options_should_pass_ssh_options_to_net_ssh server = server("jamis@capistrano:1235", :ssh_options => { :keys => %w(some_valid_key), :auth_methods => %w(a_method), :hmac => 'none' }) Net::SSH.expects(:start).with(server.host, "jamis", @options.merge(:port => 1235, :keys => %w(some_valid_key), :auth_methods => %w(a_method), :hmac => 'none' )).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_ssh_options_should_use_ssh_options ssh_options = { :username => "JamisMan", :port => 8125, :config => false } Net::SSH.expects(:start).with(@server.host, "JamisMan", @options.merge(:port => 8125, :config => false)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, {:ssh_options => ssh_options}) end def test_connect_with_options_and_ssh_options_should_see_options_override_ssh_options ssh_options = { :username => "JamisMan", :port => 8125, :forward_agent => true } Net::SSH.expects(:start).with(@server.host, "jamis", @options.merge(:port => 1235, :forward_agent => true)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :ssh_options => ssh_options, :user => "jamis", :port => 1235) end def test_connect_with_verbose_option_should_set_verbose_option_on_ssh Net::SSH.expects(:start).with(@server.host, "default-user", @options).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :verbose => 0) Net::SSH.expects(:start).with(@server.host, "default-user", @options.merge(:verbose => :debug)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :verbose => 1) Net::SSH.expects(:start).with(@server.host, "default-user", @options.merge(:verbose => :debug)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server, :verbose => 2) end def test_connect_with_ssh_options_should_see_server_options_override_ssh_options ssh_options = { :username => "JamisMan", :port => 8125, :forward_agent => true } server = server("jamis@capistrano:1235") Net::SSH.expects(:start).with(server.host, "jamis", @options.merge(:port => 1235, :forward_agent => true, :config => false)).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(server, {:ssh_options => ssh_options}) end def test_connect_should_add_xserver_accessor_to_connection Net::SSH.expects(:start).with(@server.host, "default-user", @options).returns(success = Object.new) assert_equal success, Capistrano::SSH.connect(@server) assert success.respond_to?(:xserver) assert success.respond_to?(:xserver) assert_equal success.xserver, @server end def test_connect_should_not_retry_if_custom_auth_methods_are_given Net::SSH.expects(:start).with(@server.host, "default-user", @options.merge(:auth_methods => %w(publickey))).raises(Net::SSH::AuthenticationFailed) assert_raises(Net::SSH::AuthenticationFailed) { Capistrano::SSH.connect(@server, :ssh_options => { :auth_methods => %w(publickey) }) } end end capistrano-2.12.0/test/transfer_test.rb0000644000004100000410000001552711746743503020202 0ustar www-datawww-datarequire 'utils' require 'capistrano/transfer' class TransferTest < Test::Unit::TestCase def test_class_process_should_delegate_to_instance_process Capistrano::Transfer.expects(:new).with(:up, "from", "to", %w(a b c), {}).returns(mock('transfer', :process! => nil)).yields yielded = false Capistrano::Transfer.process(:up, "from", "to", %w(a b c), {}) { yielded = true } assert yielded end def test_default_transport_is_sftp transfer = Capistrano::Transfer.new(:up, "from", "to", []) assert_equal :sftp, transfer.transport end def test_active_is_true_when_any_sftp_transfers_are_active returns = [false, false, true] sessions = [session('app1', :sftp), session('app2', :sftp), session('app3', :sftp)].each { |s| s.xsftp.expects(:upload).returns(stub('operation', :active? => returns.shift)) } transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :sftp) assert_equal true, transfer.active? end def test_active_is_false_when_all_sftp_transfers_are_not_active sessions = [session('app1', :sftp), session('app2', :sftp)].each { |s| s.xsftp.expects(:upload).returns(stub('operation', :active? => false)) } transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :sftp) assert_equal false, transfer.active? end def test_active_is_true_when_any_scp_transfers_are_active returns = [false, false, true] sessions = [session('app1', :scp), session('app2', :scp), session('app3', :scp)].each do |s| channel = stub('channel', :[]= => nil, :active? => returns.shift) s.scp.expects(:upload).returns(channel) end transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :scp) assert_equal true, transfer.active? end def test_active_is_false_when_all_scp_transfers_are_not_active sessions = [session('app1', :scp), session('app2', :scp), session('app3', :scp)].each do |s| channel = stub('channel', :[]= => nil, :active? => false) s.scp.expects(:upload).returns(channel) end transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :scp) assert_equal false, transfer.active? end [:up, :down].each do |direction| define_method("test_sftp_#{direction}load_from_file_to_file_should_normalize_from_and_to") do sessions = [session('app1', :sftp), session('app2', :sftp)] sessions.each do |session| session.xsftp.expects("#{direction}load".to_sym).with("from-#{session.xserver.host}", "to-#{session.xserver.host}", :properties => { :server => session.xserver, :host => session.xserver.host }) end transfer = Capistrano::Transfer.new(direction, "from-$CAPISTRANO:HOST$", "to-$CAPISTRANO:HOST$", sessions) end define_method("test_scp_#{direction}load_from_file_to_file_should_normalize_from_and_to") do sessions = [session('app1', :scp), session('app2', :scp)] sessions.each do |session| session.scp.expects("#{direction}load".to_sym).returns({}).with("from-#{session.xserver.host}", "to-#{session.xserver.host}", :via => :scp) end transfer = Capistrano::Transfer.new(direction, "from-$CAPISTRANO:HOST$", "to-$CAPISTRANO:HOST$", sessions, :via => :scp) end end def test_sftp_upload_from_IO_to_file_should_clone_the_IO_for_each_connection sessions = [session('app1', :sftp), session('app2', :sftp)] io = StringIO.new("from here") sessions.each do |session| session.xsftp.expects(:upload).with do |from, to, opts| from != io && from.is_a?(StringIO) && from.string == io.string && to == "/to/here-#{session.xserver.host}" && opts[:properties][:server] == session.xserver && opts[:properties][:host] == session.xserver.host end end transfer = Capistrano::Transfer.new(:up, StringIO.new("from here"), "/to/here-$CAPISTRANO:HOST$", sessions) end def test_scp_upload_from_IO_to_file_should_clone_the_IO_for_each_connection sessions = [session('app1', :scp), session('app2', :scp)] io = StringIO.new("from here") sessions.each do |session| channel = mock('channel') channel.expects(:[]=).with(:server, session.xserver) channel.expects(:[]=).with(:host, session.xserver.host) session.scp.expects(:upload).returns(channel).with do |from, to, opts| from != io && from.is_a?(StringIO) && from.string == io.string && to == "/to/here-#{session.xserver.host}" end end transfer = Capistrano::Transfer.new(:up, StringIO.new("from here"), "/to/here-$CAPISTRANO:HOST$", sessions, :via => :scp) end def test_process_should_block_until_transfer_is_no_longer_active transfer = Capistrano::Transfer.new(:up, "from", "to", []) transfer.expects(:process_iteration).times(4).yields.returns(true,true,true,false) transfer.expects(:active?).times(4) transfer.process! end def test_errors_raised_for_a_sftp_session_should_abort_session_and_continue_with_remaining_sessions s = session('app1') error = ExceptionWithSession.new(s) transfer = Capistrano::Transfer.new(:up, "from", "to", []) transfer.expects(:process_iteration).raises(error).times(3).returns(true, false) txfr = mock('transfer', :abort! => true) txfr.expects(:[]=).with(:failed, true) txfr.expects(:[]=).with(:error, error) transfer.expects(:session_map).returns(s => txfr) transfer.process! end def test_errors_raised_for_a_scp_session_should_abort_session_and_continue_with_remaining_sessions s = session('app1') error = ExceptionWithSession.new(s) transfer = Capistrano::Transfer.new(:up, "from", "to", [], :via => :scp) transfer.expects(:process_iteration).raises(error).times(3).returns(true, false) txfr = mock('channel', :close => true) txfr.expects(:[]=).with(:failed, true) txfr.expects(:[]=).with(:error, error) transfer.expects(:session_map).returns(s => txfr) transfer.process! end def test_uploading_a_non_existing_file_should_raise_an_understandable_error s = session('app1') error = Capistrano::Processable::SessionAssociation.on(ArgumentError.new('expected a file to upload'), s) transfer = Capistrano::Transfer.new(:up, "from", "to", [], :via => :scp) transfer.expects(:process_iteration).raises(error) assert_raise(ArgumentError, 'expected a file to upload') { transfer.process! } end private class ExceptionWithSession < ::Exception attr_reader :session def initialize(session) @session = session super() end end def session(host, mode=nil) session = stub('session', :xserver => stub('server', :host => host)) case mode when :sftp sftp = stub('sftp') session.expects(:sftp).with(false).returns(sftp) sftp.expects(:connect).yields(sftp).returns(sftp) session.stubs(:xsftp).returns(sftp) when :scp session.stubs(:scp).returns(stub('scp')) end session end endcapistrano-2.12.0/test/cli_test.rb0000644000004100000410000000107011746743503017111 0ustar www-datawww-datarequire "utils" require 'capistrano/cli' class CLI_Test < Test::Unit::TestCase def test_options_ui_and_help_modules_should_integrate_successfully_with_configuration cli = Capistrano::CLI.parse(%w(-T -x -X)) cli.expects(:puts).at_least_once cli.execute! end def test_options_and_execute_modules_should_integrate_successfully_with_configuration path = "#{File.dirname(__FILE__)}/fixtures/cli_integration.rb" cli = Capistrano::CLI.parse(%W(-x -X -q -f #{path} testing)) config = cli.execute! assert config[:testing_occurred] end end capistrano-2.12.0/test/server_definition_test.rb0000644000004100000410000001056011746743503022064 0ustar www-datawww-datarequire "utils" require 'capistrano/server_definition' class ServerDefinitionTest < Test::Unit::TestCase def test_new_without_credentials_or_port_should_set_values_to_defaults server = Capistrano::ServerDefinition.new("www.capistrano.test") assert_equal "www.capistrano.test", server.host assert_nil server.user assert_nil server.port end def test_new_with_encoded_user_should_extract_user_and_use_default_port server = Capistrano::ServerDefinition.new("jamis@www.capistrano.test") assert_equal "www.capistrano.test", server.host assert_equal "jamis", server.user assert_nil server.port end def test_new_with_encoded_port_should_extract_port_and_use_default_user server = Capistrano::ServerDefinition.new("www.capistrano.test:8080") assert_equal "www.capistrano.test", server.host assert_nil server.user assert_equal 8080, server.port end def test_new_with_encoded_user_and_port_should_extract_user_and_port server = Capistrano::ServerDefinition.new("jamis@www.capistrano.test:8080") assert_equal "www.capistrano.test", server.host assert_equal "jamis", server.user assert_equal 8080, server.port end def test_new_with_user_as_option_should_use_given_user server = Capistrano::ServerDefinition.new("www.capistrano.test", :user => "jamis") assert_equal "www.capistrano.test", server.host assert_equal "jamis", server.user assert_nil server.port end def test_new_with_port_as_option_should_use_given_user server = Capistrano::ServerDefinition.new("www.capistrano.test", :port => 8080) assert_equal "www.capistrano.test", server.host assert_nil server.user assert_equal 8080, server.port end def test_encoded_value_should_override_hash_option server = Capistrano::ServerDefinition.new("jamis@www.capistrano.test:8080", :user => "david", :port => 8081) assert_equal "www.capistrano.test", server.host assert_equal "jamis", server.user assert_equal 8080, server.port assert server.options.empty? end def test_new_with_option_should_dup_option_hash options = {} server = Capistrano::ServerDefinition.new("www.capistrano.test", options) assert_not_equal options.object_id, server.options.object_id end def test_new_with_options_should_keep_options server = Capistrano::ServerDefinition.new("www.capistrano.test", :primary => true) assert_equal true, server.options[:primary] end def test_default_user_should_try_to_guess_username ENV.stubs(:[]).returns(nil) assert_equal "not-specified", Capistrano::ServerDefinition.default_user ENV.stubs(:[]).returns(nil) ENV.stubs(:[]).with("USERNAME").returns("ryan") assert_equal "ryan", Capistrano::ServerDefinition.default_user ENV.stubs(:[]).returns(nil) ENV.stubs(:[]).with("USER").returns("jamis") assert_equal "jamis", Capistrano::ServerDefinition.default_user end def test_comparison_should_match_when_host_user_port_are_same s1 = server("jamis@www.capistrano.test:8080") s2 = server("www.capistrano.test", :user => "jamis", :port => 8080) assert_equal s1, s2 assert_equal s1.hash, s2.hash assert s1.eql?(s2) end def test_servers_should_be_comparable s1 = server("jamis@www.capistrano.test:8080") s2 = server("www.alphabet.test:1234") s3 = server("jamis@www.capistrano.test:8075") s4 = server("billy@www.capistrano.test:8080") assert s2 < s1 assert s3 < s1 assert s4 < s1 assert s2 < s3 assert s2 < s4 assert s3 < s4 end def test_comparison_should_not_match_when_any_of_host_user_port_differ s1 = server("jamis@www.capistrano.test:8080") s2 = server("bob@www.capistrano.test:8080") s3 = server("jamis@www.capistrano.test:8081") s4 = server("jamis@app.capistrano.test:8080") assert_not_equal s1, s2 assert_not_equal s1, s3 assert_not_equal s1, s4 assert_not_equal s2, s3 assert_not_equal s2, s4 assert_not_equal s3, s4 end def test_to_s assert_equal "www.capistrano.test", server("www.capistrano.test").to_s assert_equal "www.capistrano.test", server("www.capistrano.test:22").to_s assert_equal "www.capistrano.test:1234", server("www.capistrano.test:1234").to_s assert_equal "jamis@www.capistrano.test", server("jamis@www.capistrano.test").to_s assert_equal "jamis@www.capistrano.test:1234", server("jamis@www.capistrano.test:1234").to_s end end capistrano-2.12.0/test/fixtures/0000755000004100000410000000000011746743503016631 5ustar www-datawww-datacapistrano-2.12.0/test/fixtures/custom.rb0000644000004100000410000000012711746743503020470 0ustar www-datawww-dataConfigurationLoadingTest::MockConfig.instance(:must_exist).load do ping! :custom end capistrano-2.12.0/test/fixtures/config.rb0000644000004100000410000000022611746743503020423 0ustar www-datawww-dataset :application, "foo" set :repository, "1/2/#{application}" set :gateway, "#{__FILE__}.example.com" role :web, "www.example.com", :primary => true capistrano-2.12.0/test/fixtures/cli_integration.rb0000644000004100000410000000014711746743503022332 0ustar www-datawww-datarole :test, "www.capistrano.test" task :testing, :roles => :test do set :testing_occurred, true end capistrano-2.12.0/test/extensions_test.rb0000644000004100000410000000465411746743503020554 0ustar www-datawww-datarequire "utils" require 'capistrano' class ExtensionsTest < Test::Unit::TestCase module CustomExtension def do_something(command) run(command) end end def setup @config = Capistrano::Configuration.new end def teardown Capistrano::EXTENSIONS.keys.each { |e| Capistrano.remove_plugin(e) } end def test_register_plugin_should_add_instance_method_on_configuration_and_return_true assert !@config.respond_to?(:custom_stuff) assert Capistrano.plugin(:custom_stuff, CustomExtension) assert @config.respond_to?(:custom_stuff) end def test_register_plugin_that_already_exists_should_return_false assert Capistrano.plugin(:custom_stuff, CustomExtension) assert !Capistrano.plugin(:custom_stuff, CustomExtension) end def test_register_plugin_with_public_method_name_should_fail method = Capistrano::Configuration.public_instance_methods.first assert_not_nil method, "need a public instance method for testing" assert_raises(Capistrano::Error) { Capistrano.plugin(method, CustomExtension) } end def test_register_plugin_with_protected_method_name_should_fail method = Capistrano::Configuration.protected_instance_methods.first assert_not_nil method, "need a protected instance method for testing" assert_raises(Capistrano::Error) { Capistrano.plugin(method, CustomExtension) } end def test_register_plugin_with_private_method_name_should_fail method = Capistrano::Configuration.private_instance_methods.first assert_not_nil method, "need a private instance method for testing" assert_raises(Capistrano::Error) { Capistrano.plugin(method, CustomExtension) } end def test_unregister_plugin_that_does_not_exist_should_return_false assert !Capistrano.remove_plugin(:custom_stuff) end def test_unregister_plugin_should_remove_method_and_return_true assert Capistrano.plugin(:custom_stuff, CustomExtension) assert @config.respond_to?(:custom_stuff) assert Capistrano.remove_plugin(:custom_stuff) assert !@config.respond_to?(:custom_stuff) end def test_registered_plugin_proxy_should_return_proxy_object Capistrano.plugin(:custom_stuff, CustomExtension) assert_instance_of Capistrano::ExtensionProxy, @config.custom_stuff end def test_proxy_object_should_delegate_to_configuration Capistrano.plugin(:custom_stuff, CustomExtension) @config.expects(:run).with("hello") @config.custom_stuff.do_something("hello") end endcapistrano-2.12.0/Rakefile0000644000004100000410000000034611746743503015451 0ustar www-datawww-datarequire 'bundler' Bundler::GemHelper.install_tasks require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end task :default => :test capistrano-2.12.0/README.mdown0000644000004100000410000000713111746743503016006 0ustar www-datawww-data## Capistrano [![Build Status](https://secure.travis-ci.org/capistrano/capistrano.png)](http://travis-ci.org/capistrano/capistrano) Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH. It uses a simple DSL (borrowed in part from [Rake](http://rake.rubyforge.org/)) that allows you to define _tasks_, which may be applied to machines in certain roles. It also supports tunneling connections via some gateway machine to allow operations to be performed behind VPN's and firewalls. Capistrano was originally designed to simplify and automate deployment of web applications to distributed environments, and originally came bundled with a set of tasks designed for deploying Rails applications. ## Documentation * [http://github.com/capistrano/capistrano/wiki/Documentation-v2.x](http://github.com/capistrano/capistrano/wiki/Documentation-v2.x) ## DEPENDENCIES * [Net::SSH](http://net-ssh.rubyforge.org) * [Net::SFTP](http://net-ssh.rubyforge.org) * [Net::SCP](http://net-ssh.rubyforge.org) * [Net::SSH::Gateway](http://net-ssh.rubyforge.org) * [HighLine](http://highline.rubyforge.org) If you want to run the tests, you'll also need to install the dependencies with Bundler, see the `Gemfile` within . ## ASSUMPTIONS Capistrano is "opinionated software", which means it has very firm ideas about how things ought to be done, and tries to force those ideas on you. Some of the assumptions behind these opinions are: * You are using SSH to access the remote servers. * You either have the same password to all target machines, or you have public keys in place to allow passwordless access to them. Do not expect these assumptions to change. ## USAGE In general, you'll use Capistrano as follows: * Create a recipe file ("capfile" or "Capfile"). * Use the `cap` script to execute your recipe. Use the `cap` script as follows: cap sometask By default, the script will look for a file called one of `capfile` or `Capfile`. The `someaction` text indicates which task to execute. You can do "cap -h" to see all the available options and "cap -T" to see all the available tasks. ## CONTRIBUTING: * Fork Capistrano * Create a topic branch - `git checkout -b my_branch` * Rebase your branch so that all your changes are reflected in one commit * Push to your branch - `git push origin my_branch` * Create a Pull Request from your branch, include as much documentation as you can in the commit message/pull request, following these [guidelines on writing a good commit message](http://spheredev.org/wiki/Git_for_the_lazy#Writing_good_commit_messages) * That's it! ## LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. capistrano-2.12.0/capistrano.gemspec0000644000004100000410000000377211746743503017522 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "capistrano/version" Gem::Specification.new do |s| s.name = "capistrano" s.version = Capistrano::Version.to_s s.platform = Gem::Platform::RUBY s.authors = ["Jamis Buck", "Lee Hambley"] s.email = ["jamis@jamisbuck.org", "lee.hambley@gmail.com"] s.homepage = "http://github.com/capistrano/capistrano" s.summary = %q{Capistrano - Welcome to easy deployment with Ruby over SSH} s.description = %q{Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.} s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.extra_rdoc_files = [ "README.mdown" ] if s.respond_to? :specification_version then s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 2.0.14"]) s.add_runtime_dependency(%q, [">= 2.0.0"]) s.add_runtime_dependency(%q, [">= 1.0.0"]) s.add_runtime_dependency(%q, [">= 1.1.0"]) s.add_development_dependency(%q, [">= 0"]) else s.add_dependency(%q, [">= 2.0.14"]) s.add_dependency(%q, [">= 2.0.0"]) s.add_dependency(%q, [">= 1.0.0"]) s.add_dependency(%q, [">= 1.1.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) end else s.add_dependency(%q, [">= 2.0.14"]) s.add_dependency(%q, [">= 2.0.0"]) s.add_dependency(%q, [">= 1.0.0"]) s.add_dependency(%q, [">= 1.1.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) end end capistrano-2.12.0/bin/0000755000004100000410000000000011746743503014551 5ustar www-datawww-datacapistrano-2.12.0/bin/cap0000755000004100000410000000010611746743503015237 0ustar www-datawww-data#!/usr/bin/env ruby require 'capistrano/cli' Capistrano::CLI.execute capistrano-2.12.0/bin/capify0000755000004100000410000000536511746743503015763 0ustar www-datawww-data#!/usr/bin/env ruby require 'optparse' require 'fileutils' OptionParser.new do |opts| opts.banner = "Usage: #{File.basename($0)} [path]" opts.on("-h", "--help", "Displays this help info") do puts opts exit 0 end begin opts.parse!(ARGV) rescue OptionParser::ParseError => e warn e.message puts opts exit 1 end end if ARGV.empty? abort "Please specify the directory to capify, e.g. `#{File.basename($0)} .'" elsif !File.exists?(ARGV.first) abort "`#{ARGV.first}' does not exist." elsif !File.directory?(ARGV.first) abort "`#{ARGV.first}' is not a directory." elsif ARGV.length > 1 abort "Too many arguments; please specify only the directory to capify." end def unindent(string) indentation = string[/\A\s*/] string.strip.gsub(/^#{indentation}/, "") end files = { "Capfile" => unindent(<<-FILE), load 'deploy' # Uncomment if you are using Rails' asset pipeline # load 'deploy/assets' Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) } load 'config/deploy' # remove this line to skip loading any of the default tasks FILE "config/deploy.rb" => 'set :application, "set your application name here" set :repository, "set your repository location here" set :scm, :subversion # Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none` role :web, "your web-server here" # Your HTTP server, Apache/etc role :app, "your app-server here" # This may be the same as your `Web` server role :db, "your primary db-server here", :primary => true # This is where Rails migrations will run role :db, "your slave db-server here" # if you want to clean up old releases on each deploy uncomment this: # after "deploy:restart", "deploy:cleanup" # if you\'re still using the script/reaper helper you will need # these http://github.com/rails/irs_process_scripts # If you are using Passenger mod_rails uncomment this: # namespace :deploy do # task :start do ; end # task :stop do ; end # task :restart, :roles => :app, :except => { :no_release => true } do # run "#{try_sudo} touch #{File.join(current_path,\'tmp\',\'restart.txt\')}" # end # end'} base = ARGV.shift files.each do |file, content| file = File.join(base, file) if File.exists?(file) warn "[skip] '#{file}' already exists" elsif File.exists?(file.downcase) warn "[skip] '#{file.downcase}' exists, which could conflict with `#{file}'" else unless File.exists?(File.dirname(file)) puts "[add] making directory '#{File.dirname(file)}'" FileUtils.mkdir(File.dirname(file)) end puts "[add] writing '#{file}'" File.open(file, "w") { |f| f.write(content) } end end puts "[done] capified!" capistrano-2.12.0/metadata.yml0000644000004100000410000002031711746743503016307 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: capistrano version: !ruby/object:Gem::Version version: 2.12.0 prerelease: platform: ruby authors: - Jamis Buck - Lee Hambley autorequire: bindir: bin cert_chain: [] date: 2012-04-13 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: highline requirement: &70100200770260 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: *70100200770260 - !ruby/object:Gem::Dependency name: net-ssh requirement: &70100200769760 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.0.14 type: :runtime prerelease: false version_requirements: *70100200769760 - !ruby/object:Gem::Dependency name: net-sftp requirement: &70100200769280 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.0.0 type: :runtime prerelease: false version_requirements: *70100200769280 - !ruby/object:Gem::Dependency name: net-scp requirement: &70100200768800 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.0.0 type: :runtime prerelease: false version_requirements: *70100200768800 - !ruby/object:Gem::Dependency name: net-ssh-gateway requirement: &70100200768320 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.1.0 type: :runtime prerelease: false version_requirements: *70100200768320 - !ruby/object:Gem::Dependency name: mocha requirement: &70100200767840 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70100200767840 description: Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH. email: - jamis@jamisbuck.org - lee.hambley@gmail.com executables: - cap - capify extensions: [] extra_rdoc_files: - README.mdown files: - .gitignore - .travis.yml - CHANGELOG - Gemfile - README.mdown - Rakefile - bin/cap - bin/capify - capistrano.gemspec - lib/capistrano.rb - lib/capistrano/callback.rb - lib/capistrano/cli.rb - lib/capistrano/cli/execute.rb - lib/capistrano/cli/help.rb - lib/capistrano/cli/help.txt - lib/capistrano/cli/options.rb - lib/capistrano/cli/ui.rb - lib/capistrano/command.rb - lib/capistrano/configuration.rb - lib/capistrano/configuration/actions/file_transfer.rb - lib/capistrano/configuration/actions/inspect.rb - lib/capistrano/configuration/actions/invocation.rb - lib/capistrano/configuration/alias_task.rb - lib/capistrano/configuration/callbacks.rb - lib/capistrano/configuration/connections.rb - lib/capistrano/configuration/execution.rb - lib/capistrano/configuration/loading.rb - lib/capistrano/configuration/namespaces.rb - lib/capistrano/configuration/roles.rb - lib/capistrano/configuration/servers.rb - lib/capistrano/configuration/variables.rb - lib/capistrano/errors.rb - lib/capistrano/ext/multistage.rb - lib/capistrano/ext/string.rb - lib/capistrano/extensions.rb - lib/capistrano/fix_rake_deprecated_dsl.rb - lib/capistrano/logger.rb - lib/capistrano/processable.rb - lib/capistrano/recipes/compat.rb - lib/capistrano/recipes/deploy.rb - lib/capistrano/recipes/deploy/assets.rb - lib/capistrano/recipes/deploy/dependencies.rb - lib/capistrano/recipes/deploy/local_dependency.rb - lib/capistrano/recipes/deploy/remote_dependency.rb - lib/capistrano/recipes/deploy/scm.rb - lib/capistrano/recipes/deploy/scm/accurev.rb - lib/capistrano/recipes/deploy/scm/base.rb - lib/capistrano/recipes/deploy/scm/bzr.rb - lib/capistrano/recipes/deploy/scm/cvs.rb - lib/capistrano/recipes/deploy/scm/darcs.rb - lib/capistrano/recipes/deploy/scm/git.rb - lib/capistrano/recipes/deploy/scm/mercurial.rb - lib/capistrano/recipes/deploy/scm/none.rb - lib/capistrano/recipes/deploy/scm/perforce.rb - lib/capistrano/recipes/deploy/scm/subversion.rb - lib/capistrano/recipes/deploy/strategy.rb - lib/capistrano/recipes/deploy/strategy/base.rb - lib/capistrano/recipes/deploy/strategy/checkout.rb - lib/capistrano/recipes/deploy/strategy/copy.rb - lib/capistrano/recipes/deploy/strategy/export.rb - lib/capistrano/recipes/deploy/strategy/remote.rb - lib/capistrano/recipes/deploy/strategy/remote_cache.rb - lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb - lib/capistrano/recipes/deploy/templates/maintenance.rhtml - lib/capistrano/recipes/standard.rb - lib/capistrano/recipes/templates/maintenance.rhtml - lib/capistrano/role.rb - lib/capistrano/server_definition.rb - lib/capistrano/shell.rb - lib/capistrano/ssh.rb - lib/capistrano/task_definition.rb - lib/capistrano/transfer.rb - lib/capistrano/version.rb - test/cli/execute_test.rb - test/cli/help_test.rb - test/cli/options_test.rb - test/cli/ui_test.rb - test/cli_test.rb - test/command_test.rb - test/configuration/actions/file_transfer_test.rb - test/configuration/actions/inspect_test.rb - test/configuration/actions/invocation_test.rb - test/configuration/alias_task_test.rb - test/configuration/callbacks_test.rb - test/configuration/connections_test.rb - test/configuration/execution_test.rb - test/configuration/loading_test.rb - test/configuration/namespace_dsl_test.rb - test/configuration/roles_test.rb - test/configuration/servers_test.rb - test/configuration/variables_test.rb - test/configuration_test.rb - test/deploy/local_dependency_test.rb - test/deploy/remote_dependency_test.rb - test/deploy/scm/accurev_test.rb - test/deploy/scm/base_test.rb - test/deploy/scm/bzr_test.rb - test/deploy/scm/darcs_test.rb - test/deploy/scm/git_test.rb - test/deploy/scm/mercurial_test.rb - test/deploy/scm/none_test.rb - test/deploy/scm/perforce_test.rb - test/deploy/scm/subversion_test.rb - test/deploy/strategy/copy_test.rb - test/extensions_test.rb - test/fixtures/cli_integration.rb - test/fixtures/config.rb - test/fixtures/custom.rb - test/logger_test.rb - test/recipes_test.rb - test/role_test.rb - test/server_definition_test.rb - test/shell_test.rb - test/ssh_test.rb - test/task_definition_test.rb - test/transfer_test.rb - test/utils.rb homepage: http://github.com/capistrano/capistrano licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.11 signing_key: specification_version: 3 summary: Capistrano - Welcome to easy deployment with Ruby over SSH test_files: - test/cli/execute_test.rb - test/cli/help_test.rb - test/cli/options_test.rb - test/cli/ui_test.rb - test/cli_test.rb - test/command_test.rb - test/configuration/actions/file_transfer_test.rb - test/configuration/actions/inspect_test.rb - test/configuration/actions/invocation_test.rb - test/configuration/alias_task_test.rb - test/configuration/callbacks_test.rb - test/configuration/connections_test.rb - test/configuration/execution_test.rb - test/configuration/loading_test.rb - test/configuration/namespace_dsl_test.rb - test/configuration/roles_test.rb - test/configuration/servers_test.rb - test/configuration/variables_test.rb - test/configuration_test.rb - test/deploy/local_dependency_test.rb - test/deploy/remote_dependency_test.rb - test/deploy/scm/accurev_test.rb - test/deploy/scm/base_test.rb - test/deploy/scm/bzr_test.rb - test/deploy/scm/darcs_test.rb - test/deploy/scm/git_test.rb - test/deploy/scm/mercurial_test.rb - test/deploy/scm/none_test.rb - test/deploy/scm/perforce_test.rb - test/deploy/scm/subversion_test.rb - test/deploy/strategy/copy_test.rb - test/extensions_test.rb - test/fixtures/cli_integration.rb - test/fixtures/config.rb - test/fixtures/custom.rb - test/logger_test.rb - test/recipes_test.rb - test/role_test.rb - test/server_definition_test.rb - test/shell_test.rb - test/ssh_test.rb - test/task_definition_test.rb - test/transfer_test.rb - test/utils.rb capistrano-2.12.0/CHANGELOG0000644000004100000410000012577411746743503015233 0ustar www-datawww-data## 2.12.0 / April 13 2012 This release revserts the very verbose logging introduced in the previous version, it also enables a handful of power-user features which are largely un-documented, but shouldn't be important unless you are looking for them. Undocumented code shouldn't scare you, simply read through deploy.rb in the Gem if you want to know how a new feature works! * Update mapped commands to remove symlink deprecation warning. Despo Pentara (despo) * Add the "rpm" remote dependency. Nick Hoffman (nickhoffman) * Add commented deploy:cleanup task to default recipe. Jean-Philippe Doyle (j15e) * Teach deploy:web:enable to fail gracefully. Lee Marlow (lmarlow) * Ticket 193 alias task show wrong name when it is not overridden. Rafa García (rgo) * Allow configuration of which roles assets are precompiled on. Frederick Cheung (fcheung) * Fix transfer action to honor dry-run flag. Serg Podtynnyi (shtirlic) * Changed single to double quotes for Windows, fixes a Windows bug in the HG module. Matthew J Morrison (mattjmorrison) * Add UnsharedRemoteCache (copied from eycap gem). Ben Symonds (bensymonds) As ever, a sincere thanks to all contributors, and do not hesitate to contact me if this release causes problems for you. ## 2.11.0 / Febuary 20 2012 This release replaces and fixes a broken 2.10.0 release (see below for information) This release includes all fixes as documented for 2.10.0, plus additional code cleanup (SHA: 3eecac2), as well as changing the public API from `deploy:symlink`, to `deploy:create_symlink` * Remove a testing dependency on `ruby-debug` (Lee Hambley, reported by Serg Podtynnyi) * Formerly deprecate `deploy:symlink`, this move was prompted by the Rake namespace fiasco of their 0.9.0 release, and is replaced by `deploy:create_symlink`. If you are looking for a place to hook asset related tasks, please consider `deploy:finalize_update`. Replaced by `deploy:create_symlink`, `deploy:symlink` now raises a deprecation warning, and defers to `deploy:symlink`. (Lee Hambley) * Update the 2.10.0 changelog. (Lee Hambley, reported by Jérémy Lecour) ## 2.10.0 / Febuary 19 2012 If you are reading this after Febuary 20th 2012, do not be surprised when you cannot find 2.10.0 on Rubygems.org, it has been removed because of a breaking API change. It is replaced logically enough by 2.11.0 where the API is still changed, but now issues a warning and falls back to the expected behaviour. The CHANGELOG for this release was updated retrospectively, I'm sorry I missed that when releasing the gem, 2.10.0 apparently not my finest hour as a maintainer. Ths fixes in this release include * Include sample NGinx config for `deploy:web:disable`(added by Roger Ertesvåg) * Fix gemspec time format warning (reported by Tony Arcieri, fixed by building the Gem against Ruby-1.9.3) * Finally removed deprecated `before_` and `after_` tasks. (Lee Hambley) * Rake 0.9.x compatibility (reported by James Miller, fixed by Lee Hambley) * More detailed logging output (fixed by Huang Liang) * Includes multistage, without `capistrano-ext`. `require 'capistrano/ext/multistage'` (fixed by Lee Hambley) ## 2.9.0 / September 24 2011 A vairly heavy release, including some new features which most of you won't need, but power users have contributed, this also marks the beginning of the end of the 2.x series, more information will follow in due course, but with the proliferation of Bundler, and better ways to do deployment, we will be introducing heavier changes to Capistrano to keep the tool current. **Please note, following some reported problems with the asset pipeline code being not found, remember Capistrano needs to be in your Gemfile, and as such needs to be run with Bundler, otherwise you risk loading a system-wide version of Capistrano who's behaviour might be different from that specified in your Gemfile. This is also good practice because much of the deploy logic resides in the Gem, and you wouldn't want that to change without your knowledge. Rails applications include Cap in the Gemfile anyway, you should follow this convention.** * find_servers() will no longer raise if there are no servers, this behaviour can be modified by way of the `:on_no_matching_servers` option. Thanks to `@ppgengler`. * Short Git SHA1 fragments are now supported in commands such as `cap deploy -s revision=d7e99f` thanks to `@ndbroadbent`. * One can now specify individual SCM commands by setting `:scm_arguments_`. Thanks to `@alextk`. * Travis CI build now passes thanks to @andrew, build tested against MRI `1.8.7`. `1.9.2` and `REE`. * Support for Perforce labels, I don't know much about this, but I believe it's much like deploying a Git tag, thanks to `@ak47`. * Git SCM now correctly adheres to the `:scm_verbose` setting. Thanks `@dubek`. * `set()` can now be used to set a `false` value, previously this was a no-op. Thanks to `@nilbus`. * Support for Git 1.6x submodules, The Git SCM strategy now queries Git on the server-side to ensure it supports the `--recursive` flag, if it doesn't then it will fall back to using the long-hand. Many thanks to all those involved in the discussion surrounding this topic, and to `@nilbus` for a beautifully clean solution which doesn't hold us back. * When using `:cached_copy` with Subversion, use `svn switch` to for more reliable switching of branches/etc. Thanks to `@iGEL` for the patch that we accepted finally, and to `@richmeyers` who also submitted a patch and contributed to the discssion. Other cleanups and minor improvements to the code and tests were committed by yours truly (@leehambley), @maxim, @ak47 and @andrew). ## 2.8.0 / August 3 2011 A short release, after the last. Announcing Rails 3.1 asset pipeline support. The asset pipeline support requires an additiona `load` in your `Capfile`. You can see information pertaining to the pull request, including the inline comments here: https://github.com/capistrano/capistrano/pull/35 Documentation will be available soon in the wiki. * Drop-In Rails 3.1 asset pipeline support. (Chris Griego) ## 2.7.0 / August 3 2011 A fairly substantial release. There are fixes so that current_release works during dry-runs, (although, apparently still not with bundler.) The test-suite was also modified to work with Ruby 1.9.2, except in one case where Ruby 1.9.x calls `to_ary` and `to_a` on mocks, which still makes an error. 1.9.x has always been supported, but due to lack of maintenance on my part the tests didn't ever pass. The `start`, `stop` and `restart` tasks have been reduced to mere hooks into which extensions can define their own functionality. The `readme` was also slightly improved, simply tweaks to express how best to run the test suite. * Ensure dry-run works with `:current_release` variable (Carol Nichols) * Added a new variable `:git_submodules_recursive`, setting the value to false will ensure Git doesn't recursively initialize and checkout submodules. (Konstantin Kudryashov) * Added an additional task option, `:on_no_matching_servers`, setting the value to `:continue` will ensure tasks with no matched servers continue without error, instead of raising `Capistrano::NoMatchingServersError` as was the previous behaviour. (Chris Griego) A huge thanks to all contributors, as always! Remember: @capistranorb on twitter for news. ## 2.6.1 / June 25 2011 A short maintenance release, Some fixes to the verbose flag inside the Git SCM as well as another argument for the (internal) `variable()` command, offering a default. The Git SCM is now verbose by default, but can be disabled by setting `:scm_verbose` to false. There has been an additional method added to string, within the context of the test suite, I'm always sketchy about adding additional methods to core classes, but it's a short term fix until I make the time to patch the test suite not to compare strings literally. The method is `String#compact`, and is implemented simply as `self.gsub(/\s+/, ' ')`. Here's the run-down of changes, and their committers, as always - a huge thank you to the community that continues to drive Capistrano's development. * `deploy:setup` now respects `:group_writable` (Daniel Duvall) * Fixes to `:scm_verbose` for the Git module (defaults to On.) (Matthew Davies) * Will now copy hidden files in the project's root into the release directory (Mark Jaquith) * Now handles closing already-dead connections in a sane way (does not raise an exception) (Will Bryant) * Renamed `Capistrano::VERSION::TINY` to `Capistrano::VERSION::PATCH` (Lee Hambley) * Removed the `VERSION` file (Lee Hambley) ## 2.6.0 / May 3 2011 A rather large release, feature-version bump because of the new multiple-gateways feature as implemented by Ryan Duryea (way to go!) Please also note from this release that if you use Git submodules, the Git-version requirement for the new implementation is now >= 1.5.6, from previously un-documented. (1.5.6 is new-enough that I think this is acceptable) * Upgrade Net::SSH-gateway dependency to 1.1 (fixes a thread-deadlocking bug on MRI 1.9) * Respect "dry-run" on transfer methods (Florian Frank) * Add support for multiple gateways: (Ryan Duryea) set :gateway, { 'gate1.example.com' => 'server1.example.com', [ 'gate2.example.com', 'gate3.example.com' ] => [ 'server5.example.com', 'server6.example.com' ] } * Properly support nested Git submodules, moves Git requirement to >= 1.5.6 [if you rely upon submodules] (Ken Miller) * Fetch tags into the remote cache, allows deploying a tag when using Git, with the remote_cache strategy (Florian Frank) * Various fixes to path handling bugs in the copt strategy. (Philippe Rathé) ## 2.5.21 / April 6 2011 * Fixed to follow best-practice guidelines from Bundler (Ben Langfeld) * No longer force a gemset for Capistrano development. (Ben Langfeld) ## 2.5.20 / March 16 2011 * `deploy:migrations` will now always operate on the latest_release, not current_release (Mike Vincent) * Adds a check for the presence of `rsync` when using the copy strategy with `rsync`. (Chris Griego) * Do not try to look up the `:release_path` on servers which are defined `:no_release` (Chris Griego) * Tiny patch to the `CVS` SCM code to be Ruby 1.9 compatible (Martin Carpenter) * Changed the default `Git` submodule behaviour to use `--recursive`, Lighthouse Issue #176. (Lee Hambley) * `:public_children` can now be `set()`, the default is unchanged, thanks (Chris Griego) * Fixing the load path in the default `Capfile` to search vendored/unpacked Gems. Lighthouse Issue #174 (Mari Carmen/Rafael García) * Adds a `maintenance_basename` variable (default value is `maintenance`) to allow you to set the maintenance page name (Celestino Gomes) * Spelling fixes in inline-documentation (Tom Copeland) * Make `zip` and `tar` handle symlinks the same way (zip follows symlinks by default, tar needs the option `-h`) (Ross Cooperman) ## 2.5.19 / June 21, 2010 * Small bug fixes, no improvements for people who weren't experiencing problems anyway. ## 2.5.18 / March 14, 2010 Small fix for rolling back if a shell scripts exits non-zero; enabled a rollback if git (or other) externals fail during the deploy. * #151 check return code status of system command to create local copy and rollback if not 0 (David King) ## 2.5.17 / February 27, 2010 Various small bug fixes. ## 2.5.16 / February 14, 2010 Fixed a small regression in 2.5.15 ## 2.5.15 / 14 February 2010 Fixes a feature request not to overwrite roles when using the ROLES environmental variable. * #126 - The option to not overwriting the roles which are defined in the task definition. * Removed the `upgrade` file as it has been a couple of years since 1.x was in the wild. * Slight internal re-factor of the way we calculate the `version` ## 2.5.14 / 18 January 2010 Fixes a low-value bug, thanks to Chris G for the well submitted patch: * #139 - Improves consistency of variable lookup, scm variables with a local_ prefix will be honoured with priority locally (Chris Griego) ## 2.5.13 / 6 January 2010 * Small maintenance release: * #118 - Modified CLI test to not load user or system configuration file (Emily Price) * #88 - Re-fixed a problem here, massive apologies to all concerned. (Hangover from 2.5.12) ## 2.5.12 / 5 January 2010 * Tweak the directory version listing (caused a lot of problems, please upgrade immediately) ## 2.5.11 / December 2009 * Deprecations and other small changes ## 2.5.10 / 3 November 2009 * Fixes Darcs remote repository problem when using the copy strategy [Alex `regularfry` Young] * Documentation improvements for embedding Capistrano [Lee Hambley] * Fixes ticket #95 -formally deprecating the before_something and after_something methods [Lee Hambley] ## 2.5.9 / 1 August 2009 * Adds support for customizing which `tar` command to use. [Jeremy Wells] * Fixes a couple of documentation problems, typos and worse. [Lee Hambley] * #105 - Add skip_hostfilter option to find_servers() [Eric] * #103 - Using non-master branch fails with Ruby 1.9 [Suraj Kurapati] * #96 - Tweak for 1.9 Compatibility * #79 - Capistrano hangs on shell command for many computers * #77 - Copy command doesn't work on Solaris due to tar/gtar * #76 - Invalid Subversion URL * Improved web:disable task, now suggests a .htaccess block to use suggested by Rafael García * Includes more logger options (can now select stdout, stderr of a file) [Rafael García] ## 2.5.8 / July 2009 * Fixes a problem in 2.5.7 where deploy:finalize_update had been badly merged. ## 2.5.6 & 2.5.7 / July 2009 * 2.5.7 masks a broken 2.5.6 release that was accidentally mirrored via Rubyforge. * Clean the cached git repository [Graeme Mathieson] * Fixes perforce issues reported at http://bit.ly/wt0es [Scott Johnson] * Improved back-tick handling code in relation to the above. * Fixes a Git issue when submodules update upstream. (via mailing list) [sneakin] * Capify now creates the config directory in directories without one. ## 2.5.5 / 24 Feb 2009 * Make sure role(:foo) actually declares an (empty) role for :foo, even without server arguments [Jamis Buck] ## 2.5.4 / 4 Feb 2009 * When using rsync with the remote_cache strategy include -t switch to preserve file times [Kevin McCarthy] * Bump Net::SSH dependency to version 2.0.10 [Jamis Buck] * Use 'user' from .ssh/config appropriately [Jamis Buck] * Allow respond_to?() method to accept optional second parameter (include_priv) [Matthias Marschall] * Make sure sudo prompts are retried correctly even if "try again" and the prompt appear in the same text chunk from the server [Jamis Buck] * Add supported environment variables to -H output [François Beausoleil] ## 2.5.3 / December 6, 2008 * Make previous_release return nil if there is no previous release [Mathias Meyer] * Play nice with rubies that don't inspect terminals well (ie. JRuby) by defaulting screen columns to 80 [Bob McWhirter] * Rollback of deploy:symlink would explode if there was no previous revision to rollback to [Jamis Buck] * Fix bug in transfer.rb that caused get/put/upload/download to ignore blocks passed to them [arika] * Fix issue with git SCM that caused "Unable to resolve revision" errors when there was trailing whitespace in git's output [Mark Zuneska, Daniel Berlinger and Evan Closson] ## 2.5.2 / November 13, 2008 * Fix issue with git SCM that caused "Unable to resolve revision for 'HEAD'" errors on deploy [Jamis Buck] ## 2.5.1 / November 7, 2008 * Add -t (--tools) switch for better task lists for external tools [Jamis Buck] * Make the RemoteDependency#try method use invoke_command instead of run, for sudo-ability [Matthias Marschall] * Make locally executed commands in Windows more Windows-friendly [esad@esse.at] * Added :scm_arguments variable for custom SCM arguments (subversion-only, currently) [David Abdemoulaie] * Don't emit -p for sudo when :sudo_prompt is blank [Matthias Marschall] * Copy symlinks when using rsync [Paul Paradise] * Make sure git query-revision matches on exact branch name [grant@nightriot.com] * Use -T to filter listed tasks by a pattern [Mathias Meyer, Geoffrey Grosenbach] * Expose the #scm method on SCM::Base for building custom scm commands [Mathias Meyer] * Start logging some locally executed commands [springyweb] * Added HOSTFILTER environment variable for constraining tasks so they run only on hosts matching the given list of servers [Walter Smith] * Make sure the glob matching for copy excludes does not delete parent directories [Fabio Akita] * Ruby 1.9 compatibility [Jamis Buck] ## 2.5.0 / August 28, 2008 * Allow :gateway to be set to an array, in which case a chain of tunnels is created [Kerry Buckley] * Allow HOSTS spec to override even non-existent roles [Mike Bailey] * Sort releases via "ls -xt" instead of "ls -x" to allow for custom release names [Yan Pritzker] * Convert arguments to -s and -S into integers, booleans, etc. based on whether the arguments appear to be those types [Jamis Buck] * Add descriptions of -n and -d to the verbose help text [Jamis Buck] * Make rollbacks work with processes that need the current directory to be valid in order to restart properly (e.g. mongrel_rails) [Jamis Buck] * Rename deploy:rollback_code to deploy:rollback:code [Jamis Buck] * Added parallel() helper for executing multiple different commands in parallel [Jamis Buck] * Make sure a task only uses the last on_rollback block, once, on rollback [Jamis Buck] * Add :shared_children variable to customize which subdirectories are created by deploy:setup [Jonathan Share] * Allow filename globbing in copy_exclude setting for the copy strategy [Jonathan Share] * Allow remote_cache strategy to use copy_exclude settings (requires rsync) [Lewis Mackenzie] * Make None SCM module work in Windows [Carlos Kozuszko] * Recognize mingw as a Windows platform [Carlos Kozuszko] * Fixed failing tests in Windows [Carlos Kozuszko] * Made :scm_auth_cache control whether password option is emitted in subversion module [Brendan Schwartz] * Fixed timestamp bug in CVS module [Jørgen Fjeld] * Added -n/--dry-run switch, to display but not execute remote tasks [Paul Gross] ## 2.4.3 / June 28, 2008 * Fix gem dependencies so gem actually understands them [Jamis Buck] ## 2.4.2 / June 27, 2008 * Specify gem dependencies in rakefile [Jamis Buck] ## 2.4.1 / June 27, 2008 * Use Echoe to manage the Rakefile [Jamis Buck] * Let Net::SSH manage the default SSH port selection [Ben Lavender] * Changed capture() helper to not raise an exception on error, but to warn instead [Jeff Forcier] ## 2.4.0 / June 13, 2008 * Added :normalize_asset_timestamps option to deployment, defaulting to true, which allows asset timestamping to be disabled [John Trupiano] ## 2.4.0 Preview Release #1 (2.3.101) / June 5, 2008 * Only make deploy:start, deploy:stop, and deploy:restart try sudo as :runner. The other sudo-enabled tasks (deploy:setup, deploy:cleanup, etc.) will now use the :admin_runner user (which by default is unset). [Jamis Buck] * Make sure triggers defined as a block inherit the scope of the task they are attached to, instead of the task they were called from [Jamis Buck] * Make deploy:upload use the upload() helper for more efficient directory processing [Jamis Buck] * Make deploy:upload accept globs [Mark Imbriaco] * Make sure the host is reported with the output from scm_run [Jamis Buck] * Make git SCM honor the :scm_verbose option [Jamis Buck] * Don't follow symlinks when using :copy_cache [Jamis Buck] * If :mode is given to upload() helper, do a chmod after to set the mode [Jamis Buck] * Fix load_from_file method for windows users [Neil Wilson] * Display a deprecation error if a remote git branch is specified [Tim Harper] * Fix deployment recipes to use the updated sudo helper [Jamis Buck] * Enhance the sudo helper so it can be used to return the command, instead of executing it [Jamis Buck] * Revert "make sudo helper play nicely with complex command chains", since it broke stuff [Jamis Buck] * Make set(:default_shell, false) work for not using a shell on a per-command basis [Ryan McGeary] * Improved test coverage [Ryan McGeary] * Fixed "coverage" take task [Ryan McGeary] * Use upload() instead of put() with the copy strategy [Jamis Buck] * Revert the "git fetch --tags" change, since it didn't work as expected [Jamis Buck] * Fix deploy:pending when using git SCM [Ryan McGeary] * Make sure deploy:check works with :none scm (which has no default command) [Jamis Buck] * Add debug switch for enabling conditional execution of commands [Mark Imbriaco] ## 2.3.0 / May 2, 2008 * Make deploy:setup obey the :use_sudo and :runner directives, and generalize the :use_sudo and :runner options into a try_sudo() helper method [Jamis Buck] * Make sudo helper play nicely with complex command chains [Jamis Buck] * Expand file-transfer options with new upload() and download() helpers. [Jamis Buck] * Allow SCP transfers in addition to SFTP. [Jamis Buck] * Use Net::SSH v2 and Net::SSH::Gateway. [Jamis Buck] * Added #export method for git SCM [Phillip Goldenburg] * For query_revision, git SCM used git-rev-parse on the repo hosting the Capfile, which may NOT be the same tree as the actual source reposistory. Use git-ls-remote instead to resolve the revision for checkout. [Robin H. Johnson] * Allow :ssh_options hash to be specified per server [Jesse Newland] * Added support for depend :remote, :file to test for existence of a specific file [Andrew Carter] * Ensure that the default run options are mixed into the command options when executing a command from the cap shell [Ken Collins] * Added :none SCM module for deploying a specific directory's contents [Jamis Buck] * Improved "copy" strategy supports local caching and pattern exclusion (via :copy_cache and :copy_exclude variables) [Jamis Buck] ## 2.2.0 / February 27, 2008 * Fix git submodule support to init on sync [halorgium] * Add alternative server-centric role definition method [James Duncan Davidson] * Add support for password prompts from the Mercurial SCM [ches] * Add support for :max_hosts option in task definition or run() [Rob Holland ] * Distributed git support for better operability with remote_cache strategy [voidlock] * Use a default line length in help text if line length is otherwise too small [Jamis Buck] * Fix incorrect reference to the 'setup' task in task documentation [rajeshduggal] * Don't try to kill the spawner process on deploy:stop if no spawner process exists [Jamis Buck] * Dynamic roles (e.g. role(:app) { "host.name" }) [dmasover] * Implement Bzr#next_revision so that pending changes can be reported correctly [casret] * Use a proper export command for bzr SCM [drudru] * Use checkout instead of merge for git SCM [nuttycom] * Fix typo in Subversion SCM module, encountered when an update fails [kemiller] * Fix documentation typo in upload.rb [evolving_jerk] * Added test case to show that the :scm_command is honored by the git SCM module [grempe] * Fail gracefully when double-colons are used to delimit namespaces [richie] * Add support for :git_enable_submodules variable, to enable submodules with the git SCM [halorgium] * If subversion asks for a password, prompt as a last resort [Jamis Buck] * Use checkout --lightweight for bzr checkout, instead of branch [michiels] * Make sure bzr SCM works when revision is head (or unspecified) [michiels] * Support p4sync_flags and p4client_root variables for Perforce [gseidman] * Prepare for Net::SSH v2 by making sure Capistrano only tries to load Net::SSH versions less than 1.99.0 [Jamis Buck] ## 2.1.0 / October 14, 2007 * Default to 0664 instead of 0660 on upload [Jamis Buck] * Fix deploy:pending to query SCM for the subsequent revision so that it does not include the last deployed change [Jamis Buck] * Prefer 'Last Changed Rev' over 'Revision' when querying latest revision via Subversion [Jamis Buck] * Explicitly require 'stringio' in copy_test [mislav] * When Subversion#query_revision fails, give a more sane error [Jamis Buck] * Don't run the upgrade:revisions task on non-release servers [Jamis Buck] * Fix cap shell to properly recognize sudo prompt [Mark Imbriaco, barnaby, Jamis Buck] * Git SCM module [Garry Dolley, Geoffrey Grosenbach, Scott Chacon] * Use the --password switch for subversion by default, but add :scm_prefer_prompt variable (defaults to false) [Jamis Buck] ## 2.0.100 (2.1 Preview 1) / September 1, 2007 * capify-generated Capfile will autoload all recipes from vendor/plugins/*/recipes/*.rb [Graeme Mathieson] * Use sudo -p switch to set sudo password prompt to something predictable [Mike Bailey] * Allow independent configurations to require the same recipe file [Jamis Buck] * Set :shell to false to run a command without wrapping it in "sh -c" [Jamis Buck] * Don't request a pty by default [Jamis Buck] * Add a "match" remote dependency method [Adam Greene] * Allow auth-caching of subversion credentials to be enabled via :scm_auth_cache [tsmith] * Don't let a task trigger itself when used as the source for an "on" hook [Jamis Buck] * Avoid using the --password switch with subversion for security purposes [sentinel] * Add version_dir, current_dir, and shared_dir variables for naming the directories used in deployment [drinkingbird] * Use Windows-safe binary reads for reading file contents [Ladislav Martincik] * Add Accurev SCM support [Doug Barth] * Use the :runner variable to determine who to sudo as for deploy:restart [Graham Ashton] * Add Namespaces#top to always return a reference to the topmost namespace [Jamis Buck] * Change the "-h" output so that it does not say that "-q" is the default [Jamis Buck] ## 2.0.0 / July 21, 2007 * Make the "no matching servers" error more sane [halorgium] * Make sure the invoke task gives a sane error when the COMMAND value is omitted [halorgium] * Make sure variables are conditionally set in the deploy recipes, so as not to clobber values set elsewhere [Jamis Buck] * Fix "input stream is empty" errors from HighLine on prompt [Jamis Buck] * Added "synchronous_connect" setting to try and work around SFTP hangs for certain users [Jamis Buck] * Auto-require the SSH shell service, to avoid race conditions [Jamis Buck] * Add a millisecond sleep in upload to reduce CPU impact [Jamis Buck] * Allow the logger to be set via Configuration#logger= [Jamis Buck] * Allow $CAPISTRANO:HOST$ to be used in filenames to the put command [Jamis Buck] * Allow execute_on_servers to be called without a current task again [Jamis Buck] * Put $stdout in sync mode, so that Net::SSH prompts are displayed [Jamis Buck] * Make sure deploy:check aborts if it fails [Jamis Buck] * Spelling corrections in docs [Tim Carey-Smith, Giles Bowkett] ## 1.99.3 (2.0 Preview 4) / June 28, 2007 * Don't break task descriptions on a period that appears in the middle of a sentence [Jamis Buck] * Added support for :on_error => :continue in task definitions, allowing tasks to effectively ignore connection and execution errors that occur as they run [Rob Holland] * Use correct parameters for Logger constructor in the SCM and Strategy base initializers [Jamis Buck] * Set LC_ALL=C before querying the revision, to make sure the output is in a predictable locale and can be parsed predictably [via Leandro Nunes dos Santos] * Add :copy_remote_dir variable for the :copy strategy, to indicate where the archive should be copied to on the remote servers [Jamis Buck] * Make the awk use in the dependencies code work with POSIX awk [mcornick] * Make variable accesses thread safe [via Adrian Danieli] * Make user input for yes/no prompts work correctly in the Mercurial module [Matthew Elder] * Use single quotes to escape semicolon in find command, instead of a backslash [via michael.italia@gmail.com] * Better quoting of reserved characters in commands [Jamis Buck] * Make sure Net::SSH versions prior to 1.1.0 still work [Jamis Buck] * Allow the :hosts and :roles keys to accept lambdas, which will be evaluated lazily to allow runtime selection of hosts and roles in tasks [Jamis Buck] * Use `which' to test whether a command exists in the remote path, instead of `test -p' [Jamis Buck] * Make sure the connection factory is established synchronously, to avoid multiple gateway instances being spawned [Jamis Buck] * Make sure symlink and finalize_update tasks reference the most recent release when called by themselves [Jamis Buck] ## 1.99.2 (2.0 Preview 3) / June 15, 2007 * CVS SCM module [Brian Phillips] * Fix typo in Perforce SCM module [Chris Bailey] * ssh_options < server options when connecting [Jamis Buck] * Logger defaults to $stderr instead of STDERR [lhartley] * Use cp -RPp instead of -a in the remote cache strategy * Make the UploadError exception include an array of the hosts that failed [rob@inversepath.com] * Allow "empty" roles to be declared [Jamis Buck] * Mercurial SCM module [Tobias Luetke, Matthew Elder] * Invoke all commands via sh (customizable via :default_shell) [Jamis Buck] * Make sure all directories exist on each deploy which are necessary for subsequent commands to succeed, since some SCM's won't save empty directories [Matthew Elder] * Add :default_environment variable, which is applied to every command ## 1.99.1 (2.0 Preview 2) / May 10, 2007 * Fix some documentation typos [eventualbuddha] * Don't retry failed connections if an explicit auth_methods list is given [Chris Farms] * Added support for load and exit callbacks, which get invoked when all recipes have been loaded and when all requested tasks have been executed [Jamis Buck] * Added support for start and finish callbacks, which get invoked when any task is called via the command-line [Jamis Buck] * Make `capify' understand simple command-line switches [Jamis Buck] * Make the server definition itself available to SSH channels, rather than just the host name [Jamis Buck] * Identify servers by their complete credentials in logs, rather than simply by hostname [Jamis Buck] * Uniquely identify servers based on hostname, port, and username, instead of merely on hostname [Jamis Buck] * Allow (e.g.) scm_command and local_scm_command to be set in the event of different paths to the scm command on local vs. remote hosts. [Jamis Buck] * Kill the "deploy:app" namespace and move those tasks into deploy, directly. [Jamis Buck] * Make sure 'desc' applies to the next defined task, in any namespace. [Jamis Buck] * Fix shell so that servers for a task are correctly discovered. [Jamis Buck] * Added before(), after(), and on() callback creation methods. [Jamis Buck] * Fix broken check! method for some deployment strategies. [Jamis Buck] * deploy:cold should also run migrations before starting the app [Jamis Buck] * Make the copy strategy check out to a temporary directory [Jamis Buck] ## 1.99.0 (2.0 Preview 1) / April 24, 2007 * Add `capify' script to make it easier to prepare a project for deployment using cap [Jamis Buck] * Make sure the sudo helper understands the SuSE dialect of the sudo password prompt [Steven Wisener] * Fix synchronization issue with Gateway initialization [Doug Barth] * Added opt-in "compat" and "upgrade" recipes for tasks to aid in the upgrade process to Capistrano 2 [Jamis Buck] * The deployment recipes are now opt-in. Just do 'load "deploy"' in your recipe script. [Jamis Buck] * Added $CAPISTRANO:HOST$ placeholder in commands, which will be replaced with the name of the host on which the command is executing [Jamis Buck] * Added -e switch to explain specific task. Added -X to extend -x. Made -h much briefer. Added -T to list known tasks. [Jamis Buck] * Added namespaces for tasks [Jamis Buck] * Merged the Configuration and Actor classes, performed various other massive refactorings of the code [Jamis Buck] ## 1.4.1 / February 24, 2007 * Use the no-auth-cache option with subversion so that username/password tokens do not get cached by capistrano usage [jonathan] * Deprecated upper-cased variables [Jamis Buck] * Make sure Actor#get does not close the SFTP channel (so subsequent SFTP operations work) [Dov Murik] * Add :env option to 'run' (and friends) so that you can specify environment variables to be injected into the new process' environment [Mathieu Lajugie] ## 1.4.0 / February 3, 2007 * Use the auth info for subversion more consistently [Jamis Buck] * Add a "capture" helper, for capturing the stdout of a remote command and returning it as a string [Jamis Buck] * Add a "get" helper, to pull a file from a remote server to the localhost [bmihelac] * Fix gateway to actually increment local_port if a port is in use, so that multiple capistrano instances can run at the same time [Mark Imbriaco] * Refactor the permissions tweaking in update_code to a separate task so that people on shared hosts can override it as necessary [jaw6] * Set umask during the setup task, so that intermediate directories are created with the proper permissions [NeilW] * Removed -c/--caprc switch, since the new load order renders it meaningless (just use -f now) [Mike Bailey] * Make sure the standard recipe loads first, so that .caprc and friends can override what it defines. [Mike Bailey] * Add support for a system-wide capistrano config file [Mike Bailey] * Make cold_deploy call update instead of deploy (to avoid invoking the restart task). * Make the touch command run by update_code set the TZ to UTC, for consistent setting of asset timestamps. [NeilW] * Fix off-by-one bug in show_tasks width-computation [NeilW] ## 1.3.1 / January 5, 2007 * Fix connection problems when using gateways [Ezra Zygmuntowicz] ## 1.3.0 / December 23, 2006 * Deprecate rake integration in favor of invoking `cap' directly [Jamis Buck] * Make sure the CVS module references the repository explicitly in cvs_log [weyus@att.net] * Remove trace messages when loading a file [Jamis Buck] * Cleaner error messages for authentication failures and command errors [Jamis Buck] * Added support for ~/.caprc, also -x and -c switches. [Jamis Buck] * Updated migrate action to use db:migrate task in Rails instead of the deprecated migrate task [DHH] * Allow SSH user and port to be encoded in the hostname strings [Ezra Zygmuntowicz] * Fixed that new checkouts were not group-writable [DHH, Jamis Buck] * Fixed that cap setup would use 755 on the deploy_to and shared directory roots instead of 775 [DHH] * Don't run the cleanup task on servers marked no_release [Jamis Buck] * Fix typo in default_io_proc so it correctly checks the stream parameter to see if it is the error stream [Stephen Haberman] * Make sure assets in images, javascripts, and stylesheets are touched after updating the code, to ensure the asset timestamping feature of rails works correctly [Jamis Buck] * Added warning if password is prompted for and termios is not installed [John Labovitz] * Added :as option to sudo, so you can specify who the command is executed as [Mark Imbriaco] ## 1.2.0 / September 14, 2006 * Add experimental 'shell' task [Jamis Buck] * Display file for external configurations, rather than inspected proc. [Jamis Buck] * Connect to multiple servers in parallel, rather than serially. [Jamis Buck] * Add SCM module for Mercurial (closes #4150) [Matthew Elder] * Remove unused line in SCM::Base (closes #5619) [chris@seagul.co.uk] * More efficient "svn log" usage (closes #5620) [Anatol Pomozov] * Better support for key passphrases in the SVN module (closes #5920) [llasram@gmail.com] * Fix missing default for :local in cvs.rb (closes #3645) [jeremy@hinegardner.org] * Fix awkward spacing in gemspec file (closes #3888) [grant@antiflux.org] * Add support for :sudo variable to specify path to sudo (closes #4578) [timothee.peignier@tryphon.org] * Make previous_release return nil if there are no previous releases (closes #4959) [bdabney@cavoksolutions.com] * Uncache releases list after update_code is called so that newly released dir is included (closes #3766) [Jamis Buck] * Allow the subversion scm to accept HTTPS certificates (closes #4792) [Jamis Buck] * Make sure rollbacks occur within the scope of the task that triggered them [Jamis Buck] * Fixed the default recipe to work with setups that haven't yet gone pids [DHH] * Symlink and setup for shared/pids to tmp/pids [DHH] * Fix some incorrect usage text (closes #4507) [gerry_shaw@yahoo.com] * Added Actor#stream method that makes it easy to create cross-server streams [DHH]. Example: desc "Run a tail on multiple log files at the same time" task :tail_fcgi, :roles => :app do stream "tail -f #{shared_path}/log/fastcgi.crash.log" end * Make update_code and symlink a macro task under the name "update" for easy of deploy to servers that does not run fcgis [DHH] * Changed setup, update_code, rollback_code, and symlink to work on all servers instead of only those in the :app, :web, and :db roles. A server can opt out of being part of the release deployment by setting :no_release => true [DHH] * Added support for :except on task declarations as the opposite of :only [DHH]. Example: role :app, "192.168.0.2" role :file, "192.168.0.3", :no_release => true task :symlink, :except => { :no_release => true } do on_rollback { run "ln -nfs #{previous_release} #{current_path}" } run "ln -nfs #{current_release} #{current_path}" end cap symlink # will not run on 192.168.0.3 * Deprecate the -r/--recipe switch in favor of -f/--file (for more make/rake-like semantics) [Jamis Buck] * Fix gemspec to include a dependency on rake 0.7 [Jamis Buck] * Added respect for ENV["HOSTS"] that'll be used instead of the roles specified in the task definition [DHH]. Example: HOSTS=192.168.0.1 cap setup # one-off setup for that server, doesn't need to be prespecified in the recipes file * Added respect for ENV["ROLES"] that'll be used instead of the roles specified in the task definition [DHH]. Example: task :setup, :roles => [ :app, :web, :db ] # normally this would run every where end ROLES=app cap setup # this will only run for the app role, overwritting the default declaration * Added :hosts option to task definition that allows you to specify cross-cutting tasks [DHH]. Example: task :setup, :hosts => [ "06.example.com", "01.example.com" ] do # this task will happen on 06 and 01 regardless of which roles they belong to end * Fix operator precedence problem in script for touching the revisions.log #3223 [jason.garber@emu.edu] ## 1.1.0 / March 6th, 2006 * Simplify the generated capistrano.rake file, and make it easier to customize * Use rake-like command-line semantics ("cap deploy", in addition to "cap -a deploy") * Rename to capistrano * Make the generated capistrano.rake file use rake namespaces, and include all default tasks * Look for config/deploy.rb, capfile, and Capfile by default ## 1.0.1 / February 20th, 2006 * Fix broken switchtower_invoke function in switchtower.rake (missing require statement) ## 1.0.0 / Feburary 18th, 2006 * Make CVS module's :local value default to "." * Add "invoke" task for executing one-off commands * Make port selection smarter for gateway connections * Add extension mechanism for custom ST operations and third-party task libraries * Make ST rails rake tasks more configurable * Add Actor#current_task and simplify Task#servers * Add Actor#connect! method for working around lazy connection establishing * Make sure IO::TRUNC is specified for Net::SFTP uploads (#3510) * Add branch support to CVS [jeremy@hinegardner.org] (#3596) * Add bazaar-ng SCM module [Damien Merenne] * Add optional :svn_username and :svn_password variables * Allow Proc-valued variables to be set more conveniently (set(:foo) { "bar" }) * Add perforce SCM module [Richard McMahon] * Add bazaar (v1) SCM module [Edd Dumbill] (#3533) * Fix stftime format string used in CVS module to be Windows-compatible (fixes #3383) * Add an better error when a task is run and no servers match the required conditions * Add default spinner and cold_deploy tasks, and spinner_user variable * Changed restart_via variable to (boolean) use_sudo * Only chmod when the revisions.log file is first created * Make UPPERCASE variables work * Added rails_env variable (defaults to production) for use by tasks that employ the RAILS_ENV environment variable * Added Actor.default_io_proc * Set :actor key on SSH channel instances ## 0.10.0 / January 2nd, 2006 * Handle ssh password prompts like "someone's password:" * Make CLI#echo available as a class method. * Add CLI#with_echo. * Make the default password prompt available as a class method. # Add documentation for the CLI class. * Add a sanity check to make sure the correct versions of Net::SSH and Net::SFTP are installed. * Added a cleanup task to remove unused releases from the deployment directory * Allow password to be reentered on sudo if it was entered incorrectly * Use && as the command separator for the checkouts, so that errors are caught early. * Ping each SSH connection every 1s during command processing so that long-running commands don't cause the connection to timeout. * Add a 0.01s sleep during the command loop so that the CPU doesn't go ballistic while ST is doing its thing. * Add :restart_via variable for specifying whether restart ought to use :sudo (default, use sudo) * Use SFTP for file transfers (if available). * Add an "update_current" task that will do an svn up on the current release * Use the :checkout variable to determine what operation to use for svn checkouts (instead of co, like "export"). * The Rails rake tasks now load ST directly, instead of invoking it via system * Added ssh_options variable to configure the SSH connection parameters #2734 [jerrett@bravenet.com] * Require Net::SSH 1.0.5 ## 0.9.0 / October 18th, 2005 * Use process reaper instead of custom reap script for restarting * Use -S switch to set variables before reading recipe files #2242 * Have setup.rb create a switchtower.cmd file on Win32 platforms #2402 * Add diff_from_last_deploy to the rails switchtower rakefile template * Add diff_from_last_deploy task (currently only works with subversion) * Add deploy_with_migrations task. * Make the migrate task more customizable. * If no password is given with the -p switch, prompt for password immediately. * Do not install a switchtower stub in the script directory. Assume the switchtower executable is in the path. * Remove trailing newlines from commands to prevent trailing backslash #2141 * Default parameters work correctly with the generator #2218 [Scott Barron] * Attempt to require 'rubygems' explicitly when running the switchtower utility #2134 * Make default tasks work only on app/db/web roles, so that additional roles may be created for boxes with specific needs without needing to (for instance) deploy the app to those boxes * Default the application name to "Application" when using --apply-to * Show the help screen instead of an error when no arguments are given * Make SwitchTower easier to invoke programmatically via SwitchTower::CLI * Specify the revision to release via the :revision variable (defaults to latest revision) * Allow variables to be set via the cli using the -s switch * Log checkouts to a "revisions.log" file * Changed behavior of checkout to use the timestamp as the release name, instead of the revision number * Added CVS module (very very experimental!) * Works with public keys now, for passwordless deployment * Subversion module recognizes the password prompt for HTTP authentication * Preserve +x on scripts when using darcs #1929 [Scott Barron] * When executing multiline commands, use a backslash to escape the newline capistrano-2.12.0/Gemfile0000644000004100000410000000032311746743503015272 0ustar www-datawww-datasource "http://rubygems.org" # Specify your gem's dependencies in capistrano.gemspec gemspec # # Development Dependencies from the Gemfile # are merged here. # group :development do gem 'rake', '0.8.7' end capistrano-2.12.0/.gitignore0000644000004100000410000000011311746743503015764 0ustar www-datawww-datacoverage doc pkg *.swp *.patch *.gem *.sh .DS_Store .bundle/* Gemfile.lock capistrano-2.12.0/lib/0000755000004100000410000000000011746743503014547 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano.rb0000644000004100000410000000022111746743503017232 0ustar www-datawww-datarequire 'capistrano/fix_rake_deprecated_dsl' require 'capistrano/configuration' require 'capistrano/extensions' require 'capistrano/ext/string' capistrano-2.12.0/lib/capistrano/0000755000004100000410000000000011746743503016712 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/ext/0000755000004100000410000000000011746743503017512 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/ext/multistage.rb0000644000004100000410000000360211746743503022216 0ustar www-datawww-datarequire 'fileutils' unless Capistrano::Configuration.respond_to?(:instance) abort "capistrano/ext/multistage requires Capistrano 2" end Capistrano::Configuration.instance.load do location = fetch(:stage_dir, "config/deploy") unless exists?(:stages) set :stages, Dir["#{location}/*.rb"].map { |f| File.basename(f, ".rb") } end stages.each do |name| desc "Set the target stage to `#{name}'." task(name) do set :stage, name.to_sym load "#{location}/#{stage}" end end on :load do if stages.include?(ARGV.first) # Execute the specified stage so that recipes required in stage can contribute to task list find_and_execute_task(ARGV.first) if ARGV.any?{ |option| option =~ /-T|--tasks|-e|--explain/ } else # Execute the default stage so that recipes required in stage can contribute tasks find_and_execute_task(default_stage) if exists?(:default_stage) end end namespace :multistage do desc "[internal] Ensure that a stage has been selected." task :ensure do if !exists?(:stage) if exists?(:default_stage) logger.important "Defaulting to `#{default_stage}'" find_and_execute_task(default_stage) else abort "No stage specified. Please specify one of: #{stages.join(', ')} (e.g. `cap #{stages.first} #{ARGV.last}')" end end end desc "Stub out the staging config files." task :prepare do FileUtils.mkdir_p(location) stages.each do |name| file = File.join(location, name + ".rb") unless File.exists?(file) File.open(file, "w") do |f| f.puts "# #{name.upcase}-specific deployment configuration" f.puts "# please put general deployment config in config/deploy.rb" end end end end end on :start, "multistage:ensure", :except => stages + ['multistage:prepare'] end capistrano-2.12.0/lib/capistrano/ext/string.rb0000644000004100000410000000007711746743503021351 0ustar www-datawww-dataclass String def compact self.gsub(/\s+/, ' ') end end capistrano-2.12.0/lib/capistrano/command.rb0000644000004100000410000002065711746743503020667 0ustar www-datawww-datarequire 'benchmark' require 'capistrano/errors' require 'capistrano/processable' module Capistrano # This class encapsulates a single command to be executed on a set of remote # machines, in parallel. class Command include Processable class Tree attr_reader :configuration attr_reader :branches attr_reader :fallback include Enumerable class Branch attr_accessor :command, :callback attr_reader :options def initialize(command, options, callback) @command = command.strip.gsub(/\r?\n/, "\\\n") @callback = callback || Capistrano::Configuration.default_io_proc @options = options @skip = false end def last? options[:last] end def skip? @skip end def skip! @skip = true end def match(server) true end def to_s command.inspect end end class ConditionBranch < Branch attr_accessor :configuration attr_accessor :condition class Evaluator attr_reader :configuration, :condition, :server def initialize(config, condition, server) @configuration = config @condition = condition @server = server end def in?(role) configuration.roles[role].include?(server) end def result eval(condition, binding) end def method_missing(sym, *args, &block) if server.respond_to?(sym) server.send(sym, *args, &block) elsif configuration.respond_to?(sym) configuration.send(sym, *args, &block) else super end end end def initialize(configuration, condition, command, options, callback) @configuration = configuration @condition = condition super(command, options, callback) end def match(server) Evaluator.new(configuration, condition, server).result end def to_s "#{condition.inspect} :: #{command.inspect}" end end def initialize(config) @configuration = config @branches = [] yield self if block_given? end def when(condition, command, options={}, &block) branches << ConditionBranch.new(configuration, condition, command, options, block) end def else(command, &block) @fallback = Branch.new(command, {}, block) end def branches_for(server) seen_last = false matches = branches.select do |branch| success = !seen_last && !branch.skip? && branch.match(server) seen_last = success && branch.last? success end matches << fallback if matches.empty? && fallback return matches end def each branches.each { |branch| yield branch } yield fallback if fallback return self end end attr_reader :tree, :sessions, :options def self.process(tree, sessions, options={}) new(tree, sessions, options).process! end # Instantiates a new command object. The +command+ must be a string # containing the command to execute. +sessions+ is an array of Net::SSH # session instances, and +options+ must be a hash containing any of the # following keys: # # * +logger+: (optional), a Capistrano::Logger instance # * +data+: (optional), a string to be sent to the command via it's stdin # * +env+: (optional), a string or hash to be interpreted as environment # variables that should be defined for this command invocation. def initialize(tree, sessions, options={}, &block) if String === tree tree = Tree.new(nil) { |t| t.else(tree, &block) } elsif block raise ArgumentError, "block given with tree argument" end @tree = tree @sessions = sessions @options = options @channels = open_channels end # Processes the command in parallel on all specified hosts. If the command # fails (non-zero return code) on any of the hosts, this will raise a # Capistrano::CommandError. def process! elapsed = Benchmark.realtime do loop do break unless process_iteration { @channels.any? { |ch| !ch[:closed] } } end end logger.trace "command finished in #{(elapsed * 1000).round}ms" if logger if (failed = @channels.select { |ch| ch[:status] != 0 }).any? commands = failed.inject({}) { |map, ch| (map[ch[:command]] ||= []) << ch[:server]; map } message = commands.map { |command, list| "#{command.inspect} on #{list.join(',')}" }.join("; ") error = CommandError.new("failed: #{message}") error.hosts = commands.values.flatten raise error end self end # Force the command to stop processing, by closing all open channels # associated with this command. def stop! @channels.each do |ch| ch.close unless ch[:closed] end end private def logger options[:logger] end def open_channels sessions.map do |session| server = session.xserver tree.branches_for(server).map do |branch| session.open_channel do |channel| channel[:server] = server channel[:host] = server.host channel[:options] = options channel[:branch] = branch request_pty_if_necessary(channel) do |ch, success| if success logger.trace "executing command", ch[:server] if logger cmd = replace_placeholders(channel[:branch].command, ch) if options[:shell] == false shell = nil else shell = "#{options[:shell] || "sh"} -c" cmd = cmd.gsub(/'/) { |m| "'\\''" } cmd = "'#{cmd}'" end command_line = [environment, shell, cmd].compact.join(" ") ch[:command] = command_line ch.exec(command_line) ch.send_data(options[:data]) if options[:data] else # just log it, don't actually raise an exception, since the # process method will see that the status is not zero and will # raise an exception then. logger.important "could not open channel", ch[:server] if logger ch.close end end channel.on_data do |ch, data| ch[:branch].callback[ch, :out, data] end channel.on_extended_data do |ch, type, data| ch[:branch].callback[ch, :err, data] end channel.on_request("exit-status") do |ch, data| ch[:status] = data.read_long end channel.on_close do |ch| ch[:closed] = true end end end end.flatten end def request_pty_if_necessary(channel) if options[:pty] channel.request_pty do |ch, success| yield ch, success end else yield channel, true end end def replace_placeholders(command, channel) roles = @tree.configuration && @tree.configuration.role_names_for_host(channel[:server]) command = command.gsub(/\$CAPISTRANO:HOST\$/, channel[:host]) command.gsub!(/\$CAPISTRANO:HOSTROLES\$/, roles.join(',')) if roles command end # prepare a space-separated sequence of variables assignments # intended to be prepended to a command, so the shell sets # the environment before running the command. # i.e.: options[:env] = {'PATH' => '/opt/ruby/bin:$PATH', # 'TEST' => '( "quoted" )'} # environment returns: # "env TEST=(\ \"quoted\"\ ) PATH=/opt/ruby/bin:$PATH" def environment return if options[:env].nil? || options[:env].empty? @environment ||= if String === options[:env] "env #{options[:env]}" else options[:env].inject("env") do |string, (name, value)| value = value.to_s.gsub(/[ "]/) { |m| "\\#{m}" } string << " #{name}=#{value}" end end end end end capistrano-2.12.0/lib/capistrano/version.rb0000644000004100000410000000025111746743503020722 0ustar www-datawww-datarequire 'scanf' module Capistrano class Version MAJOR = 2 MINOR = 12 PATCH = 0 def self.to_s "#{MAJOR}.#{MINOR}.#{PATCH}" end end end capistrano-2.12.0/lib/capistrano/fix_rake_deprecated_dsl.rb0000644000004100000410000000033711746743503024054 0ustar www-datawww-data# # See https://github.com/jimweirich/rake/issues/81 # if defined?(Rake::DeprecatedObjectDSL) Rake::DeprecatedObjectDSL.private_instance_methods.each do |m| Rake::DeprecatedObjectDSL.send("undef_method", m) end end capistrano-2.12.0/lib/capistrano/cli.rb0000644000004100000410000000336611746743503020016 0ustar www-datawww-datarequire 'capistrano' require 'capistrano/cli/execute' require 'capistrano/cli/help' require 'capistrano/cli/options' require 'capistrano/cli/ui' module Capistrano # The CLI class encapsulates the behavior of capistrano when it is invoked # as a command-line utility. This allows other programs to embed Capistrano # and preserve its command-line semantics. class CLI # The array of (unparsed) command-line options attr_reader :args # Create a new CLI instance using the given array of command-line parameters # to initialize it. By default, +ARGV+ is used, but you can specify a # different set of parameters (such as when embedded cap in a program): # # require 'capistrano/cli' # Capistrano::CLI.parse(%W(-vvvv -f config/deploy update_code)).execute! # # Note that you can also embed cap directly by creating a new Configuration # instance and setting it up, The above snippet, redone using the # Configuration class directly, would look like: # # require 'capistrano' # require 'capistrano/cli' # config = Capistrano::Configuration.new # config.logger.level = Capistrano::Logger::TRACE # config.set(:password) { Capistrano::CLI.password_prompt } # config.load "config/deploy" # config.update_code # # There may be times that you want/need the additional control offered by # manipulating the Configuration directly, but generally interfacing with # the CLI class is recommended. def initialize(args) @args = args.dup $stdout.sync = true # so that Net::SSH prompts show up end # Mix-in the actual behavior include Execute, Options, UI include Help # needs to be included last, because it overrides some methods end end capistrano-2.12.0/lib/capistrano/cli/0000755000004100000410000000000011746743503017461 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/cli/options.rb0000644000004100000410000001644011746743503021506 0ustar www-datawww-datarequire 'optparse' module Capistrano class CLI module Options def self.included(base) base.extend(ClassMethods) end module ClassMethods # Return a new CLI instance with the given arguments pre-parsed and # ready for execution. def parse(args) cli = new(args) cli.parse_options! cli end end # The hash of (parsed) command-line options attr_reader :options # Return an OptionParser instance that defines the acceptable command # line switches for Capistrano, and what their corresponding behaviors # are. def option_parser #:nodoc: @logger = Logger.new @option_parser ||= OptionParser.new do |opts| opts.banner = "Usage: #{File.basename($0)} [options] action ..." opts.on("-d", "--debug", "Prompts before each remote command execution." ) { |value| options[:debug] = true } opts.on("-e", "--explain TASK", "Displays help (if available) for the task." ) { |value| options[:explain] = value } opts.on("-F", "--default-config", "Always use default config, even with -f." ) { options[:default_config] = true } opts.on("-f", "--file FILE", "A recipe file to load. May be given more than once." ) { |value| options[:recipes] << value } opts.on("-H", "--long-help", "Explain these options and environment variables.") do long_help exit end opts.on("-h", "--help", "Display this help message.") do puts opts exit end opts.on("-l", "--logger [STDERR|STDOUT|file]", "Choose logger method. STDERR used by default." ) do |value| options[:output] = if value.nil? || value.upcase == 'STDERR' # Using default logger. nil elsif value.upcase == 'STDOUT' $stdout else value end end opts.on("-n", "--dry-run", "Prints out commands without running them." ) { |value| options[:dry_run] = true } opts.on("-p", "--password", "Immediately prompt for the password." ) { options[:password] = nil } opts.on("-q", "--quiet", "Make the output as quiet as possible." ) { options[:verbose] = 0 } opts.on("-r", "--preserve-roles", "Preserve task roles" ) { options[:preserve_roles] = true } opts.on("-S", "--set-before NAME=VALUE", "Set a variable before the recipes are loaded." ) do |pair| name, value = pair.split(/=/, 2) options[:pre_vars][name.to_sym] = value end opts.on("-s", "--set NAME=VALUE", "Set a variable after the recipes are loaded." ) do |pair| name, value = pair.split(/=/, 2) options[:vars][name.to_sym] = value end opts.on("-T", "--tasks [PATTERN]", "List all tasks (matching optional PATTERN) in the loaded recipe files." ) do |value| options[:tasks] = if value value else true end options[:verbose] ||= 0 end opts.on("-t", "--tool", "Abbreviates the output of -T for tool integration." ) { options[:tool] = true } opts.on("-V", "--version", "Display the Capistrano version, and exit." ) do require 'capistrano/version' puts "Capistrano v#{Capistrano::Version}" exit end opts.on("-v", "--verbose", "Be more verbose. May be given more than once." ) do options[:verbose] ||= 0 options[:verbose] += 1 end opts.on("-X", "--skip-system-config", "Don't load the system config file (capistrano.conf)" ) { options.delete(:sysconf) } opts.on("-x", "--skip-user-config", "Don't load the user config file (.caprc)" ) { options.delete(:dotfile) } end end # If the arguments to the command are empty, this will print the # allowed options and exit. Otherwise, it will parse the command # line and set up any default options. def parse_options! #:nodoc: @options = { :recipes => [], :actions => [], :vars => {}, :pre_vars => {}, :sysconf => default_sysconf, :dotfile => default_dotfile } if args.empty? warn "Please specify at least one action to execute." warn option_parser exit end option_parser.parse!(args) coerce_variable_types! # if no verbosity has been specified, be verbose options[:verbose] = 3 if !options.has_key?(:verbose) look_for_default_recipe_file! if options[:default_config] || options[:recipes].empty? extract_environment_variables! options[:actions].concat(args) password = options.has_key?(:password) options[:password] = Proc.new { self.class.password_prompt } options[:password] = options[:password].call if password end # Extracts name=value pairs from the remaining command-line arguments # and assigns them as environment variables. def extract_environment_variables! #:nodoc: args.delete_if do |arg| next unless arg.match(/^(\w+)=(.*)$/) ENV[$1] = $2 end end # Looks for a default recipe file in the current directory. def look_for_default_recipe_file! #:nodoc: current = Dir.pwd loop do %w(Capfile capfile).each do |file| if File.file?(file) options[:recipes] << file @logger.info "Using recipes from #{File.join(current,file)}" return end end pwd = Dir.pwd Dir.chdir("..") break if pwd == Dir.pwd # if changing the directory made no difference, then we're at the top end Dir.chdir(current) end def default_sysconf #:nodoc: File.join(sysconf_directory, "capistrano.conf") end def default_dotfile #:nodoc: File.join(home_directory, ".caprc") end def sysconf_directory #:nodoc: # TODO if anyone cares, feel free to submit a patch that uses a more # appropriate location for this file in Windows. ENV["SystemRoot"] || '/etc' end def home_directory #:nodoc: ENV["HOME"] || (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") || "/" end def coerce_variable_types! [:pre_vars, :vars].each do |collection| options[collection].keys.each do |key| options[collection][key] = coerce_variable(options[collection][key]) end end end def coerce_variable(value) case value when /^"(.*)"$/ then $1 when /^'(.*)'$/ then $1 when /^\d+$/ then value.to_i when /^\d+\.\d*$/ then value.to_f when "true" then true when "false" then false when "nil" then nil else value end end end end end capistrano-2.12.0/lib/capistrano/cli/execute.rb0000644000004100000410000000520611746743503021453 0ustar www-datawww-datarequire 'capistrano/configuration' module Capistrano class CLI module Execute def self.included(base) #:nodoc: base.extend(ClassMethods) end module ClassMethods # Invoke capistrano using the ARGV array as the option parameters. This # is what the command-line capistrano utility does. def execute parse(ARGV).execute! end end # Using the options build when the command-line was parsed, instantiate # a new Capistrano configuration, initialize it, and execute the # requested actions. # # Returns the Configuration instance used, if successful. def execute! config = instantiate_configuration(options) config.debug = options[:debug] config.dry_run = options[:dry_run] config.preserve_roles = options[:preserve_roles] config.logger.level = options[:verbose] set_pre_vars(config) load_recipes(config) config.trigger(:load) execute_requested_actions(config) config.trigger(:exit) config rescue Exception => error handle_error(error) end def execute_requested_actions(config) Array(options[:vars]).each { |name, value| config.set(name, value) } Array(options[:actions]).each do |action| config.find_and_execute_task(action, :before => :start, :after => :finish) end end def set_pre_vars(config) #:nodoc: config.set :password, options[:password] Array(options[:pre_vars]).each { |name, value| config.set(name, value) } end def load_recipes(config) #:nodoc: # load the standard recipe definition config.load "standard" # load systemwide config/recipe definition config.load(options[:sysconf]) if options[:sysconf] && File.file?(options[:sysconf]) # load user config/recipe definition config.load(options[:dotfile]) if options[:dotfile] && File.file?(options[:dotfile]) Array(options[:recipes]).each { |recipe| config.load(recipe) } end # Primarily useful for testing, but subclasses of CLI could conceivably # override this method to return a Configuration subclass or replacement. def instantiate_configuration(options={}) #:nodoc: Capistrano::Configuration.new(options) end def handle_error(error) #:nodoc: case error when Net::SSH::AuthenticationFailed abort "authentication failed for `#{error.message}'" when Capistrano::Error abort(error.message) else raise error end end end end end capistrano-2.12.0/lib/capistrano/cli/help.rb0000644000004100000410000001017211746743503020737 0ustar www-datawww-datamodule Capistrano class CLI module Help LINE_PADDING = 7 MIN_MAX_LEN = 30 HEADER_LEN = 60 def self.included(base) #:nodoc: base.send :alias_method, :execute_requested_actions_without_help, :execute_requested_actions base.send :alias_method, :execute_requested_actions, :execute_requested_actions_with_help end def execute_requested_actions_with_help(config) if options[:tasks] task_list(config, options[:tasks]) elsif options[:explain] explain_task(config, options[:explain]) else execute_requested_actions_without_help(config) end end def task_list(config, pattern = true) #:nodoc: tool_output = options[:tool] if pattern.is_a?(String) tasks = config.task_list(:all).select {|t| t.fully_qualified_name =~ /#{pattern}/} end if tasks.nil? || tasks.length == 0 warn "Pattern '#{pattern}' not found. Listing all tasks.\n\n" if !tool_output && !pattern.is_a?(TrueClass) tasks = config.task_list(:all) end if tasks.empty? warn "There are no tasks available. Please specify a recipe file to load." unless tool_output else all_tasks_length = tasks.length if options[:verbose].to_i < 1 tasks = tasks.reject { |t| t.description.empty? || t.description =~ /^\[internal\]/ } end tasks = tasks.sort_by { |task| task.fully_qualified_name } longest = tasks.map { |task| task.fully_qualified_name.length }.max max_length = output_columns - longest - LINE_PADDING max_length = MIN_MAX_LEN if max_length < MIN_MAX_LEN tasks.each do |task| if tool_output puts "cap #{task.fully_qualified_name}" else puts "cap %-#{longest}s # %s" % [task.fully_qualified_name, task.brief_description(max_length)] end end unless tool_output if all_tasks_length > tasks.length puts puts "Some tasks were not listed, either because they have no description," puts "or because they are only used internally by other tasks. To see all" puts "tasks, type `#{File.basename($0)} -vT'." end puts puts "Extended help may be available for these tasks." puts "Type `#{File.basename($0)} -e taskname' to view it." end end end def explain_task(config, name) #:nodoc: task = config.find_task(name) if task.nil? warn "The task `#{name}' does not exist." else puts "-" * HEADER_LEN puts "cap #{name}" puts "-" * HEADER_LEN if task.description.empty? puts "There is no description for this task." else puts format_text(task.description) end puts end end def long_help #:nodoc: help_text = File.read(File.join(File.dirname(__FILE__), "help.txt")) self.class.ui.page_at = self.class.ui.output_rows - 2 self.class.ui.say format_text(help_text) end def format_text(text) #:nodoc: formatted = "" text.each_line do |line| indentation = line[/^\s+/] || "" indentation_size = indentation.split(//).inject(0) { |c,s| c + (s[0] == ?\t ? 8 : 1) } line_length = output_columns - indentation_size line_length = MIN_MAX_LEN if line_length < MIN_MAX_LEN lines = line.strip.gsub(/(.{1,#{line_length}})(?:\s+|\Z)/, "\\1\n").split(/\n/) if lines.empty? formatted << "\n" else formatted << lines.map { |l| "#{indentation}#{l}\n" }.join end end formatted end def output_columns #:nodoc: if ( @output_columns.nil? ) if ( self.class.ui.output_cols.nil? || self.class.ui.output_cols > 80 ) @output_columns = 80 else @output_columns = self.class.ui.output_cols end end @output_columns end end end end capistrano-2.12.0/lib/capistrano/cli/ui.rb0000644000004100000410000000200711746743503020422 0ustar www-datawww-datarequire 'highline' # work around problem where HighLine detects an eof on $stdin and raises an # error. HighLine.track_eof = false module Capistrano class CLI module UI def self.included(base) #:nodoc: base.extend(ClassMethods) end module ClassMethods # Return the object that provides UI-specific methods, such as prompts # and more. def ui @ui ||= HighLine.new end # Prompt for a password using echo suppression. def password_prompt(prompt="Password: ") ui.ask(prompt) { |q| q.echo = false } end # Debug mode prompt def debug_prompt(cmd) ui.say("Preparing to execute command: #{cmd}") prompt = "Execute ([Yes], No, Abort) " ui.ask("#{prompt}? ") do |q| q.overwrite = false q.default = 'y' q.validate = /(y(es)?)|(no?)|(a(bort)?|\n)/i q.responses[:not_valid] = prompt end end end end end end capistrano-2.12.0/lib/capistrano/cli/help.txt0000644000004100000410000001405611746743503021160 0ustar www-datawww-data----------------------------- <%= color('Capistrano', :bold) %> ----------------------------- Capistrano is a utility for automating the execution of commands across multiple remote machines. It was originally conceived as an aid to deploy Ruby on Rails web applications, but has since evolved to become a much more general-purpose tool. The command-line interface to Capistrano is via the `cap' command. cap [ option ] ... action ... The following options are understood: <%= color '-d, --debug', :bold %> Causes Capistrano to pause and prompt before executing any remote command, giving the user the option to either execute the command, skip the command, or abort execution entirely. This makes it a great way to troubleshoot tasks, or test custom tasks, by executing commands one at a time and checking the server to make sure they worked as expected before moving on to the next command. (Compare this to the --dry-run command.) <%= color '-e, --explain TASK', :bold %> Displays the extended description of the given task. Not all tasks will have an extended description, but for those that do, this can provide a wealth of additional usage information, such as describing environment variables or settings that can affect the execution of the task. <%= color '-F, --default-config', :bold %> By default, cap will search for a config file named `Capfile' or `capfile' in the current directory, or in any parent directory, and will automatically load it. However, if you specify the -f flag (see below), cap will use that file instead of the default config. If you want to use both the default config, and files loaded via -f, you can specify -F to force cap to search for and load the default config, even if additional files were specified via -f. <%= color '-f, --file FILE', :bold %> Causes the named file to be loaded. Capistrano will search both its own recipe directory, as well as the current directory, looking for the named file. An ".rb" extension is optional. The -f option may be given any number of times, but if it is given, it will take the place of the normal `Capfile' or `capfile' detection. Use -F if you want the default capfile to be loaded when you use -f. <%= color '-H, --long-help', :bold %> Displays this document and exits. <%= color '-h, --help', :bold %> Shows a brief summary of these options and exits. <%= color '-l, --logger [STDERR|STDOUT|file]', :bold %> Change the file used to print the output. It offers three options: standard error(stderr), standard output and file. Options are not case sensitive. By default Capistrano uses stderr. <%= color '-n, --dry-run', :bold %> Causes Capistrano to simply display each remote command, without executing it. In this sense it is similar to --debug, but without the prompt. Note that commands executed locally are still run--only remote commands are skipped. <%= color '-p, --password', :bold %> Normally, cap will prompt for the password on-demand, the first time it is needed. This can make it hard to walk away from Capistrano, since you might not know if it will prompt for a password down the road. In such cases, you can use the -p option to force cap to prompt for the password immediately. <%= color '-q, --quiet', :bold %> Display only critical error messages. All other output is suppressed. <%= color '-S, --set-before NAME=VALUE', :bold %> Sets the given variable to the given value, before loading any recipe files. This is useful if you have a recipe file that depends on a certain variable being set, at the time it is loaded. <%= color '-s, --set NAME=VALUE', :bold %> Sets the given variable to the given value, after loading all recipe files. This is useful when you want to override the value of a variable which is used in a task. Note that this will set the variables too late for them to affect conditions that are executed as the recipes are loaded. <%= color '-T, --tasks PATTERN', :bold %> Displays the list of all tasks (matching optional PATTERN) in all loaded recipe files. If a task has no description, or if the description starts with the [internal] tag, the task will not be listed unless you also specify -v. <%= color '-t, --tool', :bold %> Abbreviates the output of -T for integration with other tools. Without -t, -T will list tasks with their summaries, and may include additional instructive text at the bottom. When integrating with other tools (e.g., bash auto-expansion and the like) that additional text can get in the way. This switch makes it easier for those tools to parse the list of tasks. (The -t switch has no effect if the -T switch is not specified.) <%= color '-V, --version', :bold %> Shows the current Capistrano version number and exits. <%= color '-v, --verbose', :bold %> Increase the verbosity. You can specify this option up to three times to further increase verbosity. By default, cap will use maximum verbosity, but if you specify an explicit verbosity, that will be used instead. See also -q. <%= color '-X, --skip-system-config', :bold %> By default, cap will look for and (if it exists) load the global system configuration file located in /etc/capistrano.conf. If you don't want cap to load that file, give this option. <%= color '-x, --skip-user-config', :bold %> By default, cap will look for and (if it exists) load the user-specific configuration file located in $HOME/.caprc. If you don't want cap to load that file, give this option. ----------------------------- <%= color('Environment Variables', :bold) %> ----------------------------- <%= color 'HOSTS', :bold %> Execute the tasks against this comma-separated list of hosts. Effectively, this makes the host(s) part of every roles. <%= color 'HOSTFILTER', :bold %> Execute tasks against this comma-separated list of host, but only if the host has the proper role for the task. <%= color 'HOSTROLEFILTER', :bold %> Execute tasks against the hosts in this comma-separated list of roles, but only if the host has the proper role for the task. <%= color 'ROLES', :bold %> Execute tasks against this comma-separated list of roles. Hosts which do not have the right roles will be skipped. capistrano-2.12.0/lib/capistrano/configuration/0000755000004100000410000000000011746743503021561 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/configuration/callbacks.rb0000644000004100000410000001160511746743503024030 0ustar www-datawww-datarequire 'capistrano/callback' module Capistrano class Configuration module Callbacks def self.included(base) #:nodoc: %w(initialize invoke_task_directly).each do |method| base.send :alias_method, "#{method}_without_callbacks", method base.send :alias_method, method, "#{method}_with_callbacks" end end # The hash of callbacks that have been registered for this configuration attr_reader :callbacks def initialize_with_callbacks(*args) #:nodoc: initialize_without_callbacks(*args) @callbacks = {} end def invoke_task_directly_with_callbacks(task) #:nodoc: trigger :before, task result = invoke_task_directly_without_callbacks(task) trigger :after, task return result end # Defines a callback to be invoked before the given task. You must # specify the fully-qualified task name, both for the primary task, and # for the task(s) to be executed before. Alternatively, you can pass a # block to be executed before the given task. # # before "deploy:update_code", :record_difference # before :deploy, "custom:log_deploy" # before :deploy, :this, "then:this", "and:then:this" # before :some_task do # puts "an anonymous hook!" # end # # This just provides a convenient interface to the more general #on method. def before(task_name, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} args << options.merge(:only => task_name) on :before, *args, &block end # Defines a callback to be invoked after the given task. You must # specify the fully-qualified task name, both for the primary task, and # for the task(s) to be executed after. Alternatively, you can pass a # block to be executed after the given task. # # after "deploy:update_code", :log_difference # after :deploy, "custom:announce" # after :deploy, :this, "then:this", "and:then:this" # after :some_task do # puts "an anonymous hook!" # end # # This just provides a convenient interface to the more general #on method. def after(task_name, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} args << options.merge(:only => task_name) on :after, *args, &block end # Defines one or more callbacks to be invoked in response to some event. # Capistrano currently understands the following events: # # * :before, triggered before a task is invoked # * :after, triggered after a task is invoked # * :start, triggered before a top-level task is invoked via the command-line # * :finish, triggered when a top-level task completes # * :load, triggered after all recipes have loaded # * :exit, triggered after all tasks have completed # # Specify the (fully-qualified) task names that you want invoked in # response to the event. Alternatively, you can specify a block to invoke # when the event is triggered. You can also pass a hash of options as the # last parameter, which may include either of two keys: # # * :only, should specify an array of task names. Restricts this callback # so that it will only fire when the event applies to those tasks. # * :except, should specify an array of task names. Restricts this callback # so that it will never fire when the event applies to those tasks. # # Usage: # # on :before, "some:hook", "another:hook", :only => "deploy:update" # on :after, "some:hook", :except => "deploy:create_symlink" # on :before, "global:hook" # on :after, :only => :deploy do # puts "after deploy here" # end def on(event, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} callbacks[event] ||= [] if args.empty? && block.nil? raise ArgumentError, "please specify either a task name or a block to invoke" elsif args.any? && block raise ArgumentError, "please specify only a task name or a block, but not both" elsif block callbacks[event] << ProcCallback.new(block, options) else args.each do |name| callbacks[event] << TaskCallback.new(self, name, options) end end end # Trigger the named event for the named task. All associated callbacks # will be fired, in the order they were defined. def trigger(event, task=nil) pending = Array(callbacks[event]).select { |c| c.applies_to?(task) } if pending.any? msg = "triggering #{event} callbacks" msg << " for `#{task.fully_qualified_name}'" if task logger.trace(msg) pending.each { |callback| callback.call } end end end end end capistrano-2.12.0/lib/capistrano/configuration/loading.rb0000644000004100000410000001760411746743503023533 0ustar www-datawww-datamodule Capistrano class Configuration module Loading def self.included(base) #:nodoc: base.send :alias_method, :initialize_without_loading, :initialize base.send :alias_method, :initialize, :initialize_with_loading base.extend ClassMethods end module ClassMethods # Used by third-party task bundles to identify the capistrano # configuration that is loading them. Its return value is not reliable # in other contexts. If +require_config+ is not false, an exception # will be raised if the current configuration is not set. def instance(require_config=false) config = Thread.current[:capistrano_configuration] if require_config && config.nil? raise LoadError, "Please require this file from within a Capistrano recipe" end config end # Used internally by Capistrano to specify the current configuration # before loading a third-party task bundle. def instance=(config) Thread.current[:capistrano_configuration] = config end # Used internally by Capistrano to track which recipes have been loaded # via require, so that they may be successfully reloaded when require # is called again. def recipes_per_feature @recipes_per_feature ||= {} end # Used internally to determine what the current "feature" being # required is. This is used to track which files load which recipes # via require. def current_feature Thread.current[:capistrano_current_feature] end # Used internally to specify the current file being required, so that # any recipes loaded by that file can be remembered. This allows # recipes loaded via require to be correctly reloaded in different # Configuration instances in the same Ruby instance. def current_feature=(feature) Thread.current[:capistrano_current_feature] = feature end end # The load paths used for locating recipe files. attr_reader :load_paths def initialize_with_loading(*args) #:nodoc: initialize_without_loading(*args) @load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "../recipes"))] @loaded_features = [] end private :initialize_with_loading # Load a configuration file or string into this configuration. # # Usage: # # load("recipe"): # Look for and load the contents of 'recipe.rb' into this # configuration. # # load(:file => "recipe"): # same as above # # load(:string => "set :scm, :subversion"): # Load the given string as a configuration specification. # # load { ... } # Load the block in the context of the configuration. def load(*args, &block) options = args.last.is_a?(Hash) ? args.pop : {} if block raise ArgumentError, "loading a block requires 0 arguments" unless options.empty? && args.empty? load(:proc => block) elsif args.any? args.each { |arg| load options.merge(:file => arg) } elsif options[:file] load_from_file(options[:file], options[:name]) elsif options[:string] remember_load(options) unless options[:reloading] instance_eval(options[:string], options[:name] || "") elsif options[:proc] remember_load(options) unless options[:reloading] instance_eval(&options[:proc]) else raise ArgumentError, "don't know how to load #{options.inspect}" end end # Require another file. This is identical to the standard require method, # with the exception that it sets the receiver as the "current" configuration # so that third-party task bundles can include themselves relative to # that configuration. # # This is a bit more complicated than an initial review would seem to # necessitate, but the use case that complicates things is this: An # advanced user wants to embed capistrano, and needs to instantiate # more than one capistrano configuration at a time. They also want each # configuration to require a third-party capistrano extension. Using a # naive require implementation, this would allow the first configuration # to successfully load the third-party extension, but the require would # fail for the second configuration because the extension has already # been loaded. # # To work around this, we do a few things: # # 1. Each time a 'require' is invoked inside of a capistrano recipe, # we remember the arguments (see "current_feature"). # 2. Each time a 'load' is invoked inside of a capistrano recipe, and # "current_feature" is not nil (meaning we are inside of a pending # require) we remember the options (see "remember_load" and # "recipes_per_feature"). # 3. Each time a 'require' is invoked inside of a capistrano recipe, # we check to see if this particular configuration has ever seen these # arguments to require (see @loaded_features), and if not, we proceed # as if the file had never been required. If the superclass' require # returns false (meaning, potentially, that the file has already been # required), then we look in the recipes_per_feature collection and # load any remembered recipes from there. # # It's kind of a bear, but it works, and works transparently. Note that # a simpler implementation would just muck with $", allowing files to be # required multiple times, but that will cause warnings (and possibly # errors) if the file to be required contains constant definitions and # such, alongside (or instead of) capistrano recipe definitions. def require(*args) #:nodoc: # look to see if this specific configuration instance has ever seen # these arguments to require before if @loaded_features.include?(args) return false end @loaded_features << args begin original_instance, self.class.instance = self.class.instance, self original_feature, self.class.current_feature = self.class.current_feature, args result = super if !result # file has been required previously, load up the remembered recipes list = self.class.recipes_per_feature[args] || [] list.each { |options| load(options.merge(:reloading => true)) } end return result ensure # restore the original, so that require's can be nested self.class.instance = original_instance self.class.current_feature = original_feature end end private # Load a recipe from the named file. If +name+ is given, the file will # be reported using that name. def load_from_file(file, name=nil) file = find_file_in_load_path(file) unless File.file?(file) load :string => File.read(file), :name => name || file end def find_file_in_load_path(file) load_paths.each do |path| ["", ".rb"].each do |ext| name = File.join(path, "#{file}#{ext}") return name if File.file?(name) end end raise LoadError, "no such file to load -- #{file}" end # If a file is being required, the options associated with loading a # recipe are remembered in the recipes_per_feature archive under the # name of the file currently being required. def remember_load(options) if self.class.current_feature list = (self.class.recipes_per_feature[self.class.current_feature] ||= []) list << options end end end end end capistrano-2.12.0/lib/capistrano/configuration/actions/0000755000004100000410000000000011746743503023221 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/configuration/actions/inspect.rb0000644000004100000410000000304511746743503025215 0ustar www-datawww-datarequire 'capistrano/errors' module Capistrano class Configuration module Actions module Inspect # Streams the result of the command from all servers that are the # target of the current task. All these streams will be joined into a # single one, so you can, say, watch 10 log files as though they were # one. Do note that this is quite expensive from a bandwidth # perspective, so use it with care. # # The command is invoked via #invoke_command. # # Usage: # # desc "Run a tail on multiple log files at the same time" # task :tail_fcgi, :roles => :app do # stream "tail -f #{shared_path}/log/fastcgi.crash.log" # end def stream(command, options={}) invoke_command(command, options) do |ch, stream, out| puts out if stream == :out warn "[err :: #{ch[:server]}] #{out}" if stream == :err end end # Executes the given command on the first server targetted by the # current task, collects it's stdout into a string, and returns the # string. The command is invoked via #invoke_command. def capture(command, options={}) output = "" invoke_command(command, options.merge(:once => true)) do |ch, stream, data| case stream when :out then output << data when :err then warn "[err :: #{ch[:server]}] #{data}" end end output end end end end end capistrano-2.12.0/lib/capistrano/configuration/actions/file_transfer.rb0000644000004100000410000000317111746743503026373 0ustar www-datawww-datarequire 'capistrano/transfer' module Capistrano class Configuration module Actions module FileTransfer # Store the given data at the given location on all servers targetted # by the current task. If :mode is specified it is used to # set the mode on the file. def put(data, path, options={}) opts = options.dup upload(StringIO.new(data), path, opts) end # Get file remote_path from FIRST server targeted by # the current task and transfer it to local machine as path. # # get "#{deploy_to}/current/log/production.log", "log/production.log.web" def get(remote_path, path, options={}, &block) download(remote_path, path, options.merge(:once => true), &block) end def upload(from, to, options={}, &block) mode = options.delete(:mode) transfer(:up, from, to, options, &block) if mode mode = mode.is_a?(Numeric) ? mode.to_s(8) : mode.to_s run "chmod #{mode} #{to}", options end end def download(from, to, options={}, &block) transfer(:down, from, to, options, &block) end def transfer(direction, from, to, options={}, &block) if dry_run return logger.debug "transfering: #{[direction, from, to] * ', '}" end execute_on_servers(options) do |servers| targets = servers.map { |s| sessions[s] } Transfer.process(direction, from, to, targets, options.merge(:logger => logger), &block) end end end end end end capistrano-2.12.0/lib/capistrano/configuration/actions/invocation.rb0000644000004100000410000003216111746743503025722 0ustar www-datawww-datarequire 'capistrano/command' module Capistrano class Configuration module Actions module Invocation def self.included(base) #:nodoc: base.extend(ClassMethods) base.send :alias_method, :initialize_without_invocation, :initialize base.send :alias_method, :initialize, :initialize_with_invocation base.default_io_proc = Proc.new do |ch, stream, out| level = stream == :err ? :important : :info ch[:options][:logger].send(level, out, "#{stream} :: #{ch[:server]}") end end module ClassMethods attr_accessor :default_io_proc end def initialize_with_invocation(*args) #:nodoc: initialize_without_invocation(*args) set :default_environment, {} set :default_run_options, {} end # Executes different commands in parallel. This is useful for commands # that need to be different on different hosts, but which could be # otherwise run in parallel. # # The +options+ parameter is currently unused. # # Example: # # task :restart_everything do # parallel do |session| # session.when "in?(:app)", "/path/to/restart/mongrel" # session.when "in?(:web)", "/path/to/restart/apache" # session.when "in?(:db)", "/path/to/restart/mysql" # end # end # # Each command may have its own callback block, for capturing and # responding to output, with semantics identical to #run: # # session.when "in?(:app)", "/path/to/restart/mongrel" do |ch, stream, data| # # ch is the SSH channel for this command, used to send data # # back to the command (e.g. ch.send_data("password\n")) # # stream is either :out or :err, for which stream the data arrived on # # data is a string containing data sent from the remote command # end # # Also, you can specify a fallback command, to use when none of the # conditions match a server: # # session.else "/execute/something/else" # # The string specified as the first argument to +when+ may be any valid # Ruby code. It has access to the following variables and methods: # # * +in?(role)+ returns true if the server participates in the given role # * +server+ is the ServerDefinition object for the server. This can be # used to get the host-name, etc. # * +configuration+ is the current Capistrano::Configuration object, which # you can use to get the value of variables, etc. # # For example: # # session.when "server.host =~ /app/", "/some/command" # session.when "server.host == configuration[:some_var]", "/another/command" # session.when "in?(:web) || in?(:app)", "/more/commands" # # See #run for a description of the valid +options+. def parallel(options={}) raise ArgumentError, "parallel() requires a block" unless block_given? tree = Command::Tree.new(self) { |t| yield t } run_tree(tree, options) end # Invokes the given command. If a +via+ key is given, it will be used # to determine what method to use to invoke the command. It defaults # to :run, but may be :sudo, or any other method that conforms to the # same interface as run and sudo. def invoke_command(cmd, options={}, &block) options = options.dup via = options.delete(:via) || :run send(via, cmd, options, &block) end # Execute the given command on all servers that are the target of the # current task. If a block is given, it is invoked for all output # generated by the command, and should accept three parameters: the SSH # channel (which may be used to send data back to the remote process), # the stream identifier (:err for stderr, and :out for # stdout), and the data that was received. # # The +options+ hash may include any of the following keys: # # * :hosts - this is either a string (for a single target host) or an array # of strings, indicating which hosts the command should run on. By default, # the hosts are determined from the task definition. # * :roles - this is either a string or symbol (for a single target role) or # an array of strings or symbols, indicating which roles the command should # run on. If :hosts is specified, :roles will be ignored. # * :only - specifies a condition limiting which hosts will be selected to # run the command. This should refer to values set in the role definition. # For example, if a role is defined with :primary => true, then you could # select only hosts with :primary true by setting :only => { :primary => true }. # * :except - specifies a condition limiting which hosts will be selected to # run the command. This is the inverse of :only (hosts that do _not_ match # the condition will be selected). # * :on_no_matching_servers - if :continue, will continue to execute tasks if # no matching servers are found for the host criteria. The default is to raise # a NoMatchingServersError exception. # * :once - if true, only the first matching server will be selected. The default # is false (all matching servers will be selected). # * :max_hosts - specifies the maximum number of hosts that should be selected # at a time. If this value is less than the number of hosts that are selected # to run, then the hosts will be run in groups of max_hosts. The default is nil, # which indicates that there is no maximum host limit. Please note this does not # limit the number of SSH channels that can be open, only the number of hosts upon # which this will be called. # * :shell - says which shell should be used to invoke commands. This # defaults to "sh". Setting this to false causes Capistrano to invoke # the commands directly, without wrapping them in a shell invocation. # * :data - if not nil (the default), this should be a string that will # be passed to the command's stdin stream. # * :pty - if true, a pseudo-tty will be allocated for each command. The # default is false. Note that there are benefits and drawbacks both ways. # Empirically, it appears that if a pty is allocated, the SSH server daemon # will _not_ read user shell start-up scripts (e.g. bashrc, etc.). However, # if a pty is _not_ allocated, some commands will refuse to run in # interactive mode and will not prompt for (e.g.) passwords. # * :env - a hash of environment variable mappings that should be made # available to the command. The keys should be environment variable names, # and the values should be their corresponding values. The default is # empty, but may be modified by changing the +default_environment+ # Capistrano variable. # # Note that if you set these keys in the +default_run_options+ Capistrano # variable, they will apply for all invocations of #run, #invoke_command, # and #parallel. def run(cmd, options={}, &block) block ||= self.class.default_io_proc tree = Command::Tree.new(self) { |t| t.else(cmd, &block) } run_tree(tree, options) end # Executes a Capistrano::Command::Tree object. This is not for direct # use, but should instead be called indirectly, via #run or #parallel, # or #invoke_command. def run_tree(tree, options={}) #:nodoc: if tree.branches.empty? && tree.fallback logger.debug "executing #{tree.fallback}" elsif tree.branches.any? logger.debug "executing multiple commands in parallel" tree.each do |branch| logger.trace "-> #{branch}" end else raise ArgumentError, "attempt to execute without specifying a command" end return if dry_run || (debug && continue_execution(tree) == false) options = add_default_command_options(options) tree.each do |branch| if branch.command.include?(sudo) branch.callback = sudo_behavior_callback(branch.callback) end end execute_on_servers(options) do |servers| targets = servers.map { |s| sessions[s] } Command.process(tree, targets, options.merge(:logger => logger)) end end # Returns the command string used by capistrano to invoke a comamnd via # sudo. # # run "#{sudo :as => 'bob'} mkdir /path/to/dir" # # It can also be invoked like #run, but executing the command via sudo. # This assumes that the sudo password (if required) is the same as the # password for logging in to the server. # # sudo "mkdir /path/to/dir" # # Also, this method understands a :sudo configuration variable, # which (if specified) will be used as the full path to the sudo # executable on the remote machine: # # set :sudo, "/opt/local/bin/sudo" # # If you know what you're doing, you can also set :sudo_prompt, # which tells capistrano which prompt sudo should use when asking for # a password. (This is so that capistrano knows what prompt to look for # in the output.) If you set :sudo_prompt to an empty string, Capistrano # will not send a preferred prompt. def sudo(*parameters, &block) options = parameters.last.is_a?(Hash) ? parameters.pop.dup : {} command = parameters.first user = options[:as] && "-u #{options.delete(:as)}" sudo_prompt_option = "-p '#{sudo_prompt}'" unless sudo_prompt.empty? sudo_command = [fetch(:sudo, "sudo"), sudo_prompt_option, user].compact.join(" ") if command command = sudo_command + " " + command run(command, options, &block) else return sudo_command end end # Returns a Proc object that defines the behavior of the sudo # callback. The returned Proc will defer to the +fallback+ argument # (which should also be a Proc) for any output it does not # explicitly handle. def sudo_behavior_callback(fallback) #:nodoc: # in order to prevent _each host_ from prompting when the password # was wrong, let's track which host prompted first and only allow # subsequent prompts from that host. prompt_host = nil Proc.new do |ch, stream, out| if out =~ /^Sorry, try again/ if prompt_host.nil? || prompt_host == ch[:server] prompt_host = ch[:server] logger.important out, "#{stream} :: #{ch[:server]}" reset! :password end end if out =~ /^#{Regexp.escape(sudo_prompt)}/ ch.send_data "#{self[:password]}\n" elsif fallback fallback.call(ch, stream, out) end end end # Merges the various default command options into the options hash and # returns the result. The default command options that are understand # are: # # * :default_environment: If the :env key already exists, the :env # key is merged into default_environment and then added back into # options. # * :default_shell: if the :shell key already exists, it will be used. # Otherwise, if the :default_shell key exists in the configuration, # it will be used. Otherwise, no :shell key is added. def add_default_command_options(options) defaults = self[:default_run_options] options = defaults.merge(options) env = self[:default_environment] env = env.merge(options[:env]) if options[:env] options[:env] = env unless env.empty? shell = options[:shell] || self[:default_shell] options[:shell] = shell unless shell.nil? options end # Returns the prompt text to use with sudo def sudo_prompt fetch(:sudo_prompt, "sudo password: ") end def continue_execution(tree) if tree.branches.length == 1 continue_execution_for_branch(tree.branches.first) else tree.each { |branch| branch.skip! unless continue_execution_for_branch(branch) } tree.any? { |branch| !branch.skip? } end end def continue_execution_for_branch(branch) case Capistrano::CLI.debug_prompt(branch) when "y" true when "n" false when "a" exit(-1) end end end end end end capistrano-2.12.0/lib/capistrano/configuration/alias_task.rb0000644000004100000410000000153311746743503024223 0ustar www-datawww-datamodule Capistrano class Configuration module AliasTask # Attempts to find the task at the given fully-qualified path, and # alias it. If arguments don't have correct task names, an ArgumentError # wil be raised. If no such task exists, a Capistrano::NoSuchTaskError # will be raised. # # Usage: # # alias_task :original_deploy, :deploy # def alias_task(new_name, old_name) if !new_name.respond_to?(:to_sym) or !old_name.respond_to?(:to_sym) raise ArgumentError, "expected a valid task name" end original_task = find_task(old_name) or raise NoSuchTaskError, "the task `#{old_name}' does not exist" task = original_task.dup # Dup. task to avoid modify original task task.name = new_name define_task(task) end end end end capistrano-2.12.0/lib/capistrano/configuration/namespaces.rb0000644000004100000410000001607111746743503024232 0ustar www-datawww-datarequire 'capistrano/task_definition' module Capistrano class Configuration module Namespaces DEFAULT_TASK = :default def self.included(base) #:nodoc: base.send :alias_method, :initialize_without_namespaces, :initialize base.send :alias_method, :initialize, :initialize_with_namespaces end # The name of this namespace. Defaults to +nil+ for the top-level # namespace. attr_reader :name # The parent namespace of this namespace. Returns +nil+ for the top-level # namespace. attr_reader :parent # The hash of tasks defined for this namespace. attr_reader :tasks # The hash of namespaces defined for this namespace. attr_reader :namespaces def initialize_with_namespaces(*args) #:nodoc: @name = @parent = nil initialize_without_namespaces(*args) @tasks = {} @namespaces = {} end private :initialize_with_namespaces # Returns the top-level namespace (the one with no parent). def top return parent.top if parent return self end # Returns the fully-qualified name of this namespace, or nil if the # namespace is at the top-level. def fully_qualified_name return nil if name.nil? [parent.fully_qualified_name, name].compact.join(":") end # Describe the next task to be defined. The given text will be attached to # the next task that is defined and used as its description. def desc(text) @next_description = text end # Returns the value set by the last, pending "desc" call. If +reset+ is # not false, the value will be reset immediately afterwards. def next_description(reset=false) @next_description ensure @next_description = nil if reset end # Open a namespace in which to define new tasks. If the namespace was # defined previously, it will be reopened, otherwise a new namespace # will be created for the given name. def namespace(name, &block) name = name.to_sym raise ArgumentError, "expected a block" unless block_given? namespace_already_defined = namespaces.key?(name) if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined thing = tasks.key?(name) ? "task" : "method" raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name" end namespaces[name] ||= Namespace.new(name, self) namespaces[name].instance_eval(&block) # make sure any open description gets terminated namespaces[name].desc(nil) if !namespace_already_defined metaclass = class << self; self; end metaclass.send(:define_method, name) { namespaces[name] } end end # Describe a new task. If a description is active (see #desc), it is added # to the options under the :desc key. The new task is added to # the namespace. def task(name, options={}, &block) name = name.to_sym raise ArgumentError, "expected a block" unless block_given? task_already_defined = tasks.key?(name) if all_methods.any? { |m| m.to_sym == name } && !task_already_defined thing = namespaces.key?(name) ? "namespace" : "method" raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name" end task = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block) define_task(task) end def define_task(task) tasks[task.name] = task metaclass = class << self; self; end metaclass.send(:define_method, task.name) { execute_task(tasks[task.name]) } end # Find the task with the given name, where name is the fully-qualified # name of the task. This will search into the namespaces and return # the referenced task, or nil if no such task can be found. If the name # refers to a namespace, the task in that namespace named "default" # will be returned instead, if one exists. def find_task(name) parts = name.to_s.split(/:/) tail = parts.pop.to_sym ns = self until parts.empty? next_part = parts.shift ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym] return nil if ns.nil? end if ns.namespaces.key?(tail) ns = ns.namespaces[tail] tail = DEFAULT_TASK end ns.tasks[tail] end # Given a task name, this will search the current namespace, and all # parent namespaces, looking for a task that matches the name, exactly. # It returns the task, if found, or nil, if not. def search_task(name) name = name.to_sym ns = self until ns.nil? return ns.tasks[name] if ns.tasks.key?(name) ns = ns.parent end return nil end # Returns the default task for this namespace. This will be +nil+ if # the namespace is at the top-level, and will otherwise return the # task named "default". If no such task exists, +nil+ will be returned. def default_task return nil if parent.nil? return tasks[DEFAULT_TASK] end # Returns the tasks in this namespace as an array of TaskDefinition # objects. If a non-false parameter is given, all tasks in all # namespaces under this namespace will be returned as well. def task_list(all=false) list = tasks.values namespaces.each { |name,space| list.concat(space.task_list(:all)) } if all list end private def all_methods public_methods.concat(protected_methods).concat(private_methods) end class Namespace def initialize(name, parent) @parent = parent @name = name explicitly_define_clashing_kernel_methods end def role(*args) raise NotImplementedError, "roles cannot be defined in a namespace" end def respond_to?(sym, include_priv=false) super || parent.respond_to?(sym, include_priv) end def method_missing(sym, *args, &block) if parent.respond_to?(sym) parent.send(sym, *args, &block) else super end end include Capistrano::Configuration::AliasTask include Capistrano::Configuration::Namespaces undef :desc, :next_description protected def explicitly_define_clashing_kernel_methods (parent.public_methods & Kernel.methods).each do |m| next if self.method(m).owner == self.class if parent.method(m).owner == parent.class metaclass = class << self; self; end metaclass.send(:define_method, m) {|*args, &block| parent.send(m, *args, &block)} end end end end end end end capistrano-2.12.0/lib/capistrano/configuration/connections.rb0000644000004100000410000002134211746743503024432 0ustar www-datawww-datarequire 'enumerator' require 'net/ssh/gateway' require 'capistrano/ssh' require 'capistrano/errors' module Capistrano class Configuration module Connections def self.included(base) #:nodoc: base.send :alias_method, :initialize_without_connections, :initialize base.send :alias_method, :initialize, :initialize_with_connections end class DefaultConnectionFactory #:nodoc: def initialize(options) @options = options end def connect_to(server) SSH.connect(server, @options) end end class GatewayConnectionFactory #:nodoc: def initialize(gateway, options) @options = options Thread.abort_on_exception = true @gateways = {} if gateway.is_a?(Hash) @options[:logger].debug "Creating multiple gateways using #{gateway.inspect}" if @options[:logger] gateway.each do |gw, hosts| gateway_connection = add_gateway(gw) [*hosts].each do |host| @gateways[:default] ||= gateway_connection @gateways[host] = gateway_connection end end else @options[:logger].debug "Creating gateway using #{[*gateway].join(', ')}" if @options[:logger] @gateways[:default] = add_gateway(gateway) end end def add_gateway(gateway) gateways = [*gateway].collect { |g| ServerDefinition.new(g) } tunnel = SSH.connection_strategy(gateways[0], @options) do |host, user, connect_options| Net::SSH::Gateway.new(host, user, connect_options) end (gateways[1..-1]).inject(tunnel) do |tunnel, destination| @options[:logger].debug "Creating tunnel to #{destination}" if @options[:logger] local_host = ServerDefinition.new("127.0.0.1", :user => destination.user, :port => tunnel.open(destination.host, (destination.port || 22))) SSH.connection_strategy(local_host, @options) do |host, user, connect_options| Net::SSH::Gateway.new(host, user, connect_options) end end end def connect_to(server) @options[:logger].debug "establishing connection to `#{server}' via gateway" if @options[:logger] local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => gateway_for(server).open(server.host, server.port || 22)) session = SSH.connect(local_host, @options) session.xserver = server session end def gateway_for(server) @gateways[server.host] || @gateways[:default] end end # A hash of the SSH sessions that are currently open and available. # Because sessions are constructed lazily, this will only contain # connections to those servers that have been the targets of one or more # executed tasks. Stored on a per-thread basis to improve thread-safety. def sessions Thread.current[:sessions] ||= {} end def initialize_with_connections(*args) #:nodoc: initialize_without_connections(*args) Thread.current[:sessions] = {} Thread.current[:failed_sessions] = [] end # Indicate that the given server could not be connected to. def failed!(server) Thread.current[:failed_sessions] << server end # Query whether previous connection attempts to the given server have # failed. def has_failed?(server) Thread.current[:failed_sessions].include?(server) end # Used to force connections to be made to the current task's servers. # Connections are normally made lazily in Capistrano--you can use this # to force them open before performing some operation that might be # time-sensitive. def connect!(options={}) execute_on_servers(options) { } end # Returns the object responsible for establishing new SSH connections. # The factory will respond to #connect_to, which can be used to # establish connections to servers defined via ServerDefinition objects. def connection_factory @connection_factory ||= begin if exists?(:gateway) logger.debug "establishing connection to gateway `#{fetch(:gateway).inspect}'" GatewayConnectionFactory.new(fetch(:gateway), self) else DefaultConnectionFactory.new(self) end end end # Ensures that there are active sessions for each server in the list. def establish_connections_to(servers) failed_servers = [] # force the connection factory to be instantiated synchronously, # otherwise we wind up with multiple gateway instances, because # each connection is done in parallel. connection_factory threads = Array(servers).map { |server| establish_connection_to(server, failed_servers) } threads.each { |t| t.join } if failed_servers.any? errors = failed_servers.map { |h| "#{h[:server]} (#{h[:error].class}: #{h[:error].message})" } error = ConnectionError.new("connection failed for: #{errors.join(', ')}") error.hosts = failed_servers.map { |h| h[:server] } raise error end end # Destroys sessions for each server in the list. def teardown_connections_to(servers) servers.each do |server| begin sessions.delete(server).close rescue IOError # the TCP connection is already dead end end end # Determines the set of servers within the current task's scope and # establishes connections to them, and then yields that list of # servers. def execute_on_servers(options={}) raise ArgumentError, "expected a block" unless block_given? if task = current_task servers = find_servers_for_task(task, options) if servers.empty? if ENV['HOSTFILTER'] || task.options.merge(options)[:on_no_matching_servers] == :continue logger.info "skipping `#{task.fully_qualified_name}' because no servers matched" return else raise Capistrano::NoMatchingServersError, "`#{task.fully_qualified_name}' is only run for servers matching #{task.options.inspect}, but no servers matched" end end if task.continue_on_error? servers.delete_if { |s| has_failed?(s) } return if servers.empty? end else servers = find_servers(options) if servers.empty? raise Capistrano::NoMatchingServersError, "no servers found to match #{options.inspect}" if options[:on_no_matching_servers] != :continue return end end servers = [servers.first] if options[:once] logger.trace "servers: #{servers.map { |s| s.host }.inspect}" max_hosts = (options[:max_hosts] || (task && task.max_hosts) || servers.size).to_i is_subset = max_hosts < servers.size # establish connections to those servers in groups of max_hosts, as necessary servers.each_slice(max_hosts) do |servers_slice| begin establish_connections_to(servers_slice) rescue ConnectionError => error raise error unless task && task.continue_on_error? error.hosts.each do |h| servers_slice.delete(h) failed!(h) end end begin yield servers_slice rescue RemoteError => error raise error unless task && task.continue_on_error? error.hosts.each { |h| failed!(h) } end # if dealing with a subset (e.g., :max_hosts is less than the # number of servers available) teardown the subset of connections # that were just made, so that we can make room for the next subset. teardown_connections_to(servers_slice) if is_subset end end private # We establish the connection by creating a thread in a new method--this # prevents problems with the thread's scope seeing the wrong 'server' # variable if the thread just happens to take too long to start up. def establish_connection_to(server, failures=nil) current_thread = Thread.current Thread.new { safely_establish_connection_to(server, current_thread, failures) } end def safely_establish_connection_to(server, thread, failures=nil) thread[:sessions] ||= {} thread[:sessions][server] ||= connection_factory.connect_to(server) rescue Exception => err raise unless failures failures << { :server => server, :error => err } end end end end capistrano-2.12.0/lib/capistrano/configuration/execution.rb0000644000004100000410000001131311746743503024110 0ustar www-datawww-datarequire 'capistrano/errors' module Capistrano class Configuration module Execution def self.included(base) #:nodoc: base.send :alias_method, :initialize_without_execution, :initialize base.send :alias_method, :initialize, :initialize_with_execution end # A struct for representing a single instance of an invoked task. TaskCallFrame = Struct.new(:task, :rollback) def initialize_with_execution(*args) #:nodoc: initialize_without_execution(*args) end private :initialize_with_execution # Returns true if there is a transaction currently active. def transaction? !rollback_requests.nil? end # The call stack of the tasks. The currently executing task may inspect # this to see who its caller was. The current task is always the last # element of this stack. def task_call_frames Thread.current[:task_call_frames] ||= [] end # The stack of tasks that have registered rollback handlers within the # current transaction. If this is nil, then there is no transaction # that is currently active. def rollback_requests Thread.current[:rollback_requests] end def rollback_requests=(rollback_requests) Thread.current[:rollback_requests] = rollback_requests end # Invoke a set of tasks in a transaction. If any task fails (raises an # exception), all tasks executed within the transaction are inspected to # see if they have an associated on_rollback hook, and if so, that hook # is called. def transaction raise ArgumentError, "expected a block" unless block_given? raise ScriptError, "transaction must be called from within a task" if task_call_frames.empty? return yield if transaction? logger.info "transaction: start" begin self.rollback_requests = [] yield logger.info "transaction: commit" rescue Object => e rollback! raise ensure self.rollback_requests = nil if Thread.main == Thread.current end end # Specifies an on_rollback hook for the currently executing task. If this # or any subsequent task then fails, and a transaction is active, this # hook will be executed. def on_rollback(&block) if transaction? # don't note a new rollback request if one has already been set rollback_requests << task_call_frames.last unless task_call_frames.last.rollback task_call_frames.last.rollback = block end end # Returns the TaskDefinition object for the currently executing task. # It returns nil if there is no task being executed. def current_task return nil if task_call_frames.empty? task_call_frames.last.task end # Executes the task with the given name, without invoking any associated # callbacks. def execute_task(task) logger.debug "executing `#{task.fully_qualified_name}'" push_task_call_frame(task) invoke_task_directly(task) ensure pop_task_call_frame end # Attempts to locate the task at the given fully-qualified path, and # execute it. If no such task exists, a Capistrano::NoSuchTaskError will # be raised. def find_and_execute_task(path, hooks={}) task = find_task(path) or raise NoSuchTaskError, "the task `#{path}' does not exist" trigger(hooks[:before], task) if hooks[:before] result = execute_task(task) trigger(hooks[:after], task) if hooks[:after] result end protected def rollback! return if Thread.current[:rollback_requests].nil? Thread.current[:rolled_back] = true # throw the task back on the stack so that roles are properly # interpreted in the scope of the task in question. rollback_requests.reverse.each do |frame| begin push_task_call_frame(frame.task) logger.important "rolling back", frame.task.fully_qualified_name frame.rollback.call rescue Object => e logger.info "exception while rolling back: #{e.class}, #{e.message}", frame.task.fully_qualified_name ensure pop_task_call_frame end end end def push_task_call_frame(task) frame = TaskCallFrame.new(task) task_call_frames.push frame end def pop_task_call_frame task_call_frames.pop end # Invokes the task's body directly, without setting up the call frame. def invoke_task_directly(task) task.namespace.instance_eval(&task.body) end end end end capistrano-2.12.0/lib/capistrano/configuration/roles.rb0000644000004100000410000000616111746743503023236 0ustar www-datawww-datarequire 'capistrano/server_definition' require 'capistrano/role' module Capistrano class Configuration module Roles def self.included(base) #:nodoc: base.send :alias_method, :initialize_without_roles, :initialize base.send :alias_method, :initialize, :initialize_with_roles end # The hash of roles defined for this configuration. Each entry in the # hash points to an array of server definitions that belong in that # role. attr_reader :roles def initialize_with_roles(*args) #:nodoc: initialize_without_roles(*args) @roles = Hash.new { |h,k| h[k] = Role.new } end # Define a new role and its associated servers. You must specify at least # one host for each role. Also, you can specify additional information # (in the form of a Hash) which can be used to more uniquely specify the # subset of servers specified by this specific role definition. # # Usage: # # role :db, "db1.example.com", "db2.example.com" # role :db, "master.example.com", :primary => true # role :app, "app1.example.com", "app2.example.com" # # You can also encode the username and port number for each host in the # server string, if needed: # # role :web, "www@web1.example.com" # role :file, "files.example.com:4144" # role :db, "admin@db3.example.com:1234" # # Lastly, username and port number may be passed as options, if that is # preferred; note that the options apply to all servers defined in # that call to "role": # # role :web, "web2", "web3", :user => "www", :port => 2345 def role(which, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} which = which.to_sym # The roles Hash is defined so that unrecognized keys always auto-initialize # to a new Role instance (see the assignment in the initialize_with_roles method, # above). However, we explicitly assign here so that role declarations will # vivify the role object even if there are no server arguments. (Otherwise, # role(:app) won't actually instantiate a Role object for :app.) roles[which] ||= Role.new roles[which].push(block, options) if block_given? args.each { |host| roles[which] << ServerDefinition.new(host, options) } end # An alternative way to associate servers with roles. If you have a server # that participates in multiple roles, this can be a DRYer way to describe # the relationships. Pass the host definition as the first parameter, and # the roles as the remaining parameters: # # server "master.example.com", :web, :app def server(host, *roles) options = roles.last.is_a?(Hash) ? roles.pop : {} raise ArgumentError, "you must associate a server with at least one role" if roles.empty? roles.each { |name| role(name, host, options) } end def role_names_for_host(host) roles.map {|role_name, role| role_name if role.include?(host) }.compact || [] end end end end capistrano-2.12.0/lib/capistrano/configuration/servers.rb0000644000004100000410000001075011746743503023602 0ustar www-datawww-datamodule Capistrano class Configuration module Servers # Identifies all servers that the given task should be executed on. # The options hash accepts the same arguments as #find_servers, and any # preexisting options there will take precedence over the options in # the task. def find_servers_for_task(task, options={}) find_servers(task.options.merge(options)) end # Attempts to find all defined servers that match the given criteria. # The options hash may include a :hosts option (which should specify # an array of host names or ServerDefinition instances), a :roles # option (specifying an array of roles), an :only option (specifying # a hash of key/value pairs that any matching server must match), # an :exception option (like :only, but the inverse), and a # :skip_hostfilter option to ignore the HOSTFILTER environment variable # described below. # # Additionally, if the HOSTS environment variable is set, it will take # precedence over any other options. Similarly, the ROLES environment # variable will take precedence over other options. If both HOSTS and # ROLES are given, HOSTS wins. # # Yet additionally, if the HOSTFILTER environment variable is set, it # will limit the result to hosts found in that (comma-separated) list. # # If the HOSTROLEFILTER environment variable is set, it will limit the # result to hosts found in that (comma-separated) list of roles # # Usage: # # # return all known servers # servers = find_servers # # # find all servers in the app role that are not exempted from # # deployment # servers = find_servers :roles => :app, # :except => { :no_release => true } # # # returns the given hosts, translated to ServerDefinition objects # servers = find_servers :hosts => "jamis@example.host.com" def find_servers(options={}) return [] if options.key?(:hosts) && (options[:hosts].nil? || [] == options[:hosts]) return [] if options.key?(:roles) && (options[:roles].nil? || [] == options[:roles]) hosts = server_list_from(ENV['HOSTS'] || options[:hosts]) if hosts.any? if options[:skip_hostfilter] hosts.uniq else filter_server_list(hosts.uniq) end else roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys) roles = roles & Array(options[:roles]) if preserve_roles && !options[:roles].nil? only = options[:only] || {} except = options[:except] || {} # If we don't have a def for a role it means its bogus, skip it so higher level can handle servers = roles.inject([]) { |list, role| list.concat(self.roles[role] || []) } servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } } servers = servers.reject { |server| except.any? { |key,value| server.options[key] == value } } if options[:skip_hostfilter] servers.uniq else filter_server_list(servers.uniq) end end end protected def filter_server_list(servers) return servers unless ENV['HOSTFILTER'] or ENV['HOSTROLEFILTER'] if ENV['HOSTFILTER'] filters = ENV['HOSTFILTER'].split(/,/) servers.select { |server| filters.include?(server.host) } elsif ENV['HOSTROLEFILTER'] filters = ENV['HOSTROLEFILTER'].split(/,/).map do |role| local_roles = roles[role.to_sym] if local_roles.is_a? Array roles[role.to_sym] else roles[role.to_sym].servers end end.flatten servers.select { |server| filters.include?(server) } end end def server_list_from(hosts) hosts = hosts.split(/,/) if String === hosts hosts = build_list(hosts) hosts.map { |s| String === s ? ServerDefinition.new(s.strip) : s } end def role_list_from(roles) roles = roles.split(/,/) if String === roles roles = build_list(roles) roles.map do |role| role = String === role ? role.strip.to_sym : role role end end def build_list(list) Array(list).map { |item| item.respond_to?(:call) ? item.call : item }.flatten end end end end capistrano-2.12.0/lib/capistrano/configuration/variables.rb0000644000004100000410000000751111746743503024062 0ustar www-datawww-datarequire 'thread' module Capistrano class Configuration module Variables def self.included(base) #:nodoc: %w(initialize respond_to? method_missing).each do |m| base_name = m[/^\w+/] punct = m[/\W+$/] base.send :alias_method, "#{base_name}_without_variables#{punct}", m base.send :alias_method, m, "#{base_name}_with_variables#{punct}" end end # The hash of variables that have been defined in this configuration # instance. attr_reader :variables # Set a variable to the given value. def set(variable, *args, &block) if variable.to_s !~ /^[_a-z]/ raise ArgumentError, "invalid variable `#{variable}' (variables must begin with an underscore, or a lower-case letter)" end if !block_given? && args.empty? || block_given? && !args.empty? raise ArgumentError, "you must specify exactly one of either a value or a block" end if args.length > 1 raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" end value = args.empty? ? block : args.first sym = variable.to_sym protect(sym) { @variables[sym] = value } end alias :[]= :set # Removes any trace of the given variable. def unset(variable) sym = variable.to_sym protect(sym) do @original_procs.delete(sym) @variables.delete(sym) end end # Returns true if the variable has been defined, and false otherwise. def exists?(variable) @variables.key?(variable.to_sym) end # If the variable was originally a proc value, it will be reset to it's # original proc value. Otherwise, this method does nothing. It returns # true if the variable was actually reset. def reset!(variable) sym = variable.to_sym protect(sym) do if @original_procs.key?(sym) @variables[sym] = @original_procs.delete(sym) true else false end end end # Access a named variable. If the value of the variable responds_to? :call, # #call will be invoked (without parameters) and the return value cached # and returned. def fetch(variable, *args) if !args.empty? && block_given? raise ArgumentError, "you must specify either a default value or a block, but not both" end sym = variable.to_sym protect(sym) do if !@variables.key?(sym) return args.first unless args.empty? return yield(variable) if block_given? raise IndexError, "`#{variable}' not found" end if @variables[sym].respond_to?(:call) @original_procs[sym] = @variables[sym] @variables[sym] = @variables[sym].call end end @variables[sym] end def [](variable) fetch(variable, nil) end def initialize_with_variables(*args) #:nodoc: initialize_without_variables(*args) @variables = {} @original_procs = {} @variable_locks = Hash.new { |h,k| h[k] = Mutex.new } set :ssh_options, {} set :logger, logger end private :initialize_with_variables def protect(variable) @variable_locks[variable.to_sym].synchronize { yield } end private :protect def respond_to_with_variables?(sym, include_priv=false) #:nodoc: @variables.has_key?(sym.to_sym) || respond_to_without_variables?(sym, include_priv) end def method_missing_with_variables(sym, *args, &block) #:nodoc: if args.length == 0 && block.nil? && @variables.has_key?(sym) self[sym] else method_missing_without_variables(sym, *args, &block) end end end end end capistrano-2.12.0/lib/capistrano/role.rb0000644000004100000410000000502011746743503020175 0ustar www-datawww-datamodule Capistrano class Role include Enumerable def initialize(*list) @static_servers = [] @dynamic_servers = [] push(*list) end def each(&block) servers.each &block end def push(*list) options = list.last.is_a?(Hash) ? list.pop : {} list.each do |item| if item.respond_to?(:call) @dynamic_servers << DynamicServerList.new(item, options) else @static_servers << self.class.wrap_server(item, options) end end end alias_method :<<, :push def servers @static_servers + dynamic_servers end alias_method :to_ary, :servers def empty? servers.empty? end def clear @dynamic_servers.clear @static_servers.clear end def include?(server) servers.include?(server) end protected # This is the combination of a block, a hash of options, and a cached value. class DynamicServerList def initialize (block, options) @block = block @options = options @cached = [] @is_cached = false end # Convert to a list of ServerDefinitions def to_ary unless @is_cached @cached = Role::wrap_list(@block.call(@options), @options) @is_cached = true end @cached end # Clear the cached value def reset! @cached.clear @is_cached = false end end # Attribute reader for the cached results of executing the blocks in turn def dynamic_servers @dynamic_servers.inject([]) { |list, item| list.concat item } end # Wraps a string in a ServerDefinition, if it isn't already. # This and wrap_list should probably go in ServerDefinition in some form. def self.wrap_server (item, options) item.is_a?(ServerDefinition) ? item : ServerDefinition.new(item, options) end # Turns a list, or something resembling a list, into a properly-formatted # ServerDefinition list. Keep an eye on this one -- it's entirely too # magical for its own good. In particular, if ServerDefinition ever inherits # from Array, this will break. def self.wrap_list (*list) options = list.last.is_a?(Hash) ? list.pop : {} if list.length == 1 if list.first.nil? return [] elsif list.first.is_a?(Array) list = list.first end end options.merge! list.pop if list.last.is_a?(Hash) list.map do |item| self.wrap_server item, options end end end end capistrano-2.12.0/lib/capistrano/configuration.rb0000644000004100000410000000312111746743503022103 0ustar www-datawww-datarequire 'capistrano/logger' require 'capistrano/configuration/alias_task' require 'capistrano/configuration/callbacks' require 'capistrano/configuration/connections' require 'capistrano/configuration/execution' require 'capistrano/configuration/loading' require 'capistrano/configuration/namespaces' require 'capistrano/configuration/roles' require 'capistrano/configuration/servers' require 'capistrano/configuration/variables' require 'capistrano/configuration/actions/file_transfer' require 'capistrano/configuration/actions/inspect' require 'capistrano/configuration/actions/invocation' module Capistrano # Represents a specific Capistrano configuration. A Configuration instance # may be used to load multiple recipe files, define and describe tasks, # define roles, and set configuration variables. class Configuration # The logger instance defined for this configuration. attr_accessor :debug, :logger, :dry_run, :preserve_roles def initialize(options={}) #:nodoc: @debug = false @dry_run = false @preserve_roles = false @logger = Logger.new(options) end # make the DSL easier to read when using lazy evaluation via lambdas alias defer lambda # The includes must come at the bottom, since they may redefine methods # defined in the base class. include AliasTask, Connections, Execution, Loading, Namespaces, Roles, Servers, Variables # Mix in the actions include Actions::FileTransfer, Actions::Inspect, Actions::Invocation # Must mix last, because it hooks into previously defined methods include Callbacks end end capistrano-2.12.0/lib/capistrano/transfer.rb0000644000004100000410000001273611746743503021074 0ustar www-datawww-datarequire 'net/scp' require 'net/sftp' require 'capistrano/processable' module Capistrano class Transfer include Processable def self.process(direction, from, to, sessions, options={}, &block) new(direction, from, to, sessions, options, &block).process! end attr_reader :sessions attr_reader :options attr_reader :callback attr_reader :transport attr_reader :direction attr_reader :from attr_reader :to attr_reader :logger attr_reader :transfers def initialize(direction, from, to, sessions, options={}, &block) @direction = direction @from = from @to = to @sessions = sessions @options = options @callback = block @transport = options.fetch(:via, :sftp) @logger = options.delete(:logger) @session_map = {} prepare_transfers end def process! loop do begin break unless process_iteration { active? } rescue Exception => error if error.respond_to?(:session) handle_error(error) else raise end end end failed = transfers.select { |txfr| txfr[:failed] } if failed.any? hosts = failed.map { |txfr| txfr[:server] } errors = failed.map { |txfr| "#{txfr[:error]} (#{txfr[:error].message})" }.uniq.join(", ") error = TransferError.new("#{operation} via #{transport} failed on #{hosts.join(',')}: #{errors}") error.hosts = hosts logger.important(error.message) if logger raise error end logger.debug "#{transport} #{operation} complete" if logger self end def active? transfers.any? { |transfer| transfer.active? } end def operation "#{direction}load" end def sanitized_from if from.responds_to?(:read) "#<#{from.class}>" else from end end def sanitized_to if to.responds_to?(:read) "#<#{to.class}>" else to end end private def session_map @session_map end def prepare_transfers logger.info "#{transport} #{operation} #{from} -> #{to}" if logger @transfers = sessions.map do |session| session_from = normalize(from, session) session_to = normalize(to, session) session_map[session] = case transport when :sftp prepare_sftp_transfer(session_from, session_to, session) when :scp prepare_scp_transfer(session_from, session_to, session) else raise ArgumentError, "unsupported transport type: #{transport.inspect}" end end end def prepare_scp_transfer(from, to, session) real_callback = callback || Proc.new do |channel, name, sent, total| logger.trace "[#{channel[:host]}] #{name}" if logger && sent == 0 end channel = case direction when :up session.scp.upload(from, to, options, &real_callback) when :down session.scp.download(from, to, options, &real_callback) else raise ArgumentError, "unsupported transfer direction: #{direction.inspect}" end channel[:server] = session.xserver channel[:host] = session.xserver.host return channel end class SFTPTransferWrapper attr_reader :operation def initialize(session, &callback) session.sftp(false).connect do |sftp| @operation = callback.call(sftp) end end def active? @operation.nil? || @operation.active? end def [](key) @operation[key] end def []=(key, value) @operation[key] = value end def abort! @operation.abort! end end def prepare_sftp_transfer(from, to, session) SFTPTransferWrapper.new(session) do |sftp| real_callback = Proc.new do |event, op, *args| if callback callback.call(event, op, *args) elsif event == :open logger.trace "[#{op[:host]}] #{args[0].remote}" elsif event == :finish logger.trace "[#{op[:host]}] done" end end opts = options.dup opts[:properties] = (opts[:properties] || {}).merge( :server => session.xserver, :host => session.xserver.host) case direction when :up sftp.upload(from, to, opts, &real_callback) when :down sftp.download(from, to, opts, &real_callback) else raise ArgumentError, "unsupported transfer direction: #{direction.inspect}" end end end def normalize(argument, session) if argument.is_a?(String) argument.gsub(/\$CAPISTRANO:HOST\$/, session.xserver.host) elsif argument.respond_to?(:read) pos = argument.pos clone = StringIO.new(argument.read) clone.pos = argument.pos = pos clone else argument end end def handle_error(error) raise error if error.message.include?('expected a file to upload') transfer = session_map[error.session] transfer[:error] = error transfer[:failed] = true case transport when :sftp then transfer.abort! when :scp then transfer.close end end end end capistrano-2.12.0/lib/capistrano/errors.rb0000644000004100000410000000102211746743503020546 0ustar www-datawww-datamodule Capistrano Error = Class.new(RuntimeError) CaptureError = Class.new(Capistrano::Error) NoSuchTaskError = Class.new(Capistrano::Error) NoMatchingServersError = Class.new(Capistrano::Error) class RemoteError < Error attr_accessor :hosts end ConnectionError = Class.new(Capistrano::RemoteError) TransferError = Class.new(Capistrano::RemoteError) CommandError = Class.new(Capistrano::RemoteError) LocalArgumentError = Class.new(Capistrano::Error) end capistrano-2.12.0/lib/capistrano/processable.rb0000644000004100000410000000240211746743503021537 0ustar www-datawww-datamodule Capistrano module Processable module SessionAssociation def self.on(exception, session) unless exception.respond_to?(:session) exception.extend(self) exception.session = session end return exception end attr_accessor :session end def process_iteration(wait=nil, &block) ensure_each_session { |session| session.preprocess } return false if block && !block.call(self) readers = sessions.map { |session| session.listeners.keys }.flatten.reject { |io| io.closed? } writers = readers.select { |io| io.respond_to?(:pending_write?) && io.pending_write? } if readers.any? || writers.any? readers, writers, = IO.select(readers, writers, nil, wait) end if readers ensure_each_session do |session| ios = session.listeners.keys session.postprocess(ios & readers, ios & writers) end end true end def ensure_each_session errors = [] sessions.each do |session| begin yield session rescue Exception => error errors << SessionAssociation.on(error, session) end end raise errors.first if errors.any? sessions end end endcapistrano-2.12.0/lib/capistrano/server_definition.rb0000644000004100000410000000254411746743503022762 0ustar www-datawww-datamodule Capistrano class ServerDefinition include Comparable attr_reader :host attr_reader :user attr_reader :port attr_reader :options # The default user name to use when a user name is not explicitly provided def self.default_user ENV['USER'] || ENV['USERNAME'] || "not-specified" end def initialize(string, options={}) @user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3] @options = options.dup user_opt, port_opt = @options.delete(:user), @options.delete(:port) @user ||= user_opt @port ||= port_opt @port = @port.to_i if @port end def <=>(server) [host, port, user] <=> [server.host, server.port, server.user] end # Redefined, so that Array#uniq will work to remove duplicate server # definitions, based solely on their host names. def eql?(server) host == server.host && user == server.user && port == server.port end alias :== :eql? # Redefined, so that Array#uniq will work to remove duplicate server # definitions, based on their connection information. def hash @hash ||= [host, user, port].hash end def to_s @to_s ||= begin s = host s = "#{user}@#{s}" if user s = "#{s}:#{port}" if port && port != 22 s end end end endcapistrano-2.12.0/lib/capistrano/logger.rb0000644000004100000410000000244311746743503020521 0ustar www-datawww-datamodule Capistrano class Logger #:nodoc: attr_accessor :level attr_reader :device IMPORTANT = 0 INFO = 1 DEBUG = 2 TRACE = 3 MAX_LEVEL = 3 def initialize(options={}) output = options[:output] || $stderr if output.respond_to?(:puts) @device = output else @device = File.open(output.to_str, "a") @needs_close = true end @options = options @level = 0 end def close device.close if @needs_close end def log(level, message, line_prefix=nil) if level <= self.level indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)] (RUBY_VERSION >= "1.9" ? message.lines : message).each do |line| if line_prefix device.puts "#{indent} [#{line_prefix}] #{line.strip}\n" else device.puts "#{indent} #{line.strip}\n" end end end end def important(message, line_prefix=nil) log(IMPORTANT, message, line_prefix) end def info(message, line_prefix=nil) log(INFO, message, line_prefix) end def debug(message, line_prefix=nil) log(DEBUG, message, line_prefix) end def trace(message, line_prefix=nil) log(TRACE, message, line_prefix) end end end capistrano-2.12.0/lib/capistrano/task_definition.rb0000644000004100000410000000452111746743503022413 0ustar www-datawww-datarequire 'capistrano/server_definition' module Capistrano class TaskDefinition attr_reader :name, :namespace, :options, :body, :desc, :on_error, :max_hosts def initialize(name, namespace, options={}, &block) @name, @namespace, @options = name, namespace, options @desc = @options.delete(:desc) @on_error = options.delete(:on_error) @max_hosts = options[:max_hosts] && options[:max_hosts].to_i @body = block or raise ArgumentError, "a task requires a block" @servers = nil end # Returns the task's fully-qualified name, including the namespace def fully_qualified_name @fully_qualified_name ||= begin if namespace.default_task == self namespace.fully_qualified_name else [namespace.fully_qualified_name, name].compact.join(":") end end end def name=(value) raise ArgumentError, "expected a valid task name" if !value.respond_to?(:to_sym) @name = value.to_sym end # Returns the description for this task, with newlines collapsed and # whitespace stripped. Returns the empty string if there is no # description for this task. def description(rebuild=false) @description = nil if rebuild @description ||= begin description = @desc || "" indentation = description[/\A\s+/] if indentation reformatted_description = "" description.strip.each_line do |line| line = line.chomp.sub(/^#{indentation}/, "") line = line.gsub(/#{indentation}\s*/, " ") if line[/^\S/] reformatted_description << line << "\n" end description = reformatted_description end description.strip.gsub(/\r\n/, "\n") end end # Returns the first sentence of the full description. If +max_length+ is # given, the result will be truncated if it is longer than +max_length+, # and an ellipsis appended. def brief_description(max_length=nil) brief = description[/^.*?\.(?=\s|$)/] || description if max_length && brief.length > max_length brief = brief[0,max_length-3] + "..." end brief end # Indicates whether the task wants to continue, even if a server has failed # previously def continue_on_error? @on_error == :continue end end end capistrano-2.12.0/lib/capistrano/extensions.rb0000644000004100000410000000327411746743503021444 0ustar www-datawww-datamodule Capistrano class ExtensionProxy #:nodoc: def initialize(config, mod) @config = config extend(mod) end def method_missing(sym, *args, &block) @config.send(sym, *args, &block) end end # Holds the set of registered plugins, keyed by name (where the name is a # symbol). EXTENSIONS = {} # Register the given module as a plugin with the given name. It will henceforth # be available via a proxy object on Configuration instances, accessible by # a method with the given name. def self.plugin(name, mod) name = name.to_sym return false if EXTENSIONS.has_key?(name) methods = Capistrano::Configuration.public_instance_methods + Capistrano::Configuration.protected_instance_methods + Capistrano::Configuration.private_instance_methods if methods.any? { |m| m.to_sym == name } raise Capistrano::Error, "registering a plugin named `#{name}' would shadow a method on Capistrano::Configuration with the same name" end Capistrano::Configuration.class_eval <<-STR, __FILE__, __LINE__+1 def #{name} @__#{name}_proxy ||= Capistrano::ExtensionProxy.new(self, Capistrano::EXTENSIONS[#{name.inspect}]) end STR EXTENSIONS[name] = mod return true end # Unregister the plugin with the given name. def self.remove_plugin(name) name = name.to_sym if EXTENSIONS.delete(name) Capistrano::Configuration.send(:remove_method, name) return true end return false end def self.configuration(*args) #:nodoc: warn "[DEPRECATION] Capistrano.configuration is deprecated. Use Capistrano::Configuration.instance instead" Capistrano::Configuration.instance(*args) end end capistrano-2.12.0/lib/capistrano/shell.rb0000644000004100000410000001765311746743503020362 0ustar www-datawww-datarequire 'thread' require 'capistrano/processable' module Capistrano # The Capistrano::Shell class is the guts of the "shell" task. It implements # an interactive REPL interface that users can employ to execute tasks and # commands. It makes for a GREAT way to monitor systems, and perform quick # maintenance on one or more machines. class Shell include Processable # A Readline replacement for platforms where readline is either # unavailable, or has not been installed. class ReadlineFallback #:nodoc: HISTORY = [] def self.readline(prompt) STDOUT.print(prompt) STDOUT.flush STDIN.gets end end # The configuration instance employed by this shell attr_reader :configuration # Instantiate a new shell and begin executing it immediately. def self.run(config) new(config).run! end # Instantiate a new shell def initialize(config) @configuration = config end # Start the shell running. This method will block until the shell # terminates. def run! setup puts <<-INTRO ==================================================================== Welcome to the interactive Capistrano shell! This is an experimental feature, and is liable to change in future releases. Type 'help' for a summary of how to use the shell. -------------------------------------------------------------------- INTRO loop do break if !read_and_execute end @bgthread.kill end def read_and_execute command = read_line case command when "?", "help" then help when "quit", "exit" then puts "exiting" return false when /^set -(\w)\s*(\S+)/ set_option($1, $2) when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i process_command($1, $2, $3) else raise "eh?" end return true end private # Present the prompt and read a single line from the console. It also # detects ^D and returns "exit" in that case. Adds the input to the # history, unless the input is empty. Loops repeatedly until a non-empty # line is input. def read_line loop do command = reader.readline("cap> ") if command.nil? command = "exit" puts(command) else command.strip! end unless command.empty? reader::HISTORY << command return command end end end # Display a verbose help message. def help puts <<-HELP --- HELP! --------------------------------------------------- "Get me out of this thing. I just want to quit." -> Easy enough. Just type "exit", or "quit". Or press ctrl-D. "I want to execute a command on all servers." -> Just type the command, and press enter. It will be passed, verbatim, to all defined servers. "What if I only want it to execute on a subset of them?" -> No problem, just specify the list of servers, separated by commas, before the command, with the `on' keyword: cap> on app1.foo.com,app2.foo.com echo ping "Nice, but can I specify the servers by role?" -> You sure can. Just use the `with' keyword, followed by the comma-delimited list of role names: cap> with app,db echo ping "Can I execute a Capistrano task from within this shell?" -> Yup. Just prefix the task with an exclamation mark: cap> !deploy HELP end # Determine which servers the given task requires a connection to, and # establish connections to them if necessary. Return the list of # servers (names). def connect(task) servers = configuration.find_servers_for_task(task) needing_connections = servers - configuration.sessions.keys unless needing_connections.empty? puts "[establishing connection(s) to #{needing_connections.join(', ')}]" configuration.establish_connections_to(needing_connections) end servers end # Execute the given command. If the command is prefixed by an exclamation # mark, it is assumed to refer to another capistrano task, which will # be invoked. Otherwise, it is executed as a command on all associated # servers. def exec(command) @mutex.synchronize do if command[0] == ?! exec_tasks(command[1..-1].split) else servers = connect(configuration.current_task) exec_command(command, servers) end end ensure STDOUT.flush end # Given an array of task names, invoke them in sequence. def exec_tasks(list) list.each do |task_name| task = configuration.find_task(task_name) raise Capistrano::NoSuchTaskError, "no such task `#{task_name}'" unless task connect(task) configuration.execute_task(task) end rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error warn "error: #{error.message}" end # Execute a command on the given list of servers. def exec_command(command, servers) command = command.gsub(/\bsudo\b/, "sudo -p '#{configuration.sudo_prompt}'") processor = configuration.sudo_behavior_callback(Configuration.default_io_proc) sessions = servers.map { |server| configuration.sessions[server] } options = configuration.add_default_command_options({}) cmd = Command.new(command, sessions, options.merge(:logger => configuration.logger), &processor) previous = trap("INT") { cmd.stop! } cmd.process! rescue Capistrano::Error => error warn "error: #{error.message}" ensure trap("INT", previous) end # Return the object that will be used to query input from the console. # The returned object will quack (more or less) like Readline. def reader @reader ||= begin require 'readline' Readline rescue LoadError ReadlineFallback end end # Prepare every little thing for the shell. Starts the background # thread and generally gets things ready for the REPL. def setup configuration.logger.level = Capistrano::Logger::INFO @mutex = Mutex.new @bgthread = Thread.new do loop do @mutex.synchronize { process_iteration(0.1) } end end end # Set the given option to +value+. def set_option(opt, value) case opt when "v" then puts "setting log verbosity to #{value.to_i}" configuration.logger.level = value.to_i when "o" then case value when "vi" then puts "using vi edit mode" reader.vi_editing_mode when "emacs" then puts "using emacs edit mode" reader.emacs_editing_mode else puts "unknown -o option #{value.inspect}" end else puts "unknown setting #{opt.inspect}" end end # Process a command. Interprets the scope_type (must be nil, "with", or # "on") and the command. If no command is given, then the scope is made # effective for all subsequent commands. If the scope value is "all", # then the scope is unrestricted. def process_command(scope_type, scope_value, command) env_var = case scope_type when "with" then "ROLES" when "on" then "HOSTS" end old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var if command begin exec(command) ensure ENV[env_var] = old_var if env_var end else puts "scoping #{scope_type} #{scope_value}" end end end # All open sessions, needed to satisfy the Command::Processable include def sessions configuration.sessions.values end end capistrano-2.12.0/lib/capistrano/ssh.rb0000644000004100000410000000773711746743503020052 0ustar www-datawww-datarequire 'net/ssh' module Capistrano # A helper class for dealing with SSH connections. class SSH # Patch an accessor onto an SSH connection so that we can record the server # definition object that defines the connection. This is useful because # the gateway returns connections whose "host" is 127.0.0.1, instead of # the host on the other side of the tunnel. module Server #:nodoc: def self.apply_to(connection, server) connection.extend(Server) connection.xserver = server connection end attr_accessor :xserver end # An abstraction to make it possible to connect to the server via public key # without prompting for the password. If the public key authentication fails # this will fall back to password authentication. # # +server+ must be an instance of ServerDefinition. # # If a block is given, the new session is yielded to it, otherwise the new # session is returned. # # If an :ssh_options key exists in +options+, it is passed to the Net::SSH # constructor. Values in +options+ are then merged into it, and any # connection information in +server+ is added last, so that +server+ info # takes precedence over +options+, which takes precendence over ssh_options. def self.connect(server, options={}) connection_strategy(server, options) do |host, user, connection_options| connection = Net::SSH.start(host, user, connection_options) Server.apply_to(connection, server) end end # Abstracts the logic for establishing an SSH connection (which includes # testing for connection failures and retrying with a password, and so forth, # mostly made complicated because of the fact that some of these variables # might be lazily evaluated and try to do something like prompt the user, # which should only happen when absolutely necessary. # # This will yield the hostname, username, and a hash of connection options # to the given block, which should return a new connection. def self.connection_strategy(server, options={}, &block) methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ] password_value = nil # construct the hash of ssh options that should be passed more-or-less # directly to Net::SSH. This will be the general ssh options, merged with # the server-specific ssh-options. ssh_options = (options[:ssh_options] || {}).merge(server.options[:ssh_options] || {}) # load any SSH configuration files that were specified in the SSH options. This # will load from ~/.ssh/config and /etc/ssh_config by default (see Net::SSH # for details). Merge the explicitly given ssh_options over the top of the info # from the config file. ssh_options = Net::SSH.configuration_for(server.host, ssh_options.fetch(:config, true)).merge(ssh_options) # Once we've loaded the config, we don't need Net::SSH to do it again. ssh_options[:config] = false ssh_options[:verbose] = :debug if options[:verbose] && options[:verbose] > 0 user = server.user || options[:user] || ssh_options[:username] || ssh_options[:user] || ServerDefinition.default_user port = server.port || options[:port] || ssh_options[:port] # the .ssh/config file might have changed the host-name on us host = ssh_options.fetch(:host_name, server.host) ssh_options[:port] = port if port # delete these, since we've determined which username to use by this point ssh_options.delete(:username) ssh_options.delete(:user) begin connection_options = ssh_options.merge( :password => password_value, :auth_methods => ssh_options[:auth_methods] || methods.shift ) yield host, user, connection_options rescue Net::SSH::AuthenticationFailed raise if methods.empty? || ssh_options[:auth_methods] password_value = options[:password] retry end end end end capistrano-2.12.0/lib/capistrano/callback.rb0000644000004100000410000000171611746743503021000 0ustar www-datawww-datamodule Capistrano class Callback attr_reader :source, :options, :only, :except def initialize(source, options={}) @source = source @options = options @only = Array(options[:only]).map { |v| v.to_s } @except = Array(options[:except]).map { |v| v.to_s } end def applies_to?(task) if task && only.any? return only.include?(task.fully_qualified_name) elsif task && except.any? return !except.include?(task.fully_qualified_name) else return true end end end class ProcCallback < Callback def call source.call end end class TaskCallback < Callback attr_reader :config def initialize(config, source, options={}) super(source, options) @config = config end def call config.find_and_execute_task(source) end def applies_to?(task) super && (task.nil? || task.fully_qualified_name != source.to_s) end end endcapistrano-2.12.0/lib/capistrano/recipes/0000755000004100000410000000000011746743503020344 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/recipes/deploy/0000755000004100000410000000000011746743503021640 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/0000755000004100000410000000000011746743503023502 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/copy.rb0000644000004100000410000002705511746743503025012 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/strategy/base' require 'fileutils' require 'tempfile' # Dir.tmpdir module Capistrano module Deploy module Strategy # This class implements the strategy for deployments which work # by preparing the source code locally, compressing it, copying the # file to each target host, and uncompressing it to the deployment # directory. # # By default, the SCM checkout command is used to obtain the local copy # of the source code. If you would rather use the export operation, # you can set the :copy_strategy variable to :export. # # set :copy_strategy, :export # # For even faster deployments, you can set the :copy_cache variable to # true. This will cause deployments to do a new checkout of your # repository to a new directory, and then copy that checkout. Subsequent # deploys will just resync that copy, rather than doing an entirely new # checkout. Additionally, you can specify file patterns to exclude from # the copy when using :copy_cache; just set the :copy_exclude variable # to a file glob (or an array of globs). # # set :copy_cache, true # set :copy_exclude, ".git/*" # # Note that :copy_strategy is ignored when :copy_cache is set. Also, if # you want the copy cache put somewhere specific, you can set the variable # to the path you want, instead of merely 'true': # # set :copy_cache, "/tmp/caches/myapp" # # This deployment strategy also supports a special variable, # :copy_compression, which must be one of :gzip, :bz2, or # :zip, and which specifies how the source should be compressed for # transmission to each host. # # There is a possibility to pass a build command that will get # executed if your code needs to be compiled or something needs to be # done before the code is ready to run. # # set :build_script, "make all" # # Note that if you use :copy_cache, the :build_script is used on the # cache and thus you get faster compilation if your script does not # recompile everything. class Copy < Base # Obtains a copy of the source code locally (via the #command method), # compresses it to a single file, copies that file to all target # servers, and uncompresses it on each of them into the deployment # directory. def deploy! copy_cache ? run_copy_cache_strategy : run_copy_strategy create_revision_file compress_repository distribute! ensure rollback_changes end def build directory execute "running build script on #{directory}" do Dir.chdir(directory) { system(build_script) } end if build_script end def check! super.check do |d| d.local.command(source.local.command) if source.local.command d.local.command(compress(nil, nil).first) d.remote.command(decompress(nil).first) end end # Returns the location of the local copy cache, if the strategy should # use a local cache + copy instead of a new checkout/export every # time. Returns +nil+ unless :copy_cache has been set. If :copy_cache # is +true+, a default cache location will be returned. def copy_cache @copy_cache ||= configuration[:copy_cache] == true ? File.expand_path(configuration[:application], Dir.tmpdir) : File.expand_path(configuration[:copy_cache], Dir.pwd) rescue nil end private def run_copy_cache_strategy copy_repository_to_local_cache build copy_cache copy_cache_to_staging_area end def run_copy_strategy copy_repository_to_server build destination remove_excluded_files if copy_exclude.any? end def execute description, &block logger.debug description handle_system_errors &block end def handle_system_errors &block block.call raise_command_failed if last_command_failed? end def refresh_local_cache execute "refreshing local cache to revision #{revision} at #{copy_cache}" do system(source.sync(revision, copy_cache)) end end def create_local_cache execute "preparing local cache at #{copy_cache}" do system(source.checkout(revision, copy_cache)) end end def raise_command_failed raise Capistrano::Error, "shell command failed with return code #{$?}" end def last_command_failed? $? != 0 end def copy_cache_to_staging_area execute "copying cache to deployment staging area #{destination}" do create_destination Dir.chdir(copy_cache) { copy_files(queue_files) } end end def create_destination FileUtils.mkdir_p(destination) end def copy_files files files.each { |name| process_file(name) } end def process_file name send "copy_#{filetype(name)}", name end def filetype name filetype = File.ftype name filetype = "file" unless ["link", "directory"].include? filetype filetype end def copy_link name FileUtils.ln_s(File.readlink(name), File.join(destination, name)) end def copy_directory name FileUtils.mkdir(File.join(destination, name)) copy_files(queue_files(name)) end def copy_file name FileUtils.ln(name, File.join(destination, name)) end def queue_files directory=nil Dir.glob(pattern_for(directory), File::FNM_DOTMATCH).reject! { |file| excluded_files_contain? file } end def pattern_for directory !directory.nil? ? "#{directory}/*" : "*" end def excluded_files_contain? file copy_exclude.any? { |p| File.fnmatch(p, file) } or [ ".", ".."].include? File.basename(file) end def copy_repository_to_server execute "getting (via #{copy_strategy}) revision #{revision} to #{destination}" do copy_repository_via_strategy end end def copy_repository_via_strategy system(command) end def remove_excluded_files logger.debug "processing exclusions..." copy_exclude.each do |pattern| delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH) # avoid the /.. trap that deletes the parent directories delete_list.delete_if { |dir| dir =~ /\/\.\.$/ } FileUtils.rm_rf(delete_list.compact) end end def create_revision_file File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) } end def compress_repository execute "Compressing #{destination} to #{filename}" do Dir.chdir(copy_dir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) } end end def rollback_changes FileUtils.rm filename rescue nil FileUtils.rm_rf destination rescue nil end def copy_repository_to_local_cache return refresh_local_cache if File.exists?(copy_cache) create_local_cache end def build_script configuration[:build_script] end # Specify patterns to exclude from the copy. This is only valid # when using a local cache. def copy_exclude @copy_exclude ||= Array(configuration.fetch(:copy_exclude, [])) end # Returns the basename of the release_path, which will be used to # name the local copy and archive file. def destination @destination ||= File.join(copy_dir, File.basename(configuration[:release_path])) end # Returns the value of the :copy_strategy variable, defaulting to # :checkout if it has not been set. def copy_strategy @copy_strategy ||= configuration.fetch(:copy_strategy, :checkout) end # Should return the command(s) necessary to obtain the source code # locally. def command @command ||= case copy_strategy when :checkout source.checkout(revision, destination) when :export source.export(revision, destination) end end # Returns the name of the file that the source code will be # compressed to. def filename @filename ||= File.join(copy_dir, "#{File.basename(destination)}.#{compression.extension}") end # The directory to which the copy should be checked out def copy_dir @copy_dir ||= File.expand_path(configuration[:copy_dir] || Dir.tmpdir, Dir.pwd) end # The directory on the remote server to which the archive should be # copied def remote_dir @remote_dir ||= configuration[:copy_remote_dir] || "/tmp" end # The location on the remote server where the file should be # temporarily stored. def remote_filename @remote_filename ||= File.join(remote_dir, File.basename(filename)) end # A struct for representing the specifics of a compression type. # Commands are arrays, where the first element is the utility to be # used to perform the compression or decompression. Compression = Struct.new(:extension, :compress_command, :decompress_command) # The compression method to use, defaults to :gzip. def compression remote_tar = configuration[:copy_remote_tar] || 'tar' local_tar = configuration[:copy_local_tar] || 'tar' type = configuration[:copy_compression] || :gzip case type when :gzip, :gz then Compression.new("tar.gz", [local_tar, 'czf'], [remote_tar, 'xzf']) when :bzip2, :bz2 then Compression.new("tar.bz2", [local_tar, 'cjf'], [remote_tar, 'xjf']) when :zip then Compression.new("zip", %w(zip -qyr), %w(unzip -q)) else raise ArgumentError, "invalid compression type #{type.inspect}" end end # Returns the command necessary to compress the given directory # into the given file. def compress(directory, file) compression.compress_command + [file, directory] end # Returns the command necessary to decompress the given file, # relative to the current working directory. It must also # preserve the directory structure in the file. def decompress(file) compression.decompress_command + [file] end def decompress_remote_file run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}" end # Distributes the file to the remote servers def distribute! upload(filename, remote_filename) decompress_remote_file end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/base.rb0000644000004100000410000000641711746743503024751 0ustar www-datawww-datarequire 'benchmark' require 'capistrano/recipes/deploy/dependencies' module Capistrano module Deploy module Strategy # This class defines the abstract interface for all Capistrano # deployment strategies. Subclasses must implement at least the # #deploy! method. class Base attr_reader :configuration # Instantiates a strategy with a reference to the given configuration. def initialize(config={}) @configuration = config end # Executes the necessary commands to deploy the revision of the source # code identified by the +revision+ variable. Additionally, this # should write the value of the +revision+ variable to a file called # REVISION, in the base of the deployed revision. This file is used by # other tasks, to perform diffs and such. def deploy! raise NotImplementedError, "`deploy!' is not implemented by #{self.class.name}" end # Performs a check on the remote hosts to determine whether everything # is setup such that a deploy could succeed. def check! Dependencies.new(configuration) do |d| d.remote.directory(configuration[:releases_path]).or("`#{configuration[:releases_path]}' does not exist. Please run `cap deploy:setup'.") d.remote.writable(configuration[:deploy_to]).or("You do not have permissions to write to `#{configuration[:deploy_to]}'.") d.remote.writable(configuration[:releases_path]).or("You do not have permissions to write to `#{configuration[:releases_path]}'.") end end protected # This is to allow helper methods like "run" and "put" to be more # easily accessible to strategy implementations. def method_missing(sym, *args, &block) if configuration.respond_to?(sym) configuration.send(sym, *args, &block) else super end end # A wrapper for Kernel#system that logs the command being executed. def system(*args) cmd = args.join(' ') result = nil if RUBY_PLATFORM =~ /win32/ cmd = cmd.split(/\s+/).collect {|w| w.match(/^[\w+]+:\/\//) ? w : w.gsub('/', '\\') }.join(' ') # Split command by spaces, change / by \\ unless element is a some+thing:// cmd.gsub!(/^cd /,'cd /D ') # Replace cd with cd /D cmd.gsub!(/&& cd /,'&& cd /D ') # Replace cd with cd /D logger.trace "executing locally: #{cmd}" elapsed = Benchmark.realtime do result = super(cmd) end else logger.trace "executing locally: #{cmd}" elapsed = Benchmark.realtime do result = super end end logger.trace "command finished in #{(elapsed * 1000).round}ms" result end private def logger @logger ||= configuration[:logger] || Capistrano::Logger.new(:output => STDOUT) end # The revision to deploy. Must return a real revision identifier, # and not a pseudo-id. def revision configuration[:real_revision] end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/remote_cache.rb0000644000004100000410000000361611746743503026453 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/strategy/remote' module Capistrano module Deploy module Strategy # Implements the deployment strategy that keeps a cached checkout of # the source code on each remote server. Each deploy simply updates the # cached checkout, and then does a copy from the cached copy to the # final deployment location. class RemoteCache < Remote # Executes the SCM command for this strategy and writes the REVISION # mark file to each host. def deploy! update_repository_cache copy_repository_cache end def check! super.check do |d| d.remote.command("rsync") unless copy_exclude.empty? d.remote.writable(shared_path) end end private def repository_cache File.join(shared_path, configuration[:repository_cache] || "cached-copy") end def update_repository_cache logger.trace "updating the cached checkout on all servers" command = "if [ -d #{repository_cache} ]; then " + "#{source.sync(revision, repository_cache)}; " + "else #{source.checkout(revision, repository_cache)}; fi" scm_run(command) end def copy_repository_cache logger.trace "copying the cached version to #{configuration[:release_path]}" if copy_exclude.empty? run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}" else exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ') run "rsync -lrpt #{exclusions} #{repository_cache}/ #{configuration[:release_path]} && #{mark}" end end def copy_exclude @copy_exclude ||= Array(configuration.fetch(:copy_exclude, [])) end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/export.rb0000644000004100000410000000072511746743503025354 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/strategy/remote' module Capistrano module Deploy module Strategy # Implements the deployment strategy which does an SCM export on each # target host. class Export < Remote protected # Returns the SCM's export command for the revision to deploy. def command @command ||= source.export(revision, configuration[:release_path]) end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb0000644000004100000410000000063311746743503030340 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/strategy/remote_cache' module Capistrano module Deploy module Strategy class UnsharedRemoteCache < RemoteCache def check! super.check do |d| d.remote.writable(repository_cache) end end private def repository_cache configuration[:repository_cache] end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/remote.rb0000644000004100000410000000330711746743503025325 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/strategy/base' module Capistrano module Deploy module Strategy # An abstract superclass, which forms the base for all deployment # strategies which work by grabbing the code from the repository directly # from remote host. This includes deploying by checkout (the default), # and deploying by export. class Remote < Base # Executes the SCM command for this strategy and writes the REVISION # mark file to each host. def deploy! scm_run "#{command} && #{mark}" end def check! super.check do |d| d.remote.command(source.command) end end protected # Runs the given command, filtering output back through the # #handle_data filter of the SCM implementation. def scm_run(command) run(command) do |ch,stream,text| ch[:state] ||= { :channel => ch } output = source.handle_data(ch[:state], stream, text) ch.send_data(output) if output end end # An abstract method which must be overridden in subclasses, to # return the actual SCM command(s) which must be executed on each # target host in order to perform the deployment. def command raise NotImplementedError, "`command' is not implemented by #{self.class.name}" end # Returns the command which will write the identifier of the # revision being deployed to the REVISION file on each host. def mark "(echo #{revision} > #{configuration[:release_path]}/REVISION)" end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/strategy/checkout.rb0000644000004100000410000000102511746743503025632 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/strategy/remote' module Capistrano module Deploy module Strategy # Implements the deployment strategy which does an SCM checkout on each # target host. This is the default deployment strategy for Capistrano. class Checkout < Remote protected # Returns the SCM's checkout command for the revision to deploy. def command @command ||= source.checkout(revision, configuration[:release_path]) end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/remote_dependency.rb0000644000004100000410000000643111746743503025662 0ustar www-datawww-datarequire 'capistrano/errors' module Capistrano module Deploy class RemoteDependency attr_reader :configuration attr_reader :hosts def initialize(configuration) @configuration = configuration @success = true @hosts = nil end def directory(path, options={}) @message ||= "`#{path}' is not a directory" try("test -d #{path}", options) self end def file(path, options={}) @message ||= "`#{path}' is not a file" try("test -f #{path}", options) self end def writable(path, options={}) @message ||= "`#{path}' is not writable" try("test -w #{path}", options) self end def command(command, options={}) @message ||= "`#{command}' could not be found in the path" try("which #{command}", options) self end def gem(name, version, options={}) @message ||= "gem `#{name}' #{version} could not be found" gem_cmd = configuration.fetch(:gem_command, "gem") try("#{gem_cmd} specification --version '#{version}' #{name} 2>&1 | awk 'BEGIN { s = 0 } /^name:/ { s = 1; exit }; END { if(s == 0) exit 1 }'", options) self end def deb(name, version, options={}) @message ||= "package `#{name}' #{version} could not be found" try("dpkg -s #{name} | grep '^Version: #{version}'", options) self end def rpm(name, version, options={}) @message ||= "package `#{name}' #{version} could not be found" try("rpm -q #{name} | grep '#{version}'", options) self end def match(command, expect, options={}) expect = Regexp.new(Regexp.escape(expect.to_s)) unless expect.is_a?(Regexp) output_per_server = {} try("#{command} ", options) do |ch, stream, out| output_per_server[ch[:server]] ||= '' output_per_server[ch[:server]] += out end # It is possible for some of these commands to return a status != 0 # (for example, rake --version exits with a 1). For this check we # just care if the output matches, so we reset the success flag. @success = true errored_hosts = [] output_per_server.each_pair do |server, output| next if output =~ expect errored_hosts << server end if errored_hosts.any? @hosts = errored_hosts.join(', ') output = output_per_server[errored_hosts.first] @message = "the output #{output.inspect} from #{command.inspect} did not match #{expect.inspect}" @success = false end self end def or(message) @message = message self end def pass? @success end def message s = @message.dup s << " (#{@hosts})" if @hosts s end private def try(command, options) return unless @success # short-circuit evaluation configuration.invoke_command(command, options) do |ch,stream,out| warn "#{ch[:server]}: #{out}" if stream == :err yield ch, stream, out if block_given? end rescue Capistrano::CommandError => e @success = false @hosts = e.hosts.join(', ') end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/assets.rb0000644000004100000410000000452111746743503023471 0ustar www-datawww-dataload 'deploy' unless defined?(_cset) _cset :asset_env, "RAILS_GROUPS=assets" _cset :assets_prefix, "assets" _cset :assets_role, [:web] _cset :normalize_asset_timestamps, false before 'deploy:finalize_update', 'deploy:assets:symlink' after 'deploy:update_code', 'deploy:assets:precompile' namespace :deploy do namespace :assets do desc <<-DESC [internal] This task will set up a symlink to the shared directory \ for the assets directory. Assets are shared across deploys to avoid \ mid-deploy mismatches between old application html asking for assets \ and getting a 404 file not found error. The assets cache is shared \ for efficiency. If you cutomize the assets path prefix, override the \ :assets_prefix variable to match. DESC task :symlink, :roles => assets_role, :except => { :no_release => true } do run <<-CMD rm -rf #{latest_release}/public/#{assets_prefix} && mkdir -p #{latest_release}/public && mkdir -p #{shared_path}/assets && ln -s #{shared_path}/assets #{latest_release}/public/#{assets_prefix} CMD end desc <<-DESC Run the asset precompilation rake task. You can specify the full path \ to the rake executable by setting the rake variable. You can also \ specify additional environment variables to pass to rake via the \ asset_env variable. The defaults are: set :rake, "rake" set :rails_env, "production" set :asset_env, "RAILS_GROUPS=assets" DESC task :precompile, :roles => assets_role, :except => { :no_release => true } do run "cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile" end desc <<-DESC Run the asset clean rake task. Use with caution, this will delete \ all of your compiled assets. You can specify the full path \ to the rake executable by setting the rake variable. You can also \ specify additional environment variables to pass to rake via the \ asset_env variable. The defaults are: set :rake, "rake" set :rails_env, "production" set :asset_env, "RAILS_GROUPS=assets" DESC task :clean, :roles => assets_role, :except => { :no_release => true } do run "cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:clean" end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/dependencies.rb0000644000004100000410000000147011746743503024615 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/local_dependency' require 'capistrano/recipes/deploy/remote_dependency' module Capistrano module Deploy class Dependencies include Enumerable attr_reader :configuration def initialize(configuration) @configuration = configuration @dependencies = [] yield self if block_given? end def check yield self self end def remote dep = RemoteDependency.new(configuration) @dependencies << dep dep end def local dep = LocalDependency.new(configuration) @dependencies << dep dep end def each @dependencies.each { |d| yield d } self end def pass? all? { |d| d.pass? } end end end endcapistrano-2.12.0/lib/capistrano/recipes/deploy/strategy.rb0000644000004100000410000000116311746743503024030 0ustar www-datawww-datamodule Capistrano module Deploy module Strategy def self.new(strategy, config={}) strategy_file = "capistrano/recipes/deploy/strategy/#{strategy}" require(strategy_file) strategy_const = strategy.to_s.capitalize.gsub(/_(.)/) { $1.upcase } if const_defined?(strategy_const) const_get(strategy_const).new(config) else raise Capistrano::Error, "could not find `#{name}::#{strategy_const}' in `#{strategy_file}'" end rescue LoadError raise Capistrano::Error, "could not find any strategy named `#{strategy}'" end end end endcapistrano-2.12.0/lib/capistrano/recipes/deploy/scm/0000755000004100000410000000000011746743503022422 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/recipes/deploy/scm/base.rb0000644000004100000410000001741711746743503023673 0ustar www-datawww-datamodule Capistrano module Deploy module SCM # The ancestor class for all Capistrano SCM implementations. It provides # minimal infrastructure for subclasses to build upon and override. # # Note that subclasses that implement this abstract class only return # the commands that need to be executed--they do not execute the commands # themselves. In this way, the deployment method may execute the commands # either locally or remotely, as necessary. class Base class << self # If no parameters are given, it returns the current configured # name of the command-line utility of this SCM. If a parameter is # given, the defeault command is set to that value. def default_command(value=nil) if value @default_command = value else @default_command end end end # Wraps an SCM instance and forces all messages sent to it to be # relayed to the underlying SCM instance, in "local" mode. See # Base#local. class LocalProxy def initialize(scm) @scm = scm end def method_missing(sym, *args, &block) @scm.local { return @scm.send(sym, *args, &block) } end end # The options available for this SCM instance to reference. Should be # treated like a hash. attr_reader :configuration # Creates a new SCM instance with the given configuration options. def initialize(configuration={}) @configuration = configuration end # Returns a proxy that wraps the SCM instance and forces it to operate # in "local" mode, which changes how variables are looked up in the # configuration. Normally, if the value of a variable "foo" is needed, # it is queried for in the configuration as "foo". However, in "local" # mode, first "local_foo" would be looked for, and only if it is not # found would "foo" be used. This allows for both (e.g.) "scm_command" # and "local_scm_command" to be set, if the two differ. # # Alternatively, it may be called with a block, and for the duration of # the block, all requests on this configuration object will be # considered local. def local if block_given? begin saved, @local_mode = @local_mode, true yield ensure @local_mode = saved end else LocalProxy.new(self) end end # Returns true if running in "local" mode. See #local. def local? @local_mode end # Returns the string used to identify the latest revision in the # repository. This will be passed as the "revision" parameter of # the methods below. def head raise NotImplementedError, "`head' is not implemented by #{self.class.name}" end # Checkout a copy of the repository, at the given +revision+, to the # given +destination+. The checkout is suitable for doing development # work in, e.g. allowing subsequent commits and updates. def checkout(revision, destination) raise NotImplementedError, "`checkout' is not implemented by #{self.class.name}" end # Resynchronize the working copy in +destination+ to the specified # +revision+. def sync(revision, destination) raise NotImplementedError, "`sync' is not implemented by #{self.class.name}" end # Compute the difference between the two revisions, +from+ and +to+. def diff(from, to=nil) raise NotImplementedError, "`diff' is not implemented by #{self.class.name}" end # Return a log of all changes between the two specified revisions, # +from+ and +to+, inclusive. def log(from, to=nil) raise NotImplementedError, "`log' is not implemented by #{self.class.name}" end # If the given revision represents a "real" revision, this should # simply return the revision value. If it represends a pseudo-revision # (like Subversions "HEAD" identifier), it should yield a string # containing the commands that, when executed will return a string # that this method can then extract the real revision from. def query_revision(revision) raise NotImplementedError, "`query_revision' is not implemented by #{self.class.name}" end # Returns the revision number immediately following revision, if at # all possible. A block should always be passed to this method, which # accepts a command to invoke and returns the result, although a # particular SCM's implementation is not required to invoke the block. # # By default, this method simply returns the revision itself. If a # particular SCM is able to determine a subsequent revision given a # revision identifier, it should override this method. def next_revision(revision) revision end # Should analyze the given text and determine whether or not a # response is expected, and if so, return the appropriate response. # If no response is expected, return nil. The +state+ parameter is a # hash that may be used to preserve state between calls. This method # is used to define how Capistrano should respond to common prompts # and messages from the SCM, like password prompts and such. By # default, the output is simply displayed. def handle_data(state, stream, text) logger.info "[#{stream}] #{text}" nil end # Returns the name of the command-line utility for this SCM. It first # looks at the :scm_command variable, and if it does not exist, it # then falls back to whatever was defined by +default_command+. # # If scm_command is set to :default, the default_command will be # returned. def command command = variable(:scm_command) command = nil if command == :default command || default_command end # A helper method that can be used to define SCM commands naturally. # It returns a single string with all arguments joined by spaces, # with the scm command prefixed onto it. def scm(*args) [command, *args].compact.join(" ") end private # A helper for accessing variable values, which takes into # consideration the current mode ("normal" vs. "local"). def variable(name, default = nil) if local? && configuration.exists?("local_#{name}".to_sym) return configuration["local_#{name}".to_sym].nil? ? default : configuration["local_#{name}".to_sym] else configuration[name].nil? ? default : configuration[name] end end # A reference to a Logger instance that the SCM can use to log # activity. def logger @logger ||= variable(:logger) || Capistrano::Logger.new(:output => STDOUT) end # A helper for accessing the default command name for this SCM. It # simply delegates to the class' +default_command+ method. def default_command self.class.default_command end # A convenience method for accessing the declared repository value. def repository variable(:repository) end def arguments(command = :all) value = variable(:scm_arguments) if value.is_a?(Hash) value = value[command] end value end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/bzr.rb0000644000004100000410000000550311746743503023547 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the Bazaar-NG revision # control system (http://bazaar-vcs.org/). class Bzr < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "bzr" # Bazaar-NG doesn't support any pseudo-id's, so we'll use the convention # in this adapter that the :head symbol means the most recently # committed revision. def head :head end # Returns the command that will check out the given revision to the # given destination. def checkout(revision, destination) scm :checkout, "--lightweight", revswitch(revision), repository, destination end # The bzr 'update' command does not support updating to a specific # revision, so this just does update, followed by revert (unless # updating to head). def sync(revision, destination) commands = [scm(:update, destination)] commands << [scm(:revert, revswitch(revision), destination)] if revision != head commands.join(" && ") end # The bzr 'export' does an export similar to other SCM systems def export(revision, destination) scm :export, revswitch(revision), destination, repository end # The bzr "diff" command doesn't accept a repository argument, so it # must be run from within a working tree. def diff(from, to=nil) switch = "-r#{from}" switch << "..#{to}" if to scm :diff, switch end # Returns a log of changes between the two revisions (inclusive). def log(from, to=nil) scm :log, "--short", "-r#{from}..#{to}", repository end # Attempts to translate the given revision identifier to a "real" # revision. If the identifier is :head, the "bzr revno" command will # be yielded, and the block must execute the command and return the # output. The revision will be extracted from the output and returned. # If the 'revision' argument, on the other hand, is not :head, it is # simply returned. def query_revision(revision) return revision unless :head == revision command = scm('revno', repository) result = yield(command) end # Increments the given revision number and returns it. def next_revision(revision) revision.to_i + 1 end private def revswitch(revision) if revision == :head || revision.nil? nil else "-r #{revision}".chomp end end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/none.rb0000644000004100000410000000266311746743503023715 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' module Capistrano module Deploy module SCM # A trivial SCM wrapper for representing the current working directory # as a repository. Obviously, not all operations are available for this # SCM, but it works sufficiently for use with the "copy" deployment # strategy. # # Use of this module is _not_ recommended; in general, it is good # practice to use some kind of source code management even for anything # you are wanting to deploy. However, this module is provided in # acknowledgement of the cases where trivial deployment of your current # working directory is desired. # # set :repository, "." # set :scm, :none # set :deploy_via, :copy class None < Base # No versioning, thus, no head. Returns the empty string. def head "" end # Simply does a copy from the :repository directory to the # :destination directory. def checkout(revision, destination) !Capistrano::Deploy::LocalDependency.on_windows? ? "cp -R #{repository} #{destination}" : "xcopy #{repository} \"#{destination}\" /S/I/Y/Q/E" end alias_method :export, :checkout # No versioning, so this just returns the argument, with no # modification. def query_revision(revision) revision end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/accurev.rb0000644000004100000410000001452111746743503024402 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' require 'rexml/xpath' require 'rexml/document' module Capistrano module Deploy module SCM # Accurev bridge for use by Capistrano. This implementation does not # implement all features of a Capistrano SCM module. The ones that are # left out are either exceedingly difficult to implement with Accurev # or are considered bad form. # # When using this module in a project, the following variables are used: # * :repository - This should match the depot that code lives in. If your code # exists in a subdirectory, you can append the path depot. # eg. foo-depot/bar_dir # * :stream - The stream in the depot that code should be pulled from. If # left blank, the depot stream will be used # * :revision - Should be in the form 'stream/transaction'. class Accurev < Base include REXML default_command 'accurev' # Defines pseudo-revision value for the most recent changes to be deployed. def head "#{stream}/highest" end # Given an Accurev revision identifier, this method returns an identifier that # can be used for later SCM calls. This returned identifier will not # change as a result of further SCM activity. def query_revision(revision) internal_revision = InternalRevision.parse(revision) return revision unless internal_revision.psuedo_revision? logger.debug("Querying for real revision for #{internal_revision}") rev_stream = internal_revision.stream logger.debug("Determining what type of stream #{rev_stream} is...") stream_xml = yield show_streams_for(rev_stream) stream_doc = Document.new(stream_xml) type = XPath.first(stream_doc, '//streams/stream/@type').value case type when 'snapshot' InternalRevision.new(rev_stream, 'highest').to_s else logger.debug("Getting latest transaction id in #{rev_stream}") # Doing another yield for a second Accurev call. Hopefully this is ok. hist_xml = yield scm(:hist, '-ftx', '-s', rev_stream, '-t', 'now.1') hist_doc = Document.new(hist_xml) transaction_id = XPath.first(hist_doc, '//AcResponse/transaction/@id').value InternalRevision.new(stream, transaction_id).to_s end end # Pops a copy of the code for the specified Accurev revision identifier. # The revision identifier is represented as a stream & transaction ID combo. # Accurev can only pop a particular transaction if a stream is created on the server # with a time basis of that transaction id. Therefore, we will create a stream with # the required criteria and pop that. def export(revision_id, destination) revision = InternalRevision.parse(revision_id) logger.debug("Exporting #{revision.stream}/#{revision.transaction_id} to #{destination}") commands = [ change_or_create_stream("#{revision.stream}-capistrano-deploy", revision), "mkdir -p #{destination}", scm_quiet(:pop, "-Rv #{stream}", "-L #{destination}", "'/./#{subdir}'") ] if subdir commands.push( "mv #{destination}/#{subdir}/* #{destination}", "rm -rf #{File.join(destination, subdir)}" ) end commands.join(' && ') end # Returns the command needed to show the changes that exist between the two revisions. def log(from, to=head) logger.info("Getting transactions between #{from} and #{to}") from_rev = InternalRevision.parse(from) to_rev = InternalRevision.parse(to) [ scm(:hist, '-s', from_rev.stream, '-t', "#{to_rev.transaction_id}-#{from_rev.transaction_id}"), "sed -e '/transaction #{from_rev.transaction_id}/ { Q }'" ].join(' | ') end # Returns the command needed to show the diff between what is deployed and what is # pending. Because Accurev can not do this task without creating some streams, # two time basis streams will be created for the purposes of doing the diff. def diff(from, to=head) from = InternalRevision.parse(from) to = InternalRevision.parse(to) from_stream = "#{from.stream}-capistrano-diff-from" to_stream = "#{to.stream}-capistrano-diff-to" [ change_or_create_stream(from_stream, from), change_or_create_stream(to_stream, to), scm(:diff, '-v', from_stream, '-V', to_stream, '-a') ].join(' && ') end private def depot repository.split('/')[0] end def stream variable(:stream) || depot end def subdir repository.split('/')[1..-1].join('/') unless repository.index('/').nil? end def change_or_create_stream(name, revision) [ scm_quiet(:mkstream, '-b', revision.stream, '-s', name, '-t', revision.transaction_id), scm_quiet(:chstream, '-b', revision.stream, '-s', name, '-t', revision.transaction_id) ].join('; ') end def show_streams_for(stream) scm :show, '-fx', '-s', stream, :streams end def scm_quiet(*args) scm(*args) + (variable(:scm_verbose) ? '' : '&> /dev/null') end class InternalRevision attr_reader :stream, :transaction_id def self.parse(string) match = /([^\/]+)(\/(.+)){0,1}/.match(string) raise "Unrecognized revision identifier: #{string}" unless match stream = match[1] transaction_id = match[3] || 'highest' InternalRevision.new(stream, transaction_id) end def initialize(stream, transaction_id) @stream = stream @transaction_id = transaction_id end def psuedo_revision? @transaction_id == 'highest' end def to_s "#{stream}/#{transaction_id}" end def ==(other) (stream == other.stream) && (transaction_id == other.transaction_id) end end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/git.rb0000644000004100000410000002652511746743503023544 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' module Capistrano module Deploy module SCM # An SCM module for using Git as your source control tool with Capistrano # 2.0. If you are using Capistrano 1.x, use this plugin instead: # # http://scie.nti.st/2007/3/16/capistrano-with-git-shared-repository # # Assumes you are using a shared Git repository. # # Parts of this plugin borrowed from Scott Chacon's version, which I # found on the Capistrano mailing list but failed to be able to get # working. # # FEATURES: # # * Very simple, only requiring 2 lines in your deploy.rb. # * Can deploy different branches, tags, or any SHA1 easily. # * Supports prompting for password / passphrase upon checkout. # (I am amazed at how some plugins don't do this) # * Supports :scm_command, :scm_password, :scm_passphrase Capistrano # directives. # # CONFIGURATION # ------------- # # Use this plugin by adding the following line in your config/deploy.rb: # # set :scm, :git # # Set :repository to the path of your Git repo: # # set :repository, "someuser@somehost:/home/myproject" # # The above two options are required to be set, the ones below are # optional. # # You may set :branch, which is the reference to the branch, tag, # or any SHA1 you are deploying, for example: # # set :branch, "master" # # Otherwise, HEAD is assumed. I strongly suggest you set this. HEAD is # not always the best assumption. # # You may also set :remote, which will be used as a name for remote # tracking of repositories. This option is intended for use with the # :remote_cache strategy in a distributed git environment. # # For example in the projects config/deploy.rb: # # set :repository, "#{scm_user}@somehost:~/projects/project.git" # set :remote, "#{scm_user}" # # Then each person with deploy priveledges can add the following to their # local ~/.caprc file: # # set :scm_user, 'someuser' # # Now any time a person deploys the project, their repository will be # setup as a remote git repository within the cached repository. # # The :scm_command configuration variable, if specified, will # be used as the full path to the git executable on the *remote* machine: # # set :scm_command, "/opt/local/bin/git" # # For compatibility with deploy scripts that may have used the 1.x # version of this plugin before upgrading, :git is still # recognized as an alias for :scm_command. # # Set :scm_password to the password needed to clone your repo # if you don't have password-less (public key) entry: # # set :scm_password, "my_secret' # # Otherwise, you will be prompted for a password. # # :scm_passphrase is also supported. # # The remote cache strategy is also supported. # # set :repository_cache, "git_master" # set :deploy_via, :remote_cache # # For faster clone, you can also use shallow cloning. This will set the # '--depth' flag using the depth specified. This *cannot* be used # together with the :remote_cache strategy # # set :git_shallow_clone, 1 # # For those that don't like to leave your entire repository on # your production server you can: # # set :deploy_via, :export # # To deploy from a local repository: # # set :repository, "file://." # set :deploy_via, :copy # # AUTHORS # ------- # # Garry Dolley http://scie.nti.st # Contributions by Geoffrey Grosenbach http://topfunky.com # Scott Chacon http://jointheconversation.org # Alex Arnell http://twologic.com # and Phillip Goldenburg class Git < Base # Sets the default command name for this SCM on your *local* machine. # Users may override this by setting the :scm_command variable. default_command "git" # When referencing "head", use the branch we want to deploy or, by # default, Git's reference of HEAD (the latest changeset in the default # branch, usually called "master"). def head variable(:branch) || 'HEAD' end def origin variable(:remote) || 'origin' end # Performs a clone on the remote machine, then checkout on the branch # you want to deploy. def checkout(revision, destination) git = command remote = origin args = [] args << "-o #{remote}" unless remote == 'origin' if depth = variable(:git_shallow_clone) args << "--depth #{depth}" end execute = [] execute << "#{git} clone #{verbose} #{args.join(' ')} #{variable(:repository)} #{destination}" # checkout into a local branch rather than a detached HEAD execute << "cd #{destination} && #{git} checkout #{verbose} -b deploy #{revision}" if variable(:git_enable_submodules) execute << "#{git} submodule #{verbose} init" execute << "#{git} submodule #{verbose} sync" if false == variable(:git_submodules_recursive) execute << "#{git} submodule #{verbose} update --init" else execute << %Q(export GIT_RECURSIVE=$([ ! "`#{git} --version`" \\< "git version 1.6.5" ] && echo --recursive)) execute << "#{git} submodule #{verbose} update --init $GIT_RECURSIVE" end end execute.compact.join(" && ").gsub(/\s+/, ' ') end # An expensive export. Performs a checkout as above, then # removes the repo. def export(revision, destination) checkout(revision, destination) << " && rm -Rf #{destination}/.git" end # Merges the changes to 'head' since the last fetch, for remote_cache # deployment strategy def sync(revision, destination) git = command remote = origin execute = [] execute << "cd #{destination}" # Use git-config to setup a remote tracking branches. Could use # git-remote but it complains when a remote of the same name already # exists, git-config will just silenty overwrite the setting every # time. This could cause wierd-ness in the remote cache if the url # changes between calls, but as long as the repositories are all # based from each other it should still work fine. if remote != 'origin' execute << "#{git} config remote.#{remote}.url #{variable(:repository)}" execute << "#{git} config remote.#{remote}.fetch +refs/heads/*:refs/remotes/#{remote}/*" end # since we're in a local branch already, just reset to specified revision rather than merge execute << "#{git} fetch #{verbose} #{remote} && #{git} fetch --tags #{verbose} #{remote} && #{git} reset #{verbose} --hard #{revision}" if variable(:git_enable_submodules) execute << "#{git} submodule #{verbose} init" execute << "for mod in `#{git} submodule status | awk '{ print $2 }'`; do #{git} config -f .git/config submodule.${mod}.url `#{git} config -f .gitmodules --get submodule.${mod}.url` && echo Synced $mod; done" execute << "#{git} submodule #{verbose} sync" if false == variable(:git_submodules_recursive) execute << "#{git} submodule #{verbose} update --init" else execute << %Q(export GIT_RECURSIVE=$([ ! "`#{git} --version`" \\< "git version 1.6.5" ] && echo --recursive)) execute << "#{git} submodule #{verbose} update --init $GIT_RECURSIVE" end end # Make sure there's nothing else lying around in the repository (for # example, a submodule that has subsequently been removed). execute << "#{git} clean #{verbose} -d -x -f" execute.join(" && ") end # Returns a string of diffs between two revisions def diff(from, to=nil) return scm :diff, from unless to scm :diff, "#{from}..#{to}" end # Returns a log of changes between the two revisions (inclusive). def log(from, to=nil) scm :log, "#{from}..#{to}" end # Getting the actual commit id, in case we were passed a tag # or partial sha or something - it will return the sha if you pass a sha, too def query_revision(revision) raise ArgumentError, "Deploying remote branches is no longer supported. Specify the remote branch as a local branch for the git repository you're deploying from (ie: '#{revision.gsub('origin/', '')}' rather than '#{revision}')." if revision =~ /^origin\// return revision if revision =~ /^[0-9a-f]{40}$/ command = scm('ls-remote', repository, revision) result = yield(command) revdata = result.split(/[\t\n]/) newrev = nil revdata.each_slice(2) do |refs| rev, ref = *refs if ref.sub(/refs\/.*?\//, '').strip == revision.to_s newrev = rev break end end return newrev if newrev =~ /^[0-9a-f]{40}$/ # If sha is not found on remote, try expanding from local repository command = scm('rev-parse --revs-only', revision) newrev = yield(command).to_s.strip raise "Unable to resolve revision for '#{revision}' on repository '#{repository}'." unless newrev =~ /^[0-9a-f]{40}$/ return newrev end def command # For backwards compatibility with 1.x version of this module variable(:git) || super end # Determines what the response should be for a particular bit of text # from the SCM. Password prompts, connection requests, passphrases, # etc. are handled here. def handle_data(state, stream, text) host = state[:channel][:host] logger.info "[#{host} :: #{stream}] #{text}" case text when /\bpassword.*:/i # git is prompting for a password unless pass = variable(:scm_password) pass = Capistrano::CLI.password_prompt end "#{pass}\n" when %r{\(yes/no\)} # git is asking whether or not to connect "yes\n" when /passphrase/i # git is asking for the passphrase for the user's key unless pass = variable(:scm_passphrase) pass = Capistrano::CLI.password_prompt end "#{pass}\n" when /accept \(t\)emporarily/ # git is asking whether to accept the certificate "t\n" end end private # If verbose output is requested, return nil, otherwise return the # command-line switch for "quiet" ("-q"). def verbose variable(:scm_verbose) ? nil : "-q" end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/perforce.rb0000644000004100000410000001211111746743503024550 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' # Notes: # no global verbose flag for scm_verbose # sync, checkout and export are just sync in p4 # module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the Perforce revision # control system (http://www.perforce.com). class Perforce < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "p4" # Perforce understands '#head' to refer to the latest revision in the # depot. def head 'head' end # Returns the command that will sync the given revision to the given # destination directory. The perforce client has a fixed destination so # the files must be copied from there to their intended resting place. def checkout(revision, destination) p4_sync(revision, destination, p4sync_flags) end # Returns the command that will sync the given revision to the given # destination directory. The perforce client has a fixed destination so # the files must be copied from there to their intended resting place. def sync(revision, destination) p4_sync(revision, destination, p4sync_flags) end # Returns the command that will sync the given revision to the given # destination directory. The perforce client has a fixed destination so # the files must be copied from there to their intended resting place. def export(revision, destination) p4_sync(revision, destination, p4sync_flags) end # Returns the command that will do an "p4 diff2" for the two revisions. def diff(from, to=head) scm authentication, :diff2, "-u -db", "//#{p4client}/...#{rev_no(from)}", "//#{p4client}/...#{rev_no(to)}" end # Returns a "p4 changes" command for the two revisions. def log(from=1, to=head) scm authentication, :changes, "-s submitted", "//#{p4client}/...#{rev_no(from)},#{rev_no(to)}" end def query_revision(revision) return revision if revision.to_s =~ /^\d+$/ command = scm(authentication, :changes, "-s submitted", "-m 1", "//#{p4client}/...#{rev_no(revision)}") yield(command)[/Change (\d+) on/, 1] end # Increments the given revision number and returns it. def next_revision(revision) revision.to_i + 1 end # Determines what the response should be for a particular bit of text # from the SCM. Password prompts, connection requests, passphrases, # etc. are handled here. def handle_data(state, stream, text) case text when /\(P4PASSWD\) invalid or unset\./i raise Capistrano::Error, "scm_password (or p4passwd) is incorrect or unset" when /Can.t create a new user.*/i raise Capistrano::Error, "scm_username (or p4user) is incorrect or unset" when /Perforce client error\:/i raise Capistrano::Error, "p4port is incorrect or unset" when /Client \'[\w\-\_\.]+\' unknown.*/i raise Capistrano::Error, "p4client is incorrect or unset" end end private # Builds the set of authentication switches that perforce understands. def authentication [ p4port && "-p #{p4port}", p4user && "-u #{p4user}", p4passwd && "-P #{p4passwd}", p4client && "-c #{p4client}" ].compact.join(" ") end # Returns the command that will sync the given revision to the given # destination directory with specific options. The perforce client has # a fixed destination so the files must be copied from there to their # intended resting place. def p4_sync(revision, destination, options="") scm authentication, :sync, options, "#{rev_no(revision)}", "&& cp -rf #{p4client_root} #{destination}" end def p4client variable(:p4client) end def p4port variable(:p4port) end def p4user variable(:p4user) || variable(:scm_username) end def p4passwd variable(:p4passwd) || variable(:scm_password) end def p4sync_flags variable(:p4sync_flags) || "-f" end def p4client_root variable(:p4client_root) || "`#{command} #{authentication} client -o | grep ^Root | cut -f2`" end def rev_no(revision) if variable(:p4_label) p4_label = if variable(:p4_label) =~ /\A@/ variable(:p4_label) else "@#{variable(:p4_label)}" end return p4_label end case revision.to_s when "head" "#head" when /^\d+/ "@#{revision}" else revision end end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/darcs.rb0000644000004100000410000000720211746743503024044 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the darcs revision # control system (http://www.abridgegame.org/darcs/). class Darcs < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "darcs" # Because darcs does not have any support for pseudo-ids, we'll just # return something here that we can use in the helpers below for # determining whether we need to look up the latest revision. def head :head end def to_match(revision) if revision.nil? || revision == self.head nil else "--to-match='hash #{revision}'" end end # Returns the command that will check out the given revision to the # given destination. The 'revision' parameter must be the 'hash' value # for the revision in question, as given by 'darcs changes --xml-output'. def checkout(revision, destination) scm :get, *[verbose, "--repo-name=#{destination}", to_match(revision), repository].compact end # Tries to update the destination repository in-place, to bring it up # to the given revision. Note that because darcs' "pull" operation # does not support a "to-match" argument (or similar), this basically # nukes the destination directory and re-gets it. def sync(revision, destination) ["rm -rf #{destination}", checkout(revision, destination)].join(" && ") end # Darcs does not have a real 'export' option; there is 'darcs dist', # but that presupposes a utility that can untar and ungzip the dist # file. We'll cheat and just do a get, followed by a deletion of the # _darcs metadata directory. def export(revision, destination) [checkout(revision, destination), "rm -rf #{destination}/_darcs"].join(" && ") end # Returns the command that will do a "darcs diff" for the two revisions. # Each revision must be the 'hash' identifier of a darcs revision. def diff(from, to=nil) scm :diff, "--from-match 'hash #{from}'", to && "--to-match 'hash #{to}'" end # Returns the log of changes between the two revisions. Each revision # must be the 'hash' identifier of a darcs revision. def log(from, to=nil) scm :changes, "--from-match 'hash #{from}'", to && "--to-match 'hash #{to}'", "--repo=#{repository}" end # Attempts to translate the given revision identifier to a "real" # revision. If the identifier is a symbol, it is assumed to be a # pseudo-id. Otherwise, it will be immediately returned. If it is a # pseudo-id, a set of commands to execute will be yielded, and the # result of executing those commands must be returned by the block. # This method will then extract the actual revision hash from the # returned data. def query_revision(revision) case revision when :head xml = yield(scm(:changes, "--last 1", "--xml-output", "--repo=#{repository}")) return xml[/hash='(.*?)'/, 1] else return revision end end private def verbose case variable(:scm_verbose) when nil then "-q" when false then nil else "-v" end end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/cvs.rb0000644000004100000410000001262211746743503023545 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the CVS revision # control system. class Cvs < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "cvs" # CVS understands 'HEAD' to refer to the latest revision in the # repository. def head "HEAD" end # Returns the command that will check out the given revision to the # given destination. def checkout(revision, destination) [ prep_destination(destination), scm(verbose, cvs_root, :checkout, cvs_revision(revision), cvs_destination(destination), variable(:scm_module)) ].join(' && ') end # Returns the command that will do an "cvs update" to the given # revision, for the working copy at the given destination. def sync(revision, destination) [ prep_destination(destination), scm(verbose, cvs_root, :update, cvs_revision(revision), cvs_destination(destination)) ].join(' && ') end # Returns the command that will do an "cvs export" of the given revision # to the given destination. def export(revision, destination) [ prep_destination(destination), scm(verbose, cvs_root, :export, cvs_revision(revision), cvs_destination(destination), variable(:scm_module)) ].join(' && ') end # Returns the command that will do an "cvs diff" for the two revisions. def diff(from, to=nil) rev_type = revision_type(from) if rev_type == :date range_args = "-D '#{from}' -D '#{to || 'now'}'" else range_args = "-r '#{from}' -r '#{to || head}'" end scm cvs_root, :diff, range_args end # Returns an "cvs log" command for the two revisions. def log(from, to=nil) rev_type = revision_type(from) if rev_type == :date range_arg = "-d '#{from}<#{to || 'now'}'" else range_arg = "-r '#{from}:#{to || head}'" end scm cvs_root, :log, range_arg end # Unfortunately, cvs doesn't support the concept of a revision number like # subversion and other SCM's do. For now, we'll rely on getting the timestamp # of the latest checkin under the revision that's passed to us. def query_revision(revision) return revision if revision_type(revision) == :date revision = yield(scm(cvs_root, :log, "-r#{revision}")). split("\n"). select { |line| line =~ /^date:/ }. map { |line| line[/^date: (.*?);/, 1] }. sort.last + " UTC" return revision end # Determines what the response should be for a particular bit of text # from the SCM. Password prompts, connection requests, passphrases, # etc. are handled here. def handle_data(state, stream, text) logger.info "[#{stream}] #{text}" case text when /\bpassword.*:/i # prompting for a password "#{variable(:scm_password) || variable(:password)}\n" when %r{\(yes/no\)} # let's be agreeable... "yes\n" end end private # Constructs the CVSROOT command-line option def cvs_root root = "" root << "-d #{repository} " if repository root end # Constructs the destination dir command-line option def cvs_destination(destination) dest = "" if destination dest_parts = destination.split(/\//); dest << "-d #{dest_parts.pop}" end dest end # attempts to guess what type of revision we're working with def revision_type(rev) return :date if rev =~ /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2} UTC$/ # i.e 2007-05-15 08:13:25 UTC return :date if rev =~ /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/ # i.e 2007-05-15 08:13:25 return :revision if rev =~ /^\d/ # i.e. 1.2.1 return :tag # i.e. RELEASE_1_2 end # constructs the appropriate command-line switch for specifying a # "revision" in CVS. This could be a tag, branch, revision (i.e. 1.3) # or a date (to be used with -d) def cvs_revision(rev) revision = "" revision << case revision_type(rev) when :date "-D \"#{rev}\"" if revision_type(rev) == :date when :revision "-r #{rev}" else "-r #{head}" end return revision end # If verbose output is requested, return nil, otherwise return the # command-line switch for "quiet" ("-Q"). def verbose variable(:scm_verbose) ? nil : "-Q" end def prep_destination(destination) dest_parts = destination.split(/\//); checkout_dir = dest_parts.pop dest = dest_parts.join('/') "mkdir -p #{ dest } && cd #{ dest }" end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/subversion.rb0000644000004100000410000001164011746743503025150 0ustar www-datawww-datarequire 'capistrano/recipes/deploy/scm/base' require 'yaml' module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the Subversion revision # control system (http://subversion.tigris.org). class Subversion < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "svn" # Subversion understands 'HEAD' to refer to the latest revision in the # repository. def head "HEAD" end # Returns the command that will check out the given revision to the # given destination. def checkout(revision, destination) scm :checkout, arguments, arguments(:checkout), verbose, authentication, "-r#{revision}", repository, destination end # Returns the command that will do an "svn update" to the given # revision, for the working copy at the given destination. def sync(revision, destination) scm :switch, arguments, verbose, authentication, "-r#{revision}", repository, destination end # Returns the command that will do an "svn export" of the given revision # to the given destination. def export(revision, destination) scm :export, arguments, arguments(:export), verbose, authentication, "-r#{revision}", repository, destination end # Returns the command that will do an "svn diff" for the two revisions. def diff(from, to=nil) scm :diff, repository, arguments(:diff), authentication, "-r#{from}:#{to || head}" end # Returns an "svn log" command for the two revisions. def log(from, to=nil) scm :log, repository, arguments(:log), authentication, "-r#{from}:#{to || head}" end # Attempts to translate the given revision identifier to a "real" # revision. If the identifier is an integer, it will simply be returned. # Otherwise, this will yield a string of the commands it needs to be # executed (svn info), and will extract the revision from the response. def query_revision(revision) return revision if revision =~ /^\d+$/ command = scm(:info, arguments, arguments(:info), repository, authentication, "-r#{revision}") result = yield(command) yaml = YAML.load(result) raise "tried to run `#{command}' and got unexpected result #{result.inspect}" unless Hash === yaml [ (yaml['Last Changed Rev'] || 0).to_i, (yaml['Revision'] || 0).to_i ].max end # Increments the given revision number and returns it. def next_revision(revision) revision.to_i + 1 end # Determines what the response should be for a particular bit of text # from the SCM. Password prompts, connection requests, passphrases, # etc. are handled here. def handle_data(state, stream, text) host = state[:channel][:host] logger.info "[#{host} :: #{stream}] #{text}" case text when /\bpassword.*:/i # subversion is prompting for a password "#{scm_password_prompt}\n" when %r{\(yes/no\)} # subversion is asking whether or not to connect "yes\n" when /passphrase/i # subversion is asking for the passphrase for the user's key "#{variable(:scm_passphrase)}\n" when /The entry \'(.+?)\' is no longer a directory/ raise Capistrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore." when /accept \(t\)emporarily/ # subversion is asking whether to accept the certificate "t\n" end end private # If a username is configured for the SCM, return the command-line # switches for that. Note that we don't need to return the password # switch, since Capistrano will check for that prompt in the output # and will respond appropriately. def authentication username = variable(:scm_username) return "" unless username result = "--username #{variable(:scm_username)} " result << "--password #{variable(:scm_password)} " unless variable(:scm_auth_cache) || variable(:scm_prefer_prompt) result << "--no-auth-cache " unless variable(:scm_auth_cache) result end # If verbose output is requested, return nil, otherwise return the # command-line switch for "quiet" ("-q"). def verbose variable(:scm_verbose) ? nil : "-q" end def scm_password_prompt @scm_password_prompt ||= variable(:scm_password) || variable(:password) || Capistrano::CLI.password_prompt("Subversion password: ") end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/scm/mercurial.rb0000644000004100000410000001125111746743503024732 0ustar www-datawww-data# Copyright 2007 Matthew Elder # based on work by Tobias Luetke require 'capistrano/recipes/deploy/scm/base' module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the Mercurial revision # control system (http://www.selenic.com/mercurial/). # Latest updates at http://tackletechnology.org/oss/cap2-mercurial class Mercurial < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "hg" # For mercurial HEAD == tip except that it bases this assumption on what # tip is in the current repository (so push before you deploy) def head variable(:branch) || "tip" end # Clone the repository and update to the specified changeset. def checkout(changeset, destination) clone(destination) + " && " + update(changeset, destination) end # Pull from the repository and update to the specified changeset. def sync(changeset, destination) pull(destination) + " && " + update(changeset, destination) end # One day we will have hg archive, although i think its not needed def export(revision, destination) raise NotImplementedError, "`diff' is not implemented by #{self.class.name}" + "use checkout strategy" end # Compute the difference between the two changesets +from+ and +to+ # as a unified diff. def diff(from, to=nil) scm :diff, "--rev #{from}", (to ? "--rev #{to}" : nil) end # Return a log of all changes between the two specified changesets, # +from+ and +to+, inclusive or the log for +from+ if +to+ is omitted. def log(from, to=nil) scm :log, verbose, "--rev #{from}" + (to ? ":#{to}" : "") end # Translates a tag to a changeset if needed or just returns changeset. def query_revision(changeset) cmd = scm :log, verbose, "-r #{changeset}", '--template "{node|short}"' yield cmd end # Determine response for SCM prompts # user/pass can come from ssh and http distribution methods # yes/no is for when ssh asks you about fingerprints def handle_data(state, stream, text) host = state[:channel][:host] logger.info "[#{host} :: #{stream}] #{text}" case text when /^user:/mi # support :scm_user for backwards compatibility of this module if user = variable(:scm_username) || variable(:scm_user) "#{user}\n" else raise "No variable :scm_username specified and Mercurial asked!\n" + "Prompt was: #{text}" end when /\bpassword:/mi unless pass = scm_password_or_prompt # fall back on old behavior of erroring out with msg raise "No variable :scm_password specified and Mercurial asked!\n" + "Prompt was: #{text}" end "#{pass}\n" when /yes\/no/i "yes\n" end end private # Fine grained mercurial commands def clone(destination) scm :clone, verbose, "--noupdate", # do not update to tip when cloning is done repository, # clone which repository? destination # and put the clone where? end def pull(destination) scm :pull, verbose, "--repository #{destination}", # pull changes into what? repository # and pull the changes from? end def update(changeset, destination) scm :update, verbose, "--repository #{destination}", # update what? "--clean", # ignore untracked changes changeset # update to this changeset end # verbosity configuration grokking :) def verbose case variable(:scm_verbose) when nil then nil when false then "--quiet" else "--verbose" end end # honor Cap 2.1+'s :scm_prefer_prompt if present def scm_password_or_prompt @scm_password_or_prompt ||= variable(:scm_password) || (Capistrano::CLI.password_prompt("hg password: ") if variable(:scm_prefer_prompt)) end end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/local_dependency.rb0000644000004100000410000000235711746743503025464 0ustar www-datawww-datamodule Capistrano module Deploy class LocalDependency attr_reader :configuration attr_reader :message def initialize(configuration) @configuration = configuration @success = true end def command(command) @message ||= "`#{command}' could not be found in the path on the local host" @success = find_in_path(command) self end def or(message) @message = message self end def pass? @success end private # Searches the path, looking for the given utility. If an executable # file is found that matches the parameter, this returns true. def find_in_path(utility) path = (ENV['PATH'] || "").split(File::PATH_SEPARATOR) suffixes = self.class.on_windows? ? self.class.windows_executable_extensions : [""] path.each do |dir| suffixes.each do |sfx| file = File.join(dir, utility + sfx) return true if File.executable?(file) end end false end def self.on_windows? RUBY_PLATFORM =~ /mswin|mingw/ end def self.windows_executable_extensions %w(.exe .bat .com .cmd) end end end end capistrano-2.12.0/lib/capistrano/recipes/deploy/templates/0000755000004100000410000000000011746743503023636 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/recipes/deploy/templates/maintenance.rhtml0000644000004100000410000000247311746743503027176 0ustar www-datawww-data System down for maintenance

The system is down for <%= reason ? reason : "maintenance" %> as of <%= Time.now.strftime("%H:%M %Z") %>.

It'll be back <%= deadline ? deadline : "shortly" %>.

capistrano-2.12.0/lib/capistrano/recipes/deploy/scm.rb0000644000004100000410000000105511746743503022750 0ustar www-datawww-datamodule Capistrano module Deploy module SCM def self.new(scm, config={}) scm_file = "capistrano/recipes/deploy/scm/#{scm}" require(scm_file) scm_const = scm.to_s.capitalize.gsub(/_(.)/) { $1.upcase } if const_defined?(scm_const) const_get(scm_const).new(config) else raise Capistrano::Error, "could not find `#{name}::#{scm_const}' in `#{scm_file}'" end rescue LoadError raise Capistrano::Error, "could not find any SCM named `#{scm}'" end end end endcapistrano-2.12.0/lib/capistrano/recipes/standard.rb0000644000004100000410000000264611746743503022501 0ustar www-datawww-datadesc <<-DESC Invoke a single command on the remote servers. This is useful for performing \ one-off commands that may not require a full task to be written for them. \ Simply specify the command to execute via the COMMAND environment variable. \ To execute the command only on certain roles, specify the ROLES environment \ variable as a comma-delimited list of role names. Alternatively, you can \ specify the HOSTS environment variable as a comma-delimited list of hostnames \ to execute the task on those hosts, explicitly. Lastly, if you want to \ execute the command via sudo, specify a non-empty value for the SUDO \ environment variable. Sample usage: $ cap COMMAND=uptime HOSTS=foo.capistano.test invoke $ cap ROLES=app,web SUDO=1 COMMAND="tail -f /var/log/messages" invoke DESC task :invoke do command = ENV["COMMAND"] || "" abort "Please specify a command to execute on the remote servers (via the COMMAND environment variable)" if command.empty? method = ENV["SUDO"] ? :sudo : :run invoke_command(command, :via => method) end desc <<-DESC Begin an interactive Capistrano session. This gives you an interactive \ terminal from which to execute tasks and commands on all of your servers. \ (This is still an experimental feature, and is subject to change without \ notice!) Sample usage: $ cap shell DESC task :shell do require 'capistrano/shell' Capistrano::Shell.run(self) endcapistrano-2.12.0/lib/capistrano/recipes/compat.rb0000644000004100000410000000224011746743503022152 0ustar www-datawww-data# A collection of compatibility scripts, to ease the transition between # Capistrano 1.x and Capistrano 2.x. # Depends on the deployment system load 'deploy' map = { "diff_from_last_deploy" => "deploy:pending:diff", "update" => "deploy:update", "update_code" => "deploy:update_code", "symlink" => "deploy:create_symlink", "restart" => "deploy:restart", "rollback" => "deploy:rollback", "cleanup" => "deploy:cleanup", "disable_web" => "deploy:web:disable", "enable_web" => "deploy:web:enable", "cold_deploy" => "deploy:cold", "deploy_with_migrations" => "deploy:migrations" } map.each do |old, new| desc "DEPRECATED: See #{new}." eval "task(#{old.inspect}) do warn \"[DEPRECATED] `#{old}' is deprecated. Use `#{new}' instead.\" find_and_execute_task(#{new.inspect}) end" end desc "DEPRECATED: See deploy:start." task :spinner do warn "[DEPRECATED] `spinner' is deprecated. Use `deploy:start' instead." set :runner, fetch(:spinner_user, "app") deploy.start end capistrano-2.12.0/lib/capistrano/recipes/templates/0000755000004100000410000000000011746743503022342 5ustar www-datawww-datacapistrano-2.12.0/lib/capistrano/recipes/templates/maintenance.rhtml0000644000004100000410000000247311746743503025702 0ustar www-datawww-data System down for maintenance

The system is down for <%= reason ? reason : "maintenance" %> as of <%= Time.now.strftime("%H:%M %Z") %>.

It'll be back <%= deadline ? deadline : "shortly" %>.

capistrano-2.12.0/lib/capistrano/recipes/deploy.rb0000644000004100000410000005704011746743503022173 0ustar www-datawww-datarequire 'benchmark' require 'yaml' require 'capistrano/recipes/deploy/scm' require 'capistrano/recipes/deploy/strategy' def _cset(name, *args, &block) unless exists?(name) set(name, *args, &block) end end # ========================================================================= # These variables MUST be set in the client capfiles. If they are not set, # the deploy will fail with an error. # ========================================================================= _cset(:application) { abort "Please specify the name of your application, set :application, 'foo'" } _cset(:repository) { abort "Please specify the repository that houses your application's code, set :repository, 'foo'" } # ========================================================================= # These variables may be set in the client capfile if their default values # are not sufficient. # ========================================================================= _cset :scm, :subversion _cset :deploy_via, :checkout _cset(:deploy_to) { "/u/apps/#{application}" } _cset(:revision) { source.head } _cset :rails_env, "production" _cset :rake, "rake" _cset :maintenance_basename, "maintenance" _cset(:maintenance_template_path) { File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml") } # ========================================================================= # These variables should NOT be changed unless you are very confident in # what you are doing. Make sure you understand all the implications of your # changes if you do decide to muck with these! # ========================================================================= _cset(:source) { Capistrano::Deploy::SCM.new(scm, self) } _cset(:real_revision) { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } } } _cset(:strategy) { Capistrano::Deploy::Strategy.new(deploy_via, self) } # If overriding release name, please also select an appropriate setting for :releases below. _cset(:release_name) { set :deploy_timestamped, true; Time.now.utc.strftime("%Y%m%d%H%M%S") } _cset :version_dir, "releases" _cset :shared_dir, "shared" _cset :shared_children, %w(public/system log tmp/pids) _cset :current_dir, "current" _cset(:releases_path) { File.join(deploy_to, version_dir) } _cset(:shared_path) { File.join(deploy_to, shared_dir) } _cset(:current_path) { File.join(deploy_to, current_dir) } _cset(:release_path) { File.join(releases_path, release_name) } _cset(:releases) { capture("ls -x #{releases_path}", :except => { :no_release => true }).split.sort } _cset(:current_release) { releases.length > 0 ? File.join(releases_path, releases.last) : nil } _cset(:previous_release) { releases.length > 1 ? File.join(releases_path, releases[-2]) : nil } _cset(:current_revision) { capture("cat #{current_path}/REVISION", :except => { :no_release => true }).chomp } _cset(:latest_revision) { capture("cat #{current_release}/REVISION", :except => { :no_release => true }).chomp } _cset(:previous_revision) { capture("cat #{previous_release}/REVISION", :except => { :no_release => true }).chomp if previous_release } _cset(:run_method) { fetch(:use_sudo, true) ? :sudo : :run } # some tasks, like symlink, need to always point at the latest release, but # they can also (occassionally) be called standalone. In the standalone case, # the timestamped release_path will be inaccurate, since the directory won't # actually exist. This variable lets tasks like symlink work either in the # standalone case, or during deployment. _cset(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_release } # ========================================================================= # These are helper methods that will be available to your recipes. # ========================================================================= # Auxiliary helper method for the `deploy:check' task. Lets you set up your # own dependencies. def depend(location, type, *args) deps = fetch(:dependencies, {}) deps[location] ||= {} deps[location][type] ||= [] deps[location][type] << args set :dependencies, deps end # Temporarily sets an environment variable, yields to a block, and restores # the value when it is done. def with_env(name, value) saved, ENV[name] = ENV[name], value yield ensure ENV[name] = saved end # logs the command then executes it locally. # returns the command output as a string def run_locally(cmd) logger.trace "executing locally: #{cmd.inspect}" if logger output_on_stdout = nil elapsed = Benchmark.realtime do output_on_stdout = `#{cmd}` end if $?.to_i > 0 # $? is command exit code (posix style) raise Capistrano::LocalArgumentError, "Command #{cmd} returned status code #{$?}" end logger.trace "command finished in #{(elapsed * 1000).round}ms" if logger output_on_stdout end # If a command is given, this will try to execute the given command, as # described below. Otherwise, it will return a string for use in embedding in # another command, for executing that command as described below. # # If :run_method is :sudo (or :use_sudo is true), this executes the given command # via +sudo+. Otherwise is uses +run+. If :as is given as a key, it will be # passed as the user to sudo as, if using sudo. If the :as key is not given, # it will default to whatever the value of the :admin_runner variable is, # which (by default) is unset. # # THUS, if you want to try to run something via sudo, and what to use the # root user, you'd just to try_sudo('something'). If you wanted to try_sudo as # someone else, you'd just do try_sudo('something', :as => "bob"). If you # always wanted sudo to run as a particular user, you could do # set(:admin_runner, "bob"). def try_sudo(*args) options = args.last.is_a?(Hash) ? args.pop : {} command = args.shift raise ArgumentError, "too many arguments" if args.any? as = options.fetch(:as, fetch(:admin_runner, nil)) via = fetch(:run_method, :sudo) if command invoke_command(command, :via => via, :as => as) elsif via == :sudo sudo(:as => as) else "" end end # Same as sudo, but tries sudo with :as set to the value of the :runner # variable (which defaults to "app"). def try_runner(*args) options = args.last.is_a?(Hash) ? args.pop : {} args << options.merge(:as => fetch(:runner, "app")) try_sudo(*args) end # ========================================================================= # These are the tasks that are available to help with deploying web apps, # and specifically, Rails applications. You can have cap give you a summary # of them with `cap -T'. # ========================================================================= namespace :deploy do desc <<-DESC Deploys your project. This calls both `update' and `restart'. Note that \ this will generally only work for applications that have already been deployed \ once. For a "cold" deploy, you'll want to take a look at the `deploy:cold' \ task, which handles the cold start specifically. DESC task :default do update restart end desc <<-DESC Prepares one or more servers for deployment. Before you can use any \ of the Capistrano deployment tasks with your project, you will need to \ make sure all of your servers have been prepared with `cap deploy:setup'. When \ you add a new server to your cluster, you can easily run the setup task \ on just that server by specifying the HOSTS environment variable: $ cap HOSTS=new.server.com deploy:setup It is safe to run this task on servers that have already been set up; it \ will not destroy any deployed revisions or data. DESC task :setup, :except => { :no_release => true } do dirs = [deploy_to, releases_path, shared_path] dirs += shared_children.map { |d| File.join(shared_path, d.split('/').last) } run "#{try_sudo} mkdir -p #{dirs.join(' ')}" run "#{try_sudo} chmod g+w #{dirs.join(' ')}" if fetch(:group_writable, true) end desc <<-DESC Copies your project and updates the symlink. It does this in a \ transaction, so that if either `update_code' or `symlink' fail, all \ changes made to the remote servers will be rolled back, leaving your \ system in the same state it was in before `update' was invoked. Usually, \ you will want to call `deploy' instead of `update', but `update' can be \ handy if you want to deploy, but not immediately restart your application. DESC task :update do transaction do update_code create_symlink end end desc <<-DESC Copies your project to the remote servers. This is the first stage \ of any deployment; moving your updated code and assets to the deployment \ servers. You will rarely call this task directly, however; instead, you \ should call the `deploy' task (to do a complete deploy) or the `update' \ task (if you want to perform the `restart' task separately). You will need to make sure you set the :scm variable to the source \ control software you are using (it defaults to :subversion), and the \ :deploy_via variable to the strategy you want to use to deploy (it \ defaults to :checkout). DESC task :update_code, :except => { :no_release => true } do on_rollback { run "rm -rf #{release_path}; true" } strategy.deploy! finalize_update end desc <<-DESC [internal] Touches up the released code. This is called by update_code \ after the basic deploy finishes. It assumes a Rails project was deployed, \ so if you are deploying something else, you may want to override this \ task with your own environment's requirements. This task will make the release group-writable (if the :group_writable \ variable is set to true, which is the default). It will then set up \ symlinks to the shared directory for the log, system, and tmp/pids \ directories, and will lastly touch all assets in public/images, \ public/stylesheets, and public/javascripts so that the times are \ consistent (so that asset timestamping works). This touch process \ is only carried out if the :normalize_asset_timestamps variable is \ set to true, which is the default The asset directories can be overridden \ using the :public_children variable. DESC task :finalize_update, :except => { :no_release => true } do run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true) # mkdir -p is making sure that the directories are there for some SCM's that don't # save empty folders run <<-CMD rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids && mkdir -p #{latest_release}/public && mkdir -p #{latest_release}/tmp CMD shared_children.map do |d| run "ln -s #{shared_path}/#{d.split('/').last} #{latest_release}/#{d}" end if fetch(:normalize_asset_timestamps, true) stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S") asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{latest_release}/public/#{p}" }.join(" ") run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" } end end desc <<-DESC Deprecated API. This has become deploy:create_symlink, please update your recipes DESC task :symlink, :except => { :no_release => true } do Kernel.warn "[Deprecation Warning] This API has changed, please hook `deploy:create_symlink` instead of `deploy:symlink`." create_symlink end desc <<-DESC Updates the symlink to the most recently deployed version. Capistrano works \ by putting each new release of your application in its own directory. When \ you deploy a new version, this task's job is to update the `current' symlink \ to point at the new version. You will rarely need to call this task \ directly; instead, use the `deploy' task (which performs a complete \ deploy, including `restart') or the 'update' task (which does everything \ except `restart'). DESC task :create_symlink, :except => { :no_release => true } do on_rollback do if previous_release run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true" else logger.important "no previous release to rollback to, rollback of symlink skipped" end end run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}" end desc <<-DESC Copy files to the currently deployed version. This is useful for updating \ files piecemeal, such as when you need to quickly deploy only a single \ file. Some files, such as updated templates, images, or stylesheets, \ might not require a full deploy, and especially in emergency situations \ it can be handy to just push the updates to production, quickly. To use this task, specify the files and directories you want to copy as a \ comma-delimited list in the FILES environment variable. All directories \ will be processed recursively, with all files being pushed to the \ deployment servers. $ cap deploy:upload FILES=templates,controller.rb Dir globs are also supported: $ cap deploy:upload FILES='config/apache/*.conf' DESC task :upload, :except => { :no_release => true } do files = (ENV["FILES"] || "").split(",").map { |f| Dir[f.strip] }.flatten abort "Please specify at least one file or directory to update (via the FILES environment variable)" if files.empty? files.each { |file| top.upload(file, File.join(current_path, file)) } end desc <<-DESC Blank task exists as a hook into which to install your own environment \ specific behaviour. DESC task :restart, :roles => :app, :except => { :no_release => true } do # Empty Task to overload with your platform specifics end namespace :rollback do desc <<-DESC [internal] Points the current symlink at the previous revision. This is called by the rollback sequence, and should rarely (if ever) need to be called directly. DESC task :revision, :except => { :no_release => true } do if previous_release run "rm #{current_path}; ln -s #{previous_release} #{current_path}" else abort "could not rollback the code because there is no prior release" end end desc <<-DESC [internal] Removes the most recently deployed release. This is called by the rollback sequence, and should rarely (if ever) need to be called directly. DESC task :cleanup, :except => { :no_release => true } do run "if [ `readlink #{current_path}` != #{current_release} ]; then rm -rf #{current_release}; fi" end desc <<-DESC Rolls back to the previously deployed version. The `current' symlink will \ be updated to point at the previously deployed version, and then the \ current release will be removed from the servers. You'll generally want \ to call `rollback' instead, as it performs a `restart' as well. DESC task :code, :except => { :no_release => true } do revision cleanup end desc <<-DESC Rolls back to a previous version and restarts. This is handy if you ever \ discover that you've deployed a lemon; `cap rollback' and you're right \ back where you were, on the previously deployed version. DESC task :default do revision restart cleanup end end desc <<-DESC Run the migrate rake task. By default, it runs this in most recently \ deployed version of the app. However, you can specify a different release \ via the migrate_target variable, which must be one of :latest (for the \ default behavior), or :current (for the release indicated by the \ `current' symlink). Strings will work for those values instead of symbols, \ too. You can also specify additional environment variables to pass to rake \ via the migrate_env variable. Finally, you can specify the full path to the \ rake executable by setting the rake variable. The defaults are: set :rake, "rake" set :rails_env, "production" set :migrate_env, "" set :migrate_target, :latest DESC task :migrate, :roles => :db, :only => { :primary => true } do rake = fetch(:rake, "rake") rails_env = fetch(:rails_env, "production") migrate_env = fetch(:migrate_env, "") migrate_target = fetch(:migrate_target, :latest) directory = case migrate_target.to_sym when :current then current_path when :latest then latest_release else raise ArgumentError, "unknown migration target #{migrate_target.inspect}" end run "cd #{directory} && #{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate" end desc <<-DESC Deploy and run pending migrations. This will work similarly to the \ `deploy' task, but will also run any pending migrations (via the \ `deploy:migrate' task) prior to updating the symlink. Note that the \ update in this case it is not atomic, and transactions are not used, \ because migrations are not guaranteed to be reversible. DESC task :migrations do set :migrate_target, :latest update_code migrate create_symlink restart end desc <<-DESC Clean up old releases. By default, the last 5 releases are kept on each \ server (though you can change this with the keep_releases variable). All \ other deployed revisions are removed from the servers. By default, this \ will use sudo to clean up the old releases, but if sudo is not available \ for your environment, set the :use_sudo variable to false instead. DESC task :cleanup, :except => { :no_release => true } do count = fetch(:keep_releases, 5).to_i local_releases = capture("ls -xt #{releases_path}").split.reverse if count >= local_releases.length logger.important "no old releases to clean up" else logger.info "keeping #{count} of #{local_releases.length} deployed releases" directories = (local_releases - local_releases.last(count)).map { |release| File.join(releases_path, release) }.join(" ") try_sudo "rm -rf #{directories}" end end desc <<-DESC Test deployment dependencies. Checks things like directory permissions, \ necessary utilities, and so forth, reporting on the things that appear to \ be incorrect or missing. This is good for making sure a deploy has a \ chance of working before you actually run `cap deploy'. You can define your own dependencies, as well, using the `depend' method: depend :remote, :gem, "tzinfo", ">=0.3.3" depend :local, :command, "svn" depend :remote, :directory, "/u/depot/files" DESC task :check, :except => { :no_release => true } do dependencies = strategy.check! other = fetch(:dependencies, {}) other.each do |location, types| types.each do |type, calls| if type == :gem dependencies.send(location).command(fetch(:gem_command, "gem")).or("`gem' command could not be found. Try setting :gem_command") end calls.each do |args| dependencies.send(location).send(type, *args) end end end if dependencies.pass? puts "You appear to have all necessary dependencies installed" else puts "The following dependencies failed. Please check them and try again:" dependencies.reject { |d| d.pass? }.each do |d| puts "--> #{d.message}" end abort end end desc <<-DESC Deploys and starts a `cold' application. This is useful if you have not \ deployed your application before, or if your application is (for some \ other reason) not currently running. It will deploy the code, run any \ pending migrations, and then instead of invoking `deploy:restart', it will \ invoke `deploy:start' to fire up the application servers. DESC task :cold do update migrate start end desc <<-DESC Blank task exists as a hook into which to install your own environment \ specific behaviour. DESC task :start, :roles => :app do # Empty Task to overload with your platform specifics end desc <<-DESC Blank task exists as a hook into which to install your own environment \ specific behaviour. DESC task :stop, :roles => :app do # Empty Task to overload with your platform specifics end namespace :pending do desc <<-DESC Displays the `diff' since your last deploy. This is useful if you want \ to examine what changes are about to be deployed. Note that this might \ not be supported on all SCM's. DESC task :diff, :except => { :no_release => true } do system(source.local.diff(current_revision)) end desc <<-DESC Displays the commits since your last deploy. This is good for a summary \ of the changes that have occurred since the last deploy. Note that this \ might not be supported on all SCM's. DESC task :default, :except => { :no_release => true } do from = source.next_revision(current_revision) system(source.local.log(from)) end end namespace :web do desc <<-DESC Present a maintenance page to visitors. Disables your application's web \ interface by writing a "#{maintenance_basename}.html" file to each web server. The \ servers must be configured to detect the presence of this file, and if \ it is present, always display it instead of performing the request. By default, the maintenance page will just say the site is down for \ "maintenance", and will be back "shortly", but you can customize the \ page by specifying the REASON and UNTIL environment variables: $ cap deploy:web:disable \\ REASON="hardware upgrade" \\ UNTIL="12pm Central Time" You can use a different template for the maintenance page by setting the \ :maintenance_template_path variable in your deploy.rb file. The template file \ should either be a plaintext or an erb file. Further customization will require that you write your own task. DESC task :disable, :roles => :web, :except => { :no_release => true } do require 'erb' on_rollback { run "rm -f #{shared_path}/system/#{maintenance_basename}.html" } warn <<-EOHTACCESS # Please add something like this to your site's Apache htaccess to redirect users to the maintenance page. # More Info: http://www.shiftcommathree.com/articles/make-your-rails-maintenance-page-respond-with-a-503 ErrorDocument 503 /system/#{maintenance_basename}.html RewriteEngine On RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$ RewriteCond %{DOCUMENT_ROOT}/system/#{maintenance_basename}.html -f RewriteCond %{SCRIPT_FILENAME} !#{maintenance_basename}.html RewriteRule ^.*$ - [redirect=503,last] # Or if you are using Nginx add this to your server config: if (-f $document_root/system/maintenance.html) { return 503; } error_page 503 @maintenance; location @maintenance { rewrite ^(.*)$ /system/maintenance.html last; break; } EOHTACCESS reason = ENV['REASON'] deadline = ENV['UNTIL'] template = File.read(maintenance_template_path) result = ERB.new(template).result(binding) put result, "#{shared_path}/system/#{maintenance_basename}.html", :mode => 0644 end desc <<-DESC Makes the application web-accessible again. Removes the \ "#{maintenance_basename}.html" page generated by deploy:web:disable, which (if your \ web servers are configured correctly) will make your application \ web-accessible again. DESC task :enable, :roles => :web, :except => { :no_release => true } do run "rm -f #{shared_path}/system/#{maintenance_basename}.html" end end end