mirror of
				https://github.com/capistrano/capistrano
				synced 2023-03-27 23:21:18 -04:00 
			
		
		
		
	start refactoring the task helper actions
git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@6288 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
		
							parent
							
								
									acdd65f213
								
							
						
					
					
						commit
						472431004c
					
				
					 13 changed files with 345 additions and 168 deletions
				
			
		
							
								
								
									
										1
									
								
								Rakefile
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								Rakefile
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -19,7 +19,6 @@ desc "Build documentation"
 | 
			
		|||
task :doc => [ :rdoc ]
 | 
			
		||||
 | 
			
		||||
Rake::TestTask.new do |t|
 | 
			
		||||
  t.ruby_opts << "-rubygems"
 | 
			
		||||
  t.test_files = Dir["test/**/*_test.rb"]
 | 
			
		||||
  t.verbose = true
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,42 +13,6 @@ module Capistrano
 | 
			
		|||
  # directly--rather, you create a new Configuration instance, and access the
 | 
			
		||||
  # new actor via Configuration#actor.
 | 
			
		||||
  class Actor
 | 
			
		||||
    class <<self
 | 
			
		||||
      attr_accessor :default_io_proc
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    self.default_io_proc = Proc.new do |ch, stream, out|
 | 
			
		||||
      level = stream == :err ? :important : :info
 | 
			
		||||
      ch[:actor].logger.send(level, out, "#{stream} :: #{ch[:host]}")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def initialize(config) #:nodoc:
 | 
			
		||||
      @configuration = config
 | 
			
		||||
      @tasks = {}
 | 
			
		||||
      @task_call_frames = []
 | 
			
		||||
      @sessions = {}
 | 
			
		||||
      @factory = self.class.connection_factory.new(configuration)
 | 
			
		||||
    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 (<tt>:err</tt> for stderr, and <tt>:out</tt> for
 | 
			
		||||
    # stdout), and the data that was received.
 | 
			
		||||
    #
 | 
			
		||||
    # If +pretend+ mode is active, this does nothing.
 | 
			
		||||
    def run(cmd, options={}, &block)
 | 
			
		||||
      block ||= default_io_proc
 | 
			
		||||
      logger.debug "executing #{cmd.strip.inspect}"
 | 
			
		||||
 | 
			
		||||
      execute_on_servers(options) do |servers|
 | 
			
		||||
        # execute the command on each server in parallel
 | 
			
		||||
        command = self.class.command_factory.new(servers, cmd, block, options, self)
 | 
			
		||||
        command.process! # raises an exception if command fails on any server
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # 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
 | 
			
		||||
| 
						 | 
				
			
			@ -78,56 +42,6 @@ module Capistrano
 | 
			
		|||
      run(cmd, options)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Store the given data at the given location on all servers targetted by
 | 
			
		||||
    # the current task. If <tt>:mode</tt> is specified it is used to set the
 | 
			
		||||
    # mode on the file.
 | 
			
		||||
    def put(data, path, options={})
 | 
			
		||||
      if Capistrano::SFTP
 | 
			
		||||
        execute_on_servers(options) do |servers|
 | 
			
		||||
          transfer = self.class.transfer_factory.new(servers, self, path, :data => data,
 | 
			
		||||
            :mode => options[:mode])
 | 
			
		||||
          transfer.process!
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        # Poor-man's SFTP... just run a cat on the remote end, and send data
 | 
			
		||||
        # to it.
 | 
			
		||||
 | 
			
		||||
        cmd = "cat > #{path}"
 | 
			
		||||
        cmd << " && chmod #{options[:mode].to_s(8)} #{path}" if options[:mode]
 | 
			
		||||
        run(cmd, options.merge(:data => data + "\n\4")) do |ch, stream, out|
 | 
			
		||||
          logger.important out, "#{stream} :: #{ch[:host]}" if stream == :err
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    # Get file remote_path from FIRST server targetted by
 | 
			
		||||
    # the current task and transfer it to local machine as path. It will use
 | 
			
		||||
    # SFTP if Net::SFTP is installed; otherwise it will fall back to using
 | 
			
		||||
    # 'cat', which may cause corruption in binary files.
 | 
			
		||||
    #
 | 
			
		||||
    # get "#{deploy_to}/current/log/production.log", "log/production.log.web"
 | 
			
		||||
    def get(remote_path, path, options = {})
 | 
			
		||||
      if Capistrano::SFTP && options.fetch(:sftp, true)
 | 
			
		||||
        execute_on_servers(options.merge(:once => true)) do |servers|
 | 
			
		||||
          logger.debug "downloading #{servers.first}:#{remote_path} to #{path}" 
 | 
			
		||||
          sftp = sessions[servers.first].sftp
 | 
			
		||||
          sftp.connect unless sftp.state == :open
 | 
			
		||||
          sftp.get_file remote_path, path
 | 
			
		||||
          logger.trace "download finished" 
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        logger.important "Net::SFTP is not available; using remote 'cat' to get file, which may cause file corruption"
 | 
			
		||||
        File.open(path, "w") do |destination|
 | 
			
		||||
          run "cat #{remote_path}", :once => true do |ch, stream, data|
 | 
			
		||||
            case stream
 | 
			
		||||
            when :out then destination << data
 | 
			
		||||
            when :err then raise "error while downloading #{remote_path}: #{data.inspect}"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      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.
 | 
			
		||||
    def capture(command, options={})
 | 
			
		||||
| 
						 | 
				
			
			@ -141,42 +55,6 @@ module Capistrano
 | 
			
		|||
      output
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Like #run, but executes the command via <tt>sudo</tt>. This assumes that
 | 
			
		||||
    # the sudo password (if required) is the same as the password for logging
 | 
			
		||||
    # in to the server.
 | 
			
		||||
    #
 | 
			
		||||
    # Also, this module accepts a <tt>:sudo</tt> 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"
 | 
			
		||||
    def sudo(command, options={}, &block)
 | 
			
		||||
      block ||= default_io_proc
 | 
			
		||||
 | 
			
		||||
      # 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      
 | 
			
		||||
      user = options[:as].nil? ? '' : "-u #{options[:as]}"
 | 
			
		||||
      
 | 
			
		||||
      run "#{sudo_command} #{user} #{command}", options do |ch, stream, out|
 | 
			
		||||
        if out =~ /^Password:/
 | 
			
		||||
          ch.send_data "#{password}\n"
 | 
			
		||||
        elsif out =~ /try again/
 | 
			
		||||
          if prompt_host.nil? || prompt_host == ch[:host]
 | 
			
		||||
            prompt_host = ch[:host]
 | 
			
		||||
            logger.important out, "#{stream} :: #{ch[:host]}"
 | 
			
		||||
            # reset the password to it's original value and prepare for another
 | 
			
		||||
            # pass (the reset allows the password prompt to be attempted again
 | 
			
		||||
            # if the password variable was originally a proc (the default)
 | 
			
		||||
            set :password, self[:original_value][:password] || self[:password]
 | 
			
		||||
          end
 | 
			
		||||
        else
 | 
			
		||||
          block.call(ch, stream, out)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Renders an ERb template and returns the result. This is useful for
 | 
			
		||||
    # dynamically building documents to store on the remote servers.
 | 
			
		||||
    #
 | 
			
		||||
| 
						 | 
				
			
			@ -230,41 +108,5 @@ module Capistrano
 | 
			
		|||
          raise ArgumentError, "no file or template given for rendering"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # An instance-level reader for the class' #default_io_proc attribute.
 | 
			
		||||
    def default_io_proc
 | 
			
		||||
      self.class.default_io_proc
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    def metaclass
 | 
			
		||||
      class << self; self; end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
    
 | 
			
		||||
      def sudo_command
 | 
			
		||||
        configuration[:sudo] || "sudo"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def define_method(name, &block)
 | 
			
		||||
        metaclass.send(:define_method, name, &block)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def method_missing(sym, *args, &block)
 | 
			
		||||
        if @configuration.respond_to?(sym)
 | 
			
		||||
          @configuration.send(sym, *args, &block)
 | 
			
		||||
        else
 | 
			
		||||
          super
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,20 @@ module Capistrano
 | 
			
		|||
 | 
			
		||||
    attr_reader :command, :sessions, :options
 | 
			
		||||
 | 
			
		||||
    def initialize(command, sessions, options={}, &block) #:nodoc:
 | 
			
		||||
    def self.process(command, sessions, options={}, &block)
 | 
			
		||||
      new(command, sessions, options, &block).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(command, sessions, options={}, &block)
 | 
			
		||||
      @command = extract_environment(options) + command.strip.gsub(/\r?\n/, "\\\n")
 | 
			
		||||
      @sessions = sessions
 | 
			
		||||
      @options = options
 | 
			
		||||
| 
						 | 
				
			
			@ -15,10 +28,6 @@ module Capistrano
 | 
			
		|||
      @channels = open_channels
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def logger #:nodoc:
 | 
			
		||||
      options[:logger]
 | 
			
		||||
    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
 | 
			
		||||
    # RuntimeError.
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +68,10 @@ module Capistrano
 | 
			
		|||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
      def logger
 | 
			
		||||
        options[:logger]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def open_channels
 | 
			
		||||
        sessions.map do |session|
 | 
			
		||||
          session.open_channel do |channel|
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
require 'capistrano/logger'
 | 
			
		||||
require 'capistrano/extensions'
 | 
			
		||||
 | 
			
		||||
require 'capistrano/configuration/connections'
 | 
			
		||||
require 'capistrano/configuration/execution'
 | 
			
		||||
require 'capistrano/configuration/loading'
 | 
			
		||||
| 
						 | 
				
			
			@ -7,6 +8,8 @@ require 'capistrano/configuration/namespaces'
 | 
			
		|||
require 'capistrano/configuration/roles'
 | 
			
		||||
require 'capistrano/configuration/variables'
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
| 
						 | 
				
			
			@ -22,5 +25,8 @@ module Capistrano
 | 
			
		|||
    # The includes must come at the bottom, since they may redefine methods
 | 
			
		||||
    # defined in the base class.
 | 
			
		||||
    include Connections, Execution, Loading, Namespaces, Roles, Variables
 | 
			
		||||
 | 
			
		||||
    # Mix in the actions
 | 
			
		||||
    include Actions::FileTransfer, Actions::Invocation
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										37
									
								
								lib/capistrano/configuration/actions/file_transfer.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/capistrano/configuration/actions/file_transfer.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
require 'capistrano/upload'
 | 
			
		||||
 | 
			
		||||
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 <tt>:mode</tt> is specified it is used to
 | 
			
		||||
        # set the mode on the file.
 | 
			
		||||
        def put(data, path, options={})
 | 
			
		||||
          execute_on_servers(options) do |servers|
 | 
			
		||||
            targets = servers.map { |s| sessions[s.host] }
 | 
			
		||||
            Upload.process(targets, path, :data => data, :mode => options[:mode], :logger => logger)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
    
 | 
			
		||||
        # Get file remote_path from FIRST server targetted by
 | 
			
		||||
        # the current task and transfer it to local machine as path. It will use
 | 
			
		||||
        # SFTP if Net::SFTP is installed; otherwise it will fall back to using
 | 
			
		||||
        # 'cat', which may cause corruption in binary files.
 | 
			
		||||
        #
 | 
			
		||||
        # get "#{deploy_to}/current/log/production.log", "log/production.log.web"
 | 
			
		||||
        def get(remote_path, path, options = {})
 | 
			
		||||
          execute_on_servers(options.merge(:once => true)) do |servers|
 | 
			
		||||
            logger.info "downloading `#{servers.first.host}:#{remote_path}' to `#{path}'"
 | 
			
		||||
            sftp = sessions[servers.first.host].sftp
 | 
			
		||||
            sftp.connect unless sftp.state == :open
 | 
			
		||||
            sftp.get_file remote_path, path
 | 
			
		||||
            logger.debug "download finished" 
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										86
									
								
								lib/capistrano/configuration/actions/invocation.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/capistrano/configuration/actions/invocation.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
require 'capistrano/command'
 | 
			
		||||
 | 
			
		||||
module Capistrano
 | 
			
		||||
  class Configuration
 | 
			
		||||
    module Actions
 | 
			
		||||
      module Invocation
 | 
			
		||||
        def self.included(base)
 | 
			
		||||
          base.extend(ClassMethods)
 | 
			
		||||
 | 
			
		||||
          base.default_io_proc = Proc.new do |ch, stream, out|
 | 
			
		||||
            level = stream == :err ? :important : :info
 | 
			
		||||
            ch[:options][:logger].send(level, out, "#{stream} :: #{ch[:host]}")
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        module ClassMethods
 | 
			
		||||
          attr_accessor :default_io_proc
 | 
			
		||||
        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 (<tt>:err</tt> for stderr, and <tt>:out</tt> for
 | 
			
		||||
        # stdout), and the data that was received.
 | 
			
		||||
        #
 | 
			
		||||
        # If +pretend+ mode is active, this does nothing.
 | 
			
		||||
        def run(cmd, options={}, &block)
 | 
			
		||||
          block ||= self.class.default_io_proc
 | 
			
		||||
          logger.debug "executing #{cmd.strip.inspect}"
 | 
			
		||||
 | 
			
		||||
          execute_on_servers(options) do |servers|
 | 
			
		||||
            targets = servers.map { |s| sessions[s.host] }
 | 
			
		||||
            Command.process(cmd, targets, options.merge(:logger => logger), &block)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # Like #run, but executes the command via <tt>sudo</tt>. This assumes
 | 
			
		||||
        # that the sudo password (if required) is the same as the password for
 | 
			
		||||
        # logging in to the server.
 | 
			
		||||
        #
 | 
			
		||||
        # Also, this module accepts a <tt>:sudo</tt> 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"
 | 
			
		||||
        def sudo(command, options={}, &block)
 | 
			
		||||
          block ||= self.class.default_io_proc
 | 
			
		||||
 | 
			
		||||
          options = options.dup
 | 
			
		||||
          as = options.delete(:as)
 | 
			
		||||
 | 
			
		||||
          user = as && "-u #{as}"
 | 
			
		||||
          command = [fetch(:sudo, "sudo"), user, command].compact.join(" ")
 | 
			
		||||
 | 
			
		||||
          run(command, options, &sudo_behavior_callback(block))
 | 
			
		||||
        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 =~ /^Password:/
 | 
			
		||||
              ch.send_data "#{self[:password]}\n"
 | 
			
		||||
            elsif out =~ /try again/
 | 
			
		||||
              if prompt_host.nil? || prompt_host == ch[:host]
 | 
			
		||||
                prompt_host = ch[:host]
 | 
			
		||||
                logger.important out, "#{stream} :: #{ch[:host]}"
 | 
			
		||||
                reset! :password
 | 
			
		||||
              end
 | 
			
		||||
            else
 | 
			
		||||
              fallback.call(ch, stream, out)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +30,10 @@ module Capistrano
 | 
			
		|||
    # the requested data.
 | 
			
		||||
    class Error < RuntimeError; end
 | 
			
		||||
 | 
			
		||||
    def self.process(sessions, filename, options)
 | 
			
		||||
      new(sessions, filename, options).process!
 | 
			
		||||
    end
 | 
			
		||||
  
 | 
			
		||||
    attr_reader :sessions, :filename, :options
 | 
			
		||||
    attr_reader :failed, :completed
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,14 +11,14 @@ module Capistrano
 | 
			
		|||
  # If +require_config+ is not false, an exception will be raised if the current
 | 
			
		||||
  # configuration is not set.
 | 
			
		||||
  def self.configuration(require_config=false)
 | 
			
		||||
    warn "[DEPRECATION] please use Capistrano::Configuration.instance instead of Capistrano.configuration. (You may be using a Capistrano plugin that is using this deprecated syntax.)"
 | 
			
		||||
    warn "[DEPRECATION] use Capistrano::Configuration.instance instead of Capistrano.configuration. (You may be using a Capistrano plugin that is using this deprecated syntax.)"
 | 
			
		||||
    Capistrano::Configuration.instance(require_config)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Used internally by Capistrano to specify the current configuration before
 | 
			
		||||
  # loading a third-party task bundle.
 | 
			
		||||
  def self.configuration=(config)
 | 
			
		||||
    warn "[DEPRECATION] please us Capistrano::Configuration.instance= instead of Capistrano.configuration=."
 | 
			
		||||
    warn "[DEPRECATION] use Capistrano::Configuration.instance= instead of Capistrano.configuration=."
 | 
			
		||||
    Capistrano::Configuration.instance = config
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,8 +19,8 @@ module Capistrano
 | 
			
		|||
    end
 | 
			
		||||
 | 
			
		||||
    MAJOR = 1
 | 
			
		||||
    MINOR = 4
 | 
			
		||||
    TINY  = 1
 | 
			
		||||
    MINOR = 99
 | 
			
		||||
    TINY  = 0
 | 
			
		||||
 | 
			
		||||
    STRING = [MAJOR, MINOR, TINY].join(".")
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								test/configuration/actions/file_transfer_test.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								test/configuration/actions/file_transfer_test.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
require "#{File.dirname(__FILE__)}/../../utils"
 | 
			
		||||
require 'capistrano/configuration/actions/file_transfer'
 | 
			
		||||
 | 
			
		||||
class ConfigurationActionsFileTransferTest < Test::Unit::TestCase
 | 
			
		||||
  class MockConfig
 | 
			
		||||
    include Capistrano::Configuration::Actions::FileTransfer
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def setup
 | 
			
		||||
    @config = MockConfig.new
 | 
			
		||||
    @config.stubs(:logger).returns(stub_everything)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_put_should_pass_options_to_execute_on_servers
 | 
			
		||||
    @config.expects(:execute_on_servers).with(:foo => "bar")
 | 
			
		||||
    @config.put("some data", "test.txt", :foo => "bar")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_put_should_delegate_to_Upload_process
 | 
			
		||||
    @config.expects(:execute_on_servers).yields(%w(s1 s2 s3).map { |s| mock(:host => s) })
 | 
			
		||||
    @config.expects(:sessions).times(3).returns(Hash.new{|h,k| h[k] = k.to_sym})
 | 
			
		||||
    Capistrano::Upload.expects(:process).with([:s1,:s2,:s3], "test.txt", :data => "some data", :mode => 0777, :logger => @config.logger)
 | 
			
		||||
    @config.put("some data", "test.txt", :mode => 0777)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_get_should_pass_options_execute_on_servers_including_once
 | 
			
		||||
    @config.expects(:execute_on_servers).with(:foo => "bar", :once => true)
 | 
			
		||||
    @config.get("test.txt", "test.txt", :foo => "bar")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_get_should_use_sftp_get_file_to_local_path
 | 
			
		||||
    sftp = mock("sftp", :state => :closed, :connect => true)
 | 
			
		||||
    sftp.expects(:get_file).with("remote.txt", "local.txt")
 | 
			
		||||
    @config.expects(:execute_on_servers).yields([stub("server", :host => "capistrano")])
 | 
			
		||||
    @config.expects(:sessions).returns("capistrano" => mock("session", :sftp => sftp))
 | 
			
		||||
    @config.get("remote.txt", "local.txt")
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										144
									
								
								test/configuration/actions/invocation_test.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								test/configuration/actions/invocation_test.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
require "#{File.dirname(__FILE__)}/../../utils"
 | 
			
		||||
require 'capistrano/configuration/actions/invocation'
 | 
			
		||||
 | 
			
		||||
class ConfigurationActionsRunTest < Test::Unit::TestCase
 | 
			
		||||
  class MockConfig
 | 
			
		||||
    attr_reader :options
 | 
			
		||||
 | 
			
		||||
    def initialize
 | 
			
		||||
      @options = {}
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def [](*args)
 | 
			
		||||
      @options[*args]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def fetch(*args)
 | 
			
		||||
      @options.fetch(*args)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    include Capistrano::Configuration::Actions::Invocation
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def setup
 | 
			
		||||
    @config = MockConfig.new
 | 
			
		||||
    @original_io_proc = MockConfig.default_io_proc
 | 
			
		||||
    @config.stubs(:logger).returns(stub_everything)
 | 
			
		||||
  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_without_block_should_use_default_io_proc
 | 
			
		||||
    @config.expects(:execute_on_servers).yields(%w(s1 s2 s3).map { |s| mock(:host => s) })
 | 
			
		||||
    @config.expects(:sessions).returns(Hash.new { |h,k| h[k] = k.to_sym }).times(3)
 | 
			
		||||
    prepare_command("ls", [:s1, :s2, :s3], {:logger => @config.logger})
 | 
			
		||||
    MockConfig.default_io_proc = inspectable_proc
 | 
			
		||||
    @config.run "ls"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_run_with_block_should_use_block
 | 
			
		||||
    @config.expects(:execute_on_servers).yields(%w(s1 s2 s3).map { |s| mock(:host => s) })
 | 
			
		||||
    @config.expects(:sessions).returns(Hash.new { |h,k| h[k] = k.to_sym }).times(3)
 | 
			
		||||
    prepare_command("ls", [:s1, :s2, :s3], {:logger => @config.logger})
 | 
			
		||||
    MockConfig.default_io_proc = Proc.new { |a,b,c| raise "shouldn't get here" }
 | 
			
		||||
    @config.run("ls", &inspectable_proc)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_default_io_proc_should_log_stdout_arguments_as_info
 | 
			
		||||
    ch = { :host => "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",
 | 
			
		||||
           :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 ls", {})
 | 
			
		||||
    @config.sudo "ls"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_sudo_should_use_sudo_variable_definition
 | 
			
		||||
    @config.expects(:run).with("/opt/local/bin/sudo 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 -u app ls", {})
 | 
			
		||||
    @config.sudo "ls", :as => "app"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_sudo_should_pass_options_through_to_run
 | 
			
		||||
    @config.expects(:run).with("sudo ls", :foo => "bar")
 | 
			
		||||
    @config.sudo "ls", :foo => "bar"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_sudo_behavior_callback_should_send_password_when_prompted
 | 
			
		||||
    ch = mock("channel")
 | 
			
		||||
    ch.expects(:send_data).with("g00b3r\n")
 | 
			
		||||
    @config.options[:password] = "g00b3r"
 | 
			
		||||
    @config.sudo_behavior_callback(nil)[ch, nil, "Password: "]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_sudo_behavior_callback_with_incorrect_password_on_first_prompt
 | 
			
		||||
    ch = mock("channel")
 | 
			
		||||
    ch.stubs(:[]).with(:host).returns("capistrano")
 | 
			
		||||
    @config.expects(:reset!).with(:password)
 | 
			
		||||
    @config.sudo_behavior_callback(nil)[ch, nil, "blah blah try again blah blah"]
 | 
			
		||||
  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")
 | 
			
		||||
    ch2 = mock("channel")
 | 
			
		||||
    ch2.stubs(:[]).with(:host).returns("cap2")
 | 
			
		||||
 | 
			
		||||
    @config.expects(:reset!).with(:password).times(2)
 | 
			
		||||
 | 
			
		||||
    callback[ch, nil, "blah blah try again blah blah"]
 | 
			
		||||
    callback[ch2, nil, "blah blah try again blah blah"] # shouldn't call reset!
 | 
			
		||||
    callback[ch, nil, "blah blah try again blah blah"]
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
      Capistrano::Command.expects(:process).with(command, sessions, options).yields(a, b, c)
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,7 @@
 | 
			
		|||
require "#{File.dirname(__FILE__)}/utils"
 | 
			
		||||
# if the following is uncommented, the capistrano gem gets loaded if it is
 | 
			
		||||
# installed, for some reason...not sure why :(
 | 
			
		||||
# require 'capistrano/configuration'
 | 
			
		||||
 | 
			
		||||
class ConfigurationTest < Test::Unit::TestCase
 | 
			
		||||
  def setup
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,11 @@ class UploadTest < Test::Unit::TestCase
 | 
			
		|||
    Capistrano::Upload.new(sessions, "test.txt", :data => "data")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_self_process_should_instantiate_uploader_and_start_process
 | 
			
		||||
    Capistrano::Upload.expects(:new).with([:s1, :s2], "test.txt", :data => "data").returns(mock(:process! => nil))
 | 
			
		||||
    Capistrano::Upload.process([:s1, :s2], "test.txt", :data => "data")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_process_when_sftp_open_fails_should_raise_error
 | 
			
		||||
    channel = mock("channel")
 | 
			
		||||
    channel.expects(:[]=).with(:done, true)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue