switchtower 0.10.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/switchtower/actor.rb +51 -20
- data/lib/switchtower/cli.rb +1 -1
- data/lib/switchtower/command.rb +1 -2
- data/lib/switchtower/configuration.rb +49 -7
- data/lib/switchtower/extensions.rb +38 -0
- data/lib/switchtower/gateway.rb +15 -3
- data/lib/switchtower/generators/rails/deployment/templates/switchtower.rake +6 -2
- data/lib/switchtower/logger.rb +8 -5
- data/lib/switchtower/recipes/standard.rb +49 -10
- data/lib/switchtower/scm/base.rb +2 -2
- data/lib/switchtower/scm/baz.rb +118 -0
- data/lib/switchtower/scm/bzr.rb +70 -0
- data/lib/switchtower/scm/cvs.rb +57 -6
- data/lib/switchtower/scm/perforce.rb +139 -0
- data/lib/switchtower/scm/subversion.rb +8 -3
- data/lib/switchtower/transfer.rb +1 -1
- data/lib/switchtower/utils.rb +26 -0
- data/lib/switchtower/version.rb +3 -3
- data/test/actor_test.rb +34 -1
- data/test/configuration_test.rb +16 -1
- data/test/fixtures/custom.rb +3 -0
- data/test/scm/cvs_test.rb +24 -2
- data/test/scm/subversion_test.rb +17 -2
- data/test/utils.rb +8 -0
- metadata +9 -3
    
        data/lib/switchtower/actor.rb
    CHANGED
    
    | @@ -3,6 +3,7 @@ require 'switchtower/command' | |
| 3 3 | 
             
            require 'switchtower/transfer'
         | 
| 4 4 | 
             
            require 'switchtower/gateway'
         | 
| 5 5 | 
             
            require 'switchtower/ssh'
         | 
| 6 | 
            +
            require 'switchtower/utils'
         | 
| 6 7 |  | 
| 7 8 | 
             
            module SwitchTower
         | 
| 8 9 |  | 
| @@ -29,12 +30,18 @@ module SwitchTower | |
| 29 30 | 
             
                  attr_accessor :connection_factory
         | 
| 30 31 | 
             
                  attr_accessor :command_factory
         | 
| 31 32 | 
             
                  attr_accessor :transfer_factory
         | 
| 33 | 
            +
                  attr_accessor :default_io_proc
         | 
| 32 34 | 
             
                end
         | 
| 33 35 |  | 
| 34 36 | 
             
                self.connection_factory = DefaultConnectionFactory
         | 
| 35 37 | 
             
                self.command_factory = Command
         | 
| 36 38 | 
             
                self.transfer_factory = Transfer
         | 
| 37 39 |  | 
| 40 | 
            +
                self.default_io_proc = Proc.new do |ch, stream, out|
         | 
| 41 | 
            +
                  level = out == :error ? :important : :info
         | 
| 42 | 
            +
                  ch[:actor].logger.send(level, out, "#{stream} :: #{ch[:host]}")
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 38 45 | 
             
                # The configuration instance associated with this actor.
         | 
| 39 46 | 
             
                attr_reader :configuration
         | 
| 40 47 |  | 
| @@ -62,18 +69,22 @@ module SwitchTower | |
| 62 69 |  | 
| 63 70 | 
             
                # Represents the definition of a single task.
         | 
| 64 71 | 
             
                class Task #:nodoc:
         | 
| 65 | 
            -
                  attr_reader :name, :options
         | 
| 72 | 
            +
                  attr_reader :name, :actor, :options
         | 
| 66 73 |  | 
| 67 | 
            -
                  def initialize(name, options)
         | 
| 68 | 
            -
                    @name, @options = name, options
         | 
| 74 | 
            +
                  def initialize(name, actor, options)
         | 
| 75 | 
            +
                    @name, @actor, @options = name, actor, options
         | 
| 69 76 | 
             
                    @servers = nil
         | 
| 70 77 | 
             
                  end
         | 
| 71 78 |  | 
| 72 79 | 
             
                  # Returns the list of servers (_not_ connections to servers) that are
         | 
| 73 80 | 
             
                  # the target of this task.
         | 
| 74 | 
            -
                  def servers | 
| 81 | 
            +
                  def servers
         | 
| 75 82 | 
             
                    unless @servers
         | 
| 76 | 
            -
                      roles = [*(@options[:roles] || configuration.roles.keys)]. | 
| 83 | 
            +
                      roles = [*(@options[:roles] || actor.configuration.roles.keys)].
         | 
| 84 | 
            +
                        map { |name|
         | 
| 85 | 
            +
                          actor.configuration.roles[name] or
         | 
| 86 | 
            +
                            raise ArgumentError, "task #{self.name.inspect} references non-existant role #{name.inspect}"
         | 
| 87 | 
            +
                        }.flatten
         | 
| 77 88 | 
             
                      only  = @options[:only] || {}
         | 
| 78 89 |  | 
| 79 90 | 
             
                      unless only.empty?
         | 
| @@ -105,10 +116,10 @@ module SwitchTower | |
| 105 116 | 
             
                # Define a new task for this actor. The block will be invoked when this
         | 
| 106 117 | 
             
                # task is called.
         | 
| 107 118 | 
             
                def define_task(name, options={}, &block)
         | 
| 108 | 
            -
                  @tasks[name] = Task.new(name, options)
         | 
| 119 | 
            +
                  @tasks[name] = (options[:task_class] || Task).new(name, self, options)
         | 
| 109 120 | 
             
                  define_method(name) do
         | 
| 110 121 | 
             
                    send "before_#{name}" if respond_to? "before_#{name}"
         | 
| 111 | 
            -
                    logger. | 
| 122 | 
            +
                    logger.debug "executing task #{name}"
         | 
| 112 123 | 
             
                    begin
         | 
| 113 124 | 
             
                      push_task_call_frame name
         | 
| 114 125 | 
             
                      result = instance_eval(&block)
         | 
| @@ -129,10 +140,7 @@ module SwitchTower | |
| 129 140 | 
             
                #
         | 
| 130 141 | 
             
                # If +pretend+ mode is active, this does nothing.
         | 
| 131 142 | 
             
                def run(cmd, options={}, &block)
         | 
| 132 | 
            -
                  block ||=  | 
| 133 | 
            -
                    logger.debug(out, "#{stream} :: #{ch[:host]}")
         | 
| 134 | 
            -
                  end
         | 
| 135 | 
            -
             | 
| 143 | 
            +
                  block ||= default_io_proc
         | 
| 136 144 | 
             
                  logger.debug "executing #{cmd.strip.inspect}"
         | 
| 137 145 |  | 
| 138 146 | 
             
                  execute_on_servers(options) do |servers|
         | 
| @@ -176,9 +184,7 @@ module SwitchTower | |
| 176 184 | 
             
                # the sudo password (if required) is the same as the password for logging
         | 
| 177 185 | 
             
                # in to the server.
         | 
| 178 186 | 
             
                def sudo(command, options={}, &block)
         | 
| 179 | 
            -
                  block ||=  | 
| 180 | 
            -
                    logger.debug(out, "#{stream} :: #{ch[:host]}")
         | 
| 181 | 
            -
                  end
         | 
| 187 | 
            +
                  block ||= default_io_proc
         | 
| 182 188 |  | 
| 183 189 | 
             
                  # in order to prevent _each host_ from prompting when the password was
         | 
| 184 190 | 
             
                  # wrong, let's track which host prompted first and only allow subsequent
         | 
| @@ -320,11 +326,29 @@ module SwitchTower | |
| 320 326 | 
             
                  task_call_frames.last.rollback = block
         | 
| 321 327 | 
             
                end
         | 
| 322 328 |  | 
| 323 | 
            -
                 | 
| 329 | 
            +
                # An instance-level reader for the class' #default_io_proc attribute.
         | 
| 330 | 
            +
                def default_io_proc
         | 
| 331 | 
            +
                  self.class.default_io_proc
         | 
| 332 | 
            +
                end
         | 
| 324 333 |  | 
| 325 | 
            -
             | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 334 | 
            +
                # Used to force connections to be made to the current task's servers.
         | 
| 335 | 
            +
                # Connections are normally made lazily in SwitchTower--you can use this
         | 
| 336 | 
            +
                # to force them open before performing some operation that might be
         | 
| 337 | 
            +
                # time-sensitive.
         | 
| 338 | 
            +
                def connect!(options={})
         | 
| 339 | 
            +
                  execute_on_servers(options) { }
         | 
| 340 | 
            +
                end
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                def current_task
         | 
| 343 | 
            +
                  return nil if task_call_frames.empty?
         | 
| 344 | 
            +
                  tasks[task_call_frames.last.name]
         | 
| 345 | 
            +
                end
         | 
| 346 | 
            +
             | 
| 347 | 
            +
                def metaclass
         | 
| 348 | 
            +
                  class << self; self; end
         | 
| 349 | 
            +
                end
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                private
         | 
| 328 352 |  | 
| 329 353 | 
             
                  def define_method(name, &block)
         | 
| 330 354 | 
             
                    metaclass.send(:define_method, name, &block)
         | 
| @@ -358,8 +382,14 @@ module SwitchTower | |
| 358 382 | 
             
                  end
         | 
| 359 383 |  | 
| 360 384 | 
             
                  def execute_on_servers(options)
         | 
| 361 | 
            -
                     | 
| 362 | 
            -
                    servers = servers | 
| 385 | 
            +
                    task = current_task
         | 
| 386 | 
            +
                    servers = task.servers
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                    if servers.empty?
         | 
| 389 | 
            +
                      raise "The #{task.name} task is only run for servers matching #{task.options.inspect}, but no servers matched"
         | 
| 390 | 
            +
                    end
         | 
| 391 | 
            +
             | 
| 392 | 
            +
                    servers = [servers.first] if options[:once]
         | 
| 363 393 | 
             
                    logger.trace "servers: #{servers.inspect}"
         | 
| 364 394 |  | 
| 365 395 | 
             
                    if !pretend
         | 
| @@ -376,5 +406,6 @@ module SwitchTower | |
| 376 406 | 
             
                      super
         | 
| 377 407 | 
             
                    end
         | 
| 378 408 | 
             
                  end
         | 
| 409 | 
            +
             | 
| 379 410 | 
             
              end
         | 
| 380 411 | 
             
            end
         | 
    
        data/lib/switchtower/cli.rb
    CHANGED
    
    | @@ -86,7 +86,7 @@ module SwitchTower | |
| 86 86 | 
             
                #   require 'switchtower/cli'
         | 
| 87 87 | 
             
                #   config = SwitchTower::Configuration.new
         | 
| 88 88 | 
             
                #   config.logger_level = SwitchTower::Logger::TRACE
         | 
| 89 | 
            -
                #   config.set | 
| 89 | 
            +
                #   config.set(:password) { SwitchTower::CLI.password_prompt }
         | 
| 90 90 | 
             
                #   config.load "standard", "config/deploy"
         | 
| 91 91 | 
             
                #   config.actor.update_code
         | 
| 92 92 | 
             
                #
         | 
    
        data/lib/switchtower/command.rb
    CHANGED
    
    | @@ -22,8 +22,6 @@ module SwitchTower | |
| 22 22 | 
             
                # fails (non-zero return code) on any of the hosts, this will raise a
         | 
| 23 23 | 
             
                # RuntimeError.
         | 
| 24 24 | 
             
                def process!
         | 
| 25 | 
            -
                  logger.debug "processing command"
         | 
| 26 | 
            -
             | 
| 27 25 | 
             
                  since = Time.now
         | 
| 28 26 | 
             
                  loop do
         | 
| 29 27 | 
             
                    active = 0
         | 
| @@ -56,6 +54,7 @@ module SwitchTower | |
| 56 54 | 
             
                    @servers.map do |server|
         | 
| 57 55 | 
             
                      @actor.sessions[server].open_channel do |channel|
         | 
| 58 56 | 
             
                        channel[:host] = server
         | 
| 57 | 
            +
                        channel[:actor] = @actor # so callbacks can access the actor instance
         | 
| 59 58 | 
             
                        channel.request_pty :want_reply => true
         | 
| 60 59 |  | 
| 61 60 | 
             
                        channel.on_success do |ch|
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            require 'switchtower/actor'
         | 
| 2 2 | 
             
            require 'switchtower/logger'
         | 
| 3 3 | 
             
            require 'switchtower/scm/subversion'
         | 
| 4 | 
            +
            require 'switchtower/extensions'
         | 
| 4 5 |  | 
| 5 6 | 
             
            module SwitchTower
         | 
| 6 7 | 
             
              # Represents a specific SwitchTower configuration. A Configuration instance
         | 
| @@ -29,6 +30,9 @@ module SwitchTower | |
| 29 30 | 
             
                # determining the release path.
         | 
| 30 31 | 
             
                attr_reader :now
         | 
| 31 32 |  | 
| 33 | 
            +
                # The has of variables currently known by the configuration
         | 
| 34 | 
            +
                attr_reader :variables
         | 
| 35 | 
            +
             | 
| 32 36 | 
             
                def initialize(actor_class=Actor) #:nodoc:
         | 
| 33 37 | 
             
                  @roles = Hash.new { |h,k| h[k] = [] }
         | 
| 34 38 | 
             
                  @actor = actor_class.new(self)
         | 
| @@ -48,18 +52,28 @@ module SwitchTower | |
| 48 52 |  | 
| 49 53 | 
             
                  set :ssh_options, Hash.new
         | 
| 50 54 |  | 
| 51 | 
            -
                  set | 
| 55 | 
            +
                  set(:deploy_to)   { "/u/apps/#{application}" }
         | 
| 52 56 |  | 
| 53 57 | 
             
                  set :version_dir, DEFAULT_VERSION_DIR_NAME
         | 
| 54 58 | 
             
                  set :current_dir, DEFAULT_CURRENT_DIR_NAME
         | 
| 55 59 | 
             
                  set :shared_dir,  DEFAULT_SHARED_DIR_NAME
         | 
| 56 60 | 
             
                  set :scm,         :subversion
         | 
| 57 61 |  | 
| 58 | 
            -
                  set | 
| 62 | 
            +
                  set(:revision)    { source.latest_revision }
         | 
| 59 63 | 
             
                end
         | 
| 60 64 |  | 
| 61 65 | 
             
                # Set a variable to the given value.
         | 
| 62 | 
            -
                def set(variable, value)
         | 
| 66 | 
            +
                def set(variable, value=nil, &block)
         | 
| 67 | 
            +
                  # if the variable is uppercase, then we add it as a constant to the
         | 
| 68 | 
            +
                  # actor. This is to allow uppercase "variables" to be set and referenced
         | 
| 69 | 
            +
                  # in recipes.
         | 
| 70 | 
            +
                  if variable.to_s[0].between?(?A, ?Z)
         | 
| 71 | 
            +
                    klass = @actor.metaclass
         | 
| 72 | 
            +
                    klass.send(:remove_const, variable) if klass.const_defined?(variable)
         | 
| 73 | 
            +
                    klass.const_set(variable, value)
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  value = block if value.nil? && block_given?
         | 
| 63 77 | 
             
                  @variables[variable] = value
         | 
| 64 78 | 
             
                end
         | 
| 65 79 |  | 
| @@ -103,11 +117,19 @@ module SwitchTower | |
| 103 117 | 
             
                #
         | 
| 104 118 | 
             
                #   load(:string => "set :scm, :subversion"):
         | 
| 105 119 | 
             
                #     Load the given string as a configuration specification.
         | 
| 106 | 
            -
                 | 
| 120 | 
            +
                #
         | 
| 121 | 
            +
                #   load { ... }
         | 
| 122 | 
            +
                #     Load the block in the context of the configuration.
         | 
| 123 | 
            +
                def load(*args, &block)
         | 
| 107 124 | 
             
                  options = args.last.is_a?(Hash) ? args.pop : {}
         | 
| 108 125 | 
             
                  args.each { |arg| load options.merge(:file => arg) }
         | 
| 126 | 
            +
                  return unless args.empty?
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  if block
         | 
| 129 | 
            +
                    raise "loading a block requires 0 parameters" unless args.empty?
         | 
| 130 | 
            +
                    load(options.merge(:proc => block))
         | 
| 109 131 |  | 
| 110 | 
            -
                   | 
| 132 | 
            +
                  elsif options[:file]
         | 
| 111 133 | 
             
                    file = options[:file]
         | 
| 112 134 | 
             
                    unless file[0] == ?/
         | 
| 113 135 | 
             
                      load_paths.each do |path|
         | 
| @@ -122,9 +144,17 @@ module SwitchTower | |
| 122 144 | 
             
                    end
         | 
| 123 145 |  | 
| 124 146 | 
             
                    load :string => File.read(file), :name => options[:name] || file
         | 
| 147 | 
            +
             | 
| 125 148 | 
             
                  elsif options[:string]
         | 
| 126 | 
            -
                    logger. | 
| 127 | 
            -
                    instance_eval | 
| 149 | 
            +
                    logger.trace "loading configuration #{options[:name] || "<eval>"}"
         | 
| 150 | 
            +
                    instance_eval(options[:string], options[:name] || "<eval>")
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  elsif options[:proc]
         | 
| 153 | 
            +
                    logger.trace "loading configuration #{options[:proc].inspect}"
         | 
| 154 | 
            +
                    instance_eval(&options[:proc])
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  else
         | 
| 157 | 
            +
                    raise ArgumentError, "don't know how to load #{options.inspect}"
         | 
| 128 158 | 
             
                  end
         | 
| 129 159 | 
             
                end
         | 
| 130 160 |  | 
| @@ -164,6 +194,18 @@ module SwitchTower | |
| 164 194 | 
             
                  actor.define_task(name, options, &block)
         | 
| 165 195 | 
             
                end
         | 
| 166 196 |  | 
| 197 | 
            +
                # Require another file. This is identical to the standard require method,
         | 
| 198 | 
            +
                # with the exception that it sets the reciever as the "current" configuration
         | 
| 199 | 
            +
                # so that third-party task bundles can include themselves relative to
         | 
| 200 | 
            +
                # that configuration.
         | 
| 201 | 
            +
                def require(*args) #:nodoc:
         | 
| 202 | 
            +
                  original, SwitchTower.configuration = SwitchTower.configuration, self
         | 
| 203 | 
            +
                  super
         | 
| 204 | 
            +
                ensure
         | 
| 205 | 
            +
                  # restore the original, so that require's can be nested
         | 
| 206 | 
            +
                  SwitchTower.configuration = original
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
             | 
| 167 209 | 
             
                # Return the path into which releases should be deployed.
         | 
| 168 210 | 
             
                def releases_path
         | 
| 169 211 | 
             
                  File.join(deploy_to, version_dir)
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            require 'switchtower/actor'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module SwitchTower
         | 
| 4 | 
            +
              class ExtensionProxy
         | 
| 5 | 
            +
                def initialize(actor, mod)
         | 
| 6 | 
            +
                  @actor = actor
         | 
| 7 | 
            +
                  extend(mod)
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def method_missing(sym, *args, &block)
         | 
| 11 | 
            +
                  @actor.send(sym, *args, &block)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              EXTENSIONS = {}
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def self.plugin(name, mod)
         | 
| 18 | 
            +
                return false if EXTENSIONS.has_key?(name)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                SwitchTower::Actor.class_eval <<-STR, __FILE__, __LINE__+1
         | 
| 21 | 
            +
                  def #{name}
         | 
| 22 | 
            +
                    @__#{name}_proxy ||= SwitchTower::ExtensionProxy.new(self, SwitchTower::EXTENSIONS[#{name.inspect}])
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                STR
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                EXTENSIONS[name] = mod
         | 
| 27 | 
            +
                return true
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def self.remove_plugin(name)
         | 
| 31 | 
            +
                if EXTENSIONS.delete(name)
         | 
| 32 | 
            +
                  SwitchTower::Actor.send(:remove_method, name)
         | 
| 33 | 
            +
                  return true
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                return false
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
    
        data/lib/switchtower/gateway.rb
    CHANGED
    
    | @@ -25,11 +25,14 @@ module SwitchTower | |
| 25 25 | 
             
                # The Net::SSH session representing the gateway connection.
         | 
| 26 26 | 
             
                attr_reader :session
         | 
| 27 27 |  | 
| 28 | 
            +
                MAX_PORT = 65535
         | 
| 29 | 
            +
                MIN_PORT = 1024
         | 
| 30 | 
            +
             | 
| 28 31 | 
             
                def initialize(server, config) #:nodoc:
         | 
| 29 32 | 
             
                  @config = config
         | 
| 30 33 | 
             
                  @pending_forward_requests = {}
         | 
| 31 34 | 
             
                  @mutex = Mutex.new
         | 
| 32 | 
            -
                  @next_port =  | 
| 35 | 
            +
                  @next_port = MAX_PORT
         | 
| 33 36 | 
             
                  @terminate_thread = false
         | 
| 34 37 |  | 
| 35 38 | 
             
                  waiter = ConditionVariable.new
         | 
| @@ -79,6 +82,13 @@ module SwitchTower | |
| 79 82 |  | 
| 80 83 | 
             
                private
         | 
| 81 84 |  | 
| 85 | 
            +
                  def next_port
         | 
| 86 | 
            +
                    port = @next_port
         | 
| 87 | 
            +
                    @next_port -= 1
         | 
| 88 | 
            +
                    @next_port = MAX_PORT if @next_port < MIN_PORT
         | 
| 89 | 
            +
                    port
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 82 92 | 
             
                  def process_next_pending_connection_request
         | 
| 83 93 | 
             
                    @mutex.synchronize do
         | 
| 84 94 | 
             
                      key = @pending_forward_requests.keys.detect { |k| ConditionVariable === @pending_forward_requests[k] } or return
         | 
| @@ -86,14 +96,16 @@ module SwitchTower | |
| 86 96 |  | 
| 87 97 | 
             
                      @config.logger.trace "establishing connection to #{key} via gateway"
         | 
| 88 98 |  | 
| 89 | 
            -
                      port =  | 
| 90 | 
            -
                      @next_port += 1
         | 
| 99 | 
            +
                      port = next_port
         | 
| 91 100 |  | 
| 92 101 | 
             
                      begin
         | 
| 93 102 | 
             
                        @session.forward.local(port, key, 22)
         | 
| 94 103 | 
             
                        @pending_forward_requests[key] = SSH.connect('127.0.0.1', @config,
         | 
| 95 104 | 
             
                          port)
         | 
| 96 105 | 
             
                        @config.logger.trace "connection to #{key} via gateway established"
         | 
| 106 | 
            +
                      rescue Errno::EADDRINUSE
         | 
| 107 | 
            +
                        port = next_port
         | 
| 108 | 
            +
                        retry
         | 
| 97 109 | 
             
                      rescue Object
         | 
| 98 110 | 
             
                        @pending_forward_requests[key] = nil
         | 
| 99 111 | 
             
                        raise
         | 
| @@ -10,7 +10,11 @@ def switchtower_invoke(*actions) | |
| 10 10 | 
             
                # no rubygems to load, so we fail silently
         | 
| 11 11 | 
             
              end
         | 
| 12 12 |  | 
| 13 | 
            -
               | 
| 13 | 
            +
              options = actions.last.is_a?(Hash) ? actions.pop : {}
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              args = %w[-r config/deploy]
         | 
| 16 | 
            +
              verbose = options[:verbose] || "-vvvvv"
         | 
| 17 | 
            +
              args << verbose
         | 
| 14 18 |  | 
| 15 19 | 
             
              args = %w[-vvvvv -r config/<%= recipe_file %>]
         | 
| 16 20 | 
             
              args.concat(actions.map { |act| ["-a", act.to_s] }.flatten)
         | 
| @@ -34,7 +38,7 @@ end | |
| 34 38 |  | 
| 35 39 | 
             
            desc "Enumerate all available deployment tasks"
         | 
| 36 40 | 
             
            task :show_deploy_tasks do
         | 
| 37 | 
            -
              switchtower_invoke :show_tasks
         | 
| 41 | 
            +
              switchtower_invoke :show_tasks, :verbose => ""
         | 
| 38 42 | 
             
            end
         | 
| 39 43 |  | 
| 40 44 | 
             
            desc "Execute a specific action using switchtower"
         | 
    
        data/lib/switchtower/logger.rb
    CHANGED
    
    | @@ -6,6 +6,8 @@ module SwitchTower | |
| 6 6 | 
             
                INFO      = 1
         | 
| 7 7 | 
             
                DEBUG     = 2
         | 
| 8 8 | 
             
                TRACE     = 3
         | 
| 9 | 
            +
                
         | 
| 10 | 
            +
                MAX_LEVEL = 3
         | 
| 9 11 |  | 
| 10 12 | 
             
                def initialize(options={})
         | 
| 11 13 | 
             
                  output = options[:output] || STDERR
         | 
| @@ -27,12 +29,13 @@ module SwitchTower | |
| 27 29 |  | 
| 28 30 | 
             
                def log(level, message, line_prefix=nil)
         | 
| 29 31 | 
             
                  if level <= self.level
         | 
| 30 | 
            -
                     | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 32 | 
            +
                    indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
         | 
| 33 | 
            +
                    message.split(/\r?\n/).each do |line|
         | 
| 34 | 
            +
                      if line_prefix
         | 
| 35 | 
            +
                        @device.print "#{indent} [#{line_prefix}] #{line.strip}\n"
         | 
| 36 | 
            +
                      else
         | 
| 37 | 
            +
                        @device.puts "#{indent} #{line.strip}\n"
         | 
| 33 38 | 
             
                      end
         | 
| 34 | 
            -
                    else
         | 
| 35 | 
            -
                      @device.puts message.strip
         | 
| 36 39 | 
             
                    end
         | 
| 37 40 | 
             
                  end
         | 
| 38 41 | 
             
                end
         | 
| @@ -11,10 +11,15 @@ | |
| 11 11 |  | 
| 12 12 | 
             
            set :rake, "rake"
         | 
| 13 13 |  | 
| 14 | 
            +
            set :rails_env, :production
         | 
| 15 | 
            +
             | 
| 14 16 | 
             
            set :migrate_target, :current
         | 
| 15 17 | 
             
            set :migrate_env, ""
         | 
| 16 18 |  | 
| 17 | 
            -
            set : | 
| 19 | 
            +
            set :use_sudo, true
         | 
| 20 | 
            +
            set(:run_method) { use_sudo ? :sudo : :run }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            set :spinner_user, :app
         | 
| 18 23 |  | 
| 19 24 | 
             
            desc "Enumerate and describe every available task."
         | 
| 20 25 | 
             
            task :show_tasks do
         | 
| @@ -98,12 +103,12 @@ task :symlink, :roles => [:app, :db, :web] do | |
| 98 103 | 
             
            end
         | 
| 99 104 |  | 
| 100 105 | 
             
            desc <<-DESC
         | 
| 101 | 
            -
            Restart the FCGI processes on the app server. This uses the : | 
| 102 | 
            -
            variable to determine whether to use sudo or not. By default, : | 
| 103 | 
            -
            set to  | 
| 106 | 
            +
            Restart the FCGI processes on the app server. This uses the :use_sudo
         | 
| 107 | 
            +
            variable to determine whether to use sudo or not. By default, :use_sudo is
         | 
| 108 | 
            +
            set to true, but you can set it to false if you are in a shared environment.
         | 
| 104 109 | 
             
            DESC
         | 
| 105 110 | 
             
            task :restart, :roles => :app do
         | 
| 106 | 
            -
              send( | 
| 111 | 
            +
              send(run_method, "#{current_path}/script/process/reaper")
         | 
| 107 112 | 
             
            end
         | 
| 108 113 |  | 
| 109 114 | 
             
            desc <<-DESC
         | 
| @@ -126,7 +131,7 @@ task :migrate, :roles => :db, :only => { :primary => true } do | |
| 126 131 | 
             
              end
         | 
| 127 132 |  | 
| 128 133 | 
             
              run "cd #{directory} && " +
         | 
| 129 | 
            -
                  "#{rake} RAILS_ENV | 
| 134 | 
            +
                  "#{rake} RAILS_ENV=#{rails_env} #{migrate_env} migrate"
         | 
| 130 135 | 
             
            end
         | 
| 131 136 |  | 
| 132 137 | 
             
            desc <<-DESC
         | 
| @@ -189,7 +194,8 @@ end | |
| 189 194 | 
             
            desc <<-DESC
         | 
| 190 195 | 
             
            Removes unused releases from the releases directory. By default, the last 5
         | 
| 191 196 | 
             
            releases are retained, but this can be configured with the 'keep_releases'
         | 
| 192 | 
            -
            variable.
         | 
| 197 | 
            +
            variable. This will use sudo to do the delete by default, but you can specify
         | 
| 198 | 
            +
            that run should be used by setting the :use_sudo variable to false.
         | 
| 193 199 | 
             
            DESC
         | 
| 194 200 | 
             
            task :cleanup do
         | 
| 195 201 | 
             
              count = (self[:keep_releases] || 5).to_i
         | 
| @@ -197,9 +203,42 @@ task :cleanup do | |
| 197 203 | 
             
                logger.important "no old releases to clean up"
         | 
| 198 204 | 
             
              else
         | 
| 199 205 | 
             
                logger.info "keeping #{count} of #{releases.length} deployed releases"
         | 
| 200 | 
            -
                 | 
| 206 | 
            +
                directories = (releases - releases.last(count)).map { |release|
         | 
| 201 207 | 
             
                  File.join(releases_path, release) }.join(" ")
         | 
| 202 208 |  | 
| 203 | 
            -
                 | 
| 209 | 
            +
                send(run_method, "rm -rf #{directories}")
         | 
| 204 210 | 
             
              end
         | 
| 205 | 
            -
            end
         | 
| 211 | 
            +
            end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
            desc <<-DESC
         | 
| 214 | 
            +
            Start the spinner daemon for the application (requires script/spin). This will
         | 
| 215 | 
            +
            use sudo to start the spinner by default, unless :use_sudo is false. If using
         | 
| 216 | 
            +
            sudo, you can specify the user that the spinner ought to run as by setting the
         | 
| 217 | 
            +
            :spinner_user variable (defaults to :app).
         | 
| 218 | 
            +
            DESC
         | 
| 219 | 
            +
            task :spinner, :roles => :app do
         | 
| 220 | 
            +
              user = (use_sudo && spinner_user) ? "-u #{spinner_user} " : ""
         | 
| 221 | 
            +
              send(run_method, "#{user}#{current_path}/script/spin")
         | 
| 222 | 
            +
            end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
            desc <<-DESC
         | 
| 225 | 
            +
            Used only for deploying when the spinner isn't running. It invokes deploy,
         | 
| 226 | 
            +
            and when it finishes it then invokes the spinner task (to start the spinner).
         | 
| 227 | 
            +
            DESC
         | 
| 228 | 
            +
            task :cold_deploy do
         | 
| 229 | 
            +
              deploy
         | 
| 230 | 
            +
              spinner
         | 
| 231 | 
            +
            end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
            desc <<-DESC
         | 
| 234 | 
            +
            A simple task for performing one-off commands that may not require a full task
         | 
| 235 | 
            +
            to be written for them. Simply specify the command to execute via the COMMAND
         | 
| 236 | 
            +
            environment variable. To execute the command only on certain roles, specify
         | 
| 237 | 
            +
            the ROLES environment variable as a comma-delimited list of role names. Lastly,
         | 
| 238 | 
            +
            if you want to execute the command via sudo, specify a non-empty value for the
         | 
| 239 | 
            +
            SUDO environment variable.
         | 
| 240 | 
            +
            DESC
         | 
| 241 | 
            +
            task :invoke, :roles => SwitchTower.str2roles(ENV["ROLES"] || "") do
         | 
| 242 | 
            +
              method = ENV["SUDO"] ? :sudo : :run
         | 
| 243 | 
            +
              send(method, ENV["COMMAND"])
         | 
| 244 | 
            +
            end
         |