aboutsummaryrefslogtreecommitdiffstats
path: root/switchtower/lib
diff options
context:
space:
mode:
Diffstat (limited to 'switchtower/lib')
-rw-r--r--switchtower/lib/switchtower.rb1
-rw-r--r--switchtower/lib/switchtower/actor.rb350
-rw-r--r--switchtower/lib/switchtower/command.rb85
-rw-r--r--switchtower/lib/switchtower/configuration.rb193
-rw-r--r--switchtower/lib/switchtower/gateway.rb106
-rw-r--r--switchtower/lib/switchtower/logger.rb56
-rw-r--r--switchtower/lib/switchtower/recipes/standard.rb123
-rw-r--r--switchtower/lib/switchtower/recipes/templates/maintenance.rhtml53
-rw-r--r--switchtower/lib/switchtower/scm/base.rb35
-rw-r--r--switchtower/lib/switchtower/scm/cvs.rb73
-rw-r--r--switchtower/lib/switchtower/scm/darcs.rb27
-rw-r--r--switchtower/lib/switchtower/scm/subversion.rb77
-rw-r--r--switchtower/lib/switchtower/ssh.rb30
-rw-r--r--switchtower/lib/switchtower/version.rb9
14 files changed, 0 insertions, 1218 deletions
diff --git a/switchtower/lib/switchtower.rb b/switchtower/lib/switchtower.rb
deleted file mode 100644
index d8458dc4a9..0000000000
--- a/switchtower/lib/switchtower.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'switchtower/configuration'
diff --git a/switchtower/lib/switchtower/actor.rb b/switchtower/lib/switchtower/actor.rb
deleted file mode 100644
index a74ea95a24..0000000000
--- a/switchtower/lib/switchtower/actor.rb
+++ /dev/null
@@ -1,350 +0,0 @@
-require 'erb'
-require 'switchtower/command'
-require 'switchtower/gateway'
-require 'switchtower/ssh'
-
-module SwitchTower
-
- # An Actor is the entity that actually does the work of determining which
- # servers should be the target of a particular task, and of executing the
- # task on each of them in parallel. An Actor is never instantiated
- # directly--rather, you create a new Configuration instance, and access the
- # new actor via Configuration#actor.
- class Actor
-
- # An adaptor for making the SSH interface look and act like that of the
- # Gateway class.
- class DefaultConnectionFactory #:nodoc:
- def initialize(config)
- @config= config
- end
-
- def connect_to(server)
- SSH.connect(server, @config)
- end
- end
-
- class <<self
- attr_accessor :connection_factory
- attr_accessor :command_factory
- end
-
- self.connection_factory = DefaultConnectionFactory
- self.command_factory = Command
-
- # The configuration instance associated with this actor.
- attr_reader :configuration
-
- # A hash of the tasks known to this actor, keyed by name. The values are
- # instances of Actor::Task.
- attr_reader :tasks
-
- # 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.
- attr_reader :sessions
-
- # 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.
- attr_reader :task_call_frames
-
- # The history of executed tasks. This will be an array of all tasks that
- # have been executed, in the order in which they were called.
- attr_reader :task_call_history
-
- # A struct for representing a single instance of an invoked task.
- TaskCallFrame = Struct.new(:name, :rollback)
-
- # Represents the definition of a single task.
- class Task #:nodoc:
- attr_reader :name, :options
-
- def initialize(name, options)
- @name, @options = name, options
- end
-
- # Returns the list of servers (_not_ connections to servers) that are
- # the target of this task.
- def servers(configuration)
- unless @servers
- roles = [*(@options[:roles] || configuration.roles.keys)].map { |name| configuration.roles[name] or raise ArgumentError, "task #{self.name.inspect} references non-existant role #{name.inspect}" }.flatten
- only = @options[:only] || {}
-
- unless only.empty?
- roles = roles.delete_if do |role|
- catch(:done) do
- only.keys.each do |key|
- throw(:done, true) if role.options[key] != only[key]
- end
- false
- end
- end
- end
-
- @servers = roles.map { |role| role.host }.uniq
- end
-
- @servers
- end
- end
-
- def initialize(config) #:nodoc:
- @configuration = config
- @tasks = {}
- @task_call_frames = []
- @sessions = {}
- @factory = self.class.connection_factory.new(configuration)
- end
-
- # Define a new task for this actor. The block will be invoked when this
- # task is called.
- def define_task(name, options={}, &block)
- @tasks[name] = Task.new(name, options)
- define_method(name) do
- send "before_#{name}" if respond_to? "before_#{name}"
- logger.trace "executing task #{name}"
- begin
- push_task_call_frame name
- result = instance_eval &block
- ensure
- pop_task_call_frame
- end
- send "after_#{name}" if respond_to? "after_#{name}"
- result
- end
- 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 ||= Proc.new do |ch, stream, out|
- logger.debug(out, "#{stream} :: #{ch[:host]}")
- end
-
- logger.debug "executing #{cmd.strip.inspect}"
-
- # get the currently executing task and determine which servers it uses
- servers = tasks[task_call_frames.last.name].servers(configuration)
- servers = servers.first if options[:once]
- logger.trace "servers: #{servers.inspect}"
-
- if !pretend
- # establish connections to those servers, as necessary
- establish_connections(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
-
- # Deletes the given file from all servers targetted by the current task.
- # If <tt>:recursive => true</tt> is specified, it may be used to remove
- # directories.
- def delete(path, options={})
- cmd = "rm -%sf #{path}" % (options[:recursive] ? "r" : "")
- 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={})
- # 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 out == :err
- 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.
- def sudo(command, options={}, &block)
- block ||= Proc.new do |ch, stream, out|
- logger.debug(out, "#{stream} :: #{ch[:host]}")
- end
-
- run "sudo #{command}", options do |ch, stream, out|
- if out =~ /^Password:/
- ch.send_data "#{password}\n"
- 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.
- #
- # Usage:
- #
- # render("something", :foo => "hello")
- # look for "something.rhtml" in the current directory, or in the
- # switchtower/recipes/templates directory, and render it with
- # foo defined as a local variable with the value "hello".
- #
- # render(:file => "something", :foo => "hello")
- # same as above
- #
- # render(:template => "<%= foo %> world", :foo => "hello")
- # treat the given string as an ERb template and render it with
- # the given hash of local variables active.
- def render(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
- options[:file] = args.shift if args.first.is_a?(String)
- raise ArgumentError, "too many parameters" unless args.empty?
-
- case
- when options[:file]
- file = options.delete :file
- unless file[0] == ?/
- dirs = [".",
- File.join(File.dirname(__FILE__), "recipes", "templates")]
- dirs.each do |dir|
- if File.file?(File.join(dir, file))
- file = File.join(dir, file)
- break
- elsif File.file?(File.join(dir, file + ".rhtml"))
- file = File.join(dir, file + ".rhtml")
- break
- end
- end
- end
-
- render options.merge(:template => File.read(file))
-
- when options[:template]
- erb = ERB.new(options[:template])
- b = Proc.new { binding }.call
- options.each do |key, value|
- next if key == :template
- eval "#{key} = options[:#{key}]", b
- end
- erb.result(b)
-
- else
- raise ArgumentError, "no file or template given for rendering"
- end
- end
-
- # Inspects the remote servers to determine the list of all released versions
- # of the software. Releases are sorted with the most recent release last.
- def releases
- unless @releases
- buffer = ""
- run "ls -x1 #{releases_path}", :once => true do |ch, str, out|
- buffer << out if str == :out
- raise "could not determine releases #{out.inspect}" if str == :err
- end
- @releases = buffer.split.sort
- end
-
- @releases
- end
-
- # Returns the most recent deployed release
- def current_release
- release_path(releases.last)
- end
-
- # Returns the release immediately before the currently deployed one
- def previous_release
- release_path(releases[-2])
- 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
- if task_call_history
- yield
- else
- logger.info "transaction: start"
- begin
- @task_call_history = []
- yield
- logger.info "transaction: commit"
- rescue Object => e
- current = task_call_history.last
- logger.important "transaction: rollback", current ? current.name : "transaction start"
- task_call_history.reverse.each do |task|
- begin
- logger.debug "rolling back", task.name
- task.rollback.call if task.rollback
- rescue Object => e
- logger.info "exception while rolling back: #{e.class}, #{e.message}", task.name
- end
- end
- raise
- ensure
- @task_call_history = nil
- end
- 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)
- task_call_frames.last.rollback = block
- end
-
- private
-
- def metaclass
- class << self; self; end
- end
-
- def define_method(name, &block)
- metaclass.send(:define_method, name, &block)
- end
-
- def push_task_call_frame(name)
- frame = TaskCallFrame.new(name)
- task_call_frames.push frame
- task_call_history.push frame if task_call_history
- end
-
- def pop_task_call_frame
- task_call_frames.pop
- end
-
- def establish_connections(servers)
- @factory = establish_gateway if needs_gateway?
- servers.each do |server|
- @sessions[server] ||= @factory.connect_to(server)
- end
- end
-
- def establish_gateway
- logger.debug "establishing connection to gateway #{gateway}"
- @established_gateway = true
- Gateway.new(gateway, configuration)
- end
-
- def needs_gateway?
- gateway && !@established_gateway
- end
-
- def method_missing(sym, *args, &block)
- if @configuration.respond_to?(sym)
- @configuration.send(sym, *args, &block)
- else
- super
- end
- end
- end
-end
diff --git a/switchtower/lib/switchtower/command.rb b/switchtower/lib/switchtower/command.rb
deleted file mode 100644
index 807958fd76..0000000000
--- a/switchtower/lib/switchtower/command.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-module SwitchTower
-
- # This class encapsulates a single command to be executed on a set of remote
- # machines, in parallel.
- class Command
- attr_reader :servers, :command, :options, :actor
-
- def initialize(servers, command, callback, options, actor) #:nodoc:
- @servers = servers
- @command = command.gsub(/\r?\n/, "\\\n")
- @callback = callback
- @options = options
- @actor = actor
- @channels = open_channels
- end
-
- def logger #:nodoc:
- actor.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.
- def process!
- logger.debug "processing command"
-
- loop do
- active = 0
- @channels.each do |ch|
- next if ch[:closed]
- active += 1
- ch.connection.process(true)
- end
-
- break if active == 0
- end
-
- logger.trace "command finished"
-
- if failed = @channels.detect { |ch| ch[:status] != 0 }
- raise "command #{@command.inspect} failed on #{failed[:host]}"
- end
-
- self
- end
-
- private
-
- def open_channels
- @servers.map do |server|
- @actor.sessions[server].open_channel do |channel|
- channel[:host] = server
- channel.request_pty :want_reply => true
-
- channel.on_success do |ch|
- logger.trace "executing command", ch[:host]
- ch.exec command
- ch.send_data options[:data] if options[:data]
- end
-
- channel.on_failure do |ch|
- logger.important "could not open channel", ch[:host]
- ch.close
- end
-
- channel.on_data do |ch, data|
- @callback[ch, :out, data] if @callback
- end
-
- channel.on_extended_data do |ch, type, data|
- @callback[ch, :err, data] if @callback
- end
-
- channel.on_request do |ch, request, reply, data|
- ch[:status] = data.read_long if request == "exit-status"
- end
-
- channel.on_close do |ch|
- ch[:closed] = true
- end
- end
- end
- end
- end
-end
diff --git a/switchtower/lib/switchtower/configuration.rb b/switchtower/lib/switchtower/configuration.rb
deleted file mode 100644
index 6f00841636..0000000000
--- a/switchtower/lib/switchtower/configuration.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-require 'switchtower/actor'
-require 'switchtower/logger'
-require 'switchtower/scm/subversion'
-
-module SwitchTower
-
- # Represents a specific SwitchTower configuration. A Configuration instance
- # may be used to load multiple recipe files, define and describe tasks,
- # define roles, create an actor, and set configuration variables.
- class Configuration
- Role = Struct.new(:host, :options)
-
- DEFAULT_VERSION_DIR_NAME = "releases" #:nodoc:
- DEFAULT_CURRENT_DIR_NAME = "current" #:nodoc:
- DEFAULT_SHARED_DIR_NAME = "shared" #:nodoc:
-
- # The actor created for this configuration instance.
- attr_reader :actor
-
- # The list of Role instances defined for this configuration.
- attr_reader :roles
-
- # The logger instance defined for this configuration.
- attr_reader :logger
-
- # The load paths used for locating recipe files.
- attr_reader :load_paths
-
- # The time (in UTC) at which this configuration was created, used for
- # determining the release path.
- attr_reader :now
-
- def initialize(actor_class=Actor) #:nodoc:
- @roles = Hash.new { |h,k| h[k] = [] }
- @actor = actor_class.new(self)
- @logger = Logger.new
- @load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
- @variables = {}
- @now = Time.now.utc
-
- set :application, nil
- set :repository, nil
- set :gateway, nil
- set :user, nil
- set :password, nil
-
- set :deploy_to, Proc.new { "/u/apps/#{application}" }
-
- set :version_dir, DEFAULT_VERSION_DIR_NAME
- set :current_dir, DEFAULT_CURRENT_DIR_NAME
- set :shared_dir, DEFAULT_SHARED_DIR_NAME
- set :scm, :subversion
-
- set :revision, Proc.new { source.latest_revision }
- end
-
- # Set a variable to the given value.
- def set(variable, value)
- @variables[variable] = value
- end
-
- alias :[]= :set
-
- # Access a named variable. If the value of the variable is a Proc instance,
- # the proc will be invoked and the return value cached and returned.
- def [](variable)
- set variable, @variables[variable].call if Proc === @variables[variable]
- @variables[variable]
- end
-
- # Based on the current value of the <tt>:scm</tt> variable, instantiate and
- # return an SCM module representing the desired source control behavior.
- def source
- @source ||= case scm
- when Class then
- scm.new(self)
- when String, Symbol then
- require "switchtower/scm/#{scm.to_s.downcase}"
- SwitchTower::SCM.const_get(scm.to_s.downcase.capitalize).new(self)
- else
- raise "invalid scm specification: #{scm.inspect}"
- end
- end
-
- # 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.
- def load(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
- args.each { |arg| load options.merge(:file => arg) }
-
- if options[:file]
- file = options[:file]
- unless file[0] == ?/
- load_paths.each do |path|
- if File.file?(File.join(path, file))
- file = File.join(path, file)
- break
- elsif File.file?(File.join(path, file) + ".rb")
- file = File.join(path, file + ".rb")
- break
- end
- end
- end
-
- load :string => File.read(file), :name => options[:name] || file
- elsif options[:string]
- logger.debug "loading configuration #{options[:name] || "<eval>"}"
- instance_eval options[:string], options[:name] || "<eval>"
- end
- 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"
- def role(which, *args)
- options = args.last.is_a?(Hash) ? args.pop : {}
- raise ArgumentError, "must give at least one host" if args.empty?
- args.each { |host| roles[which] << Role.new(host, options) }
- 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
-
- # Define a new task. If a description is active (see #desc), it is added to
- # the options under the <tt>:desc</tt> key. This method ultimately
- # delegates to Actor#define_task.
- def task(name, options={}, &block)
- raise ArgumentError, "expected a block" unless block
-
- if @next_description
- options = options.merge(:desc => @next_description)
- @next_description = nil
- end
-
- actor.define_task(name, options, &block)
- end
-
- # Return the path into which releases should be deployed.
- def releases_path
- File.join(deploy_to, version_dir)
- end
-
- # Return the path identifying the +current+ symlink, used to identify the
- # current release.
- def current_path
- File.join(deploy_to, current_dir)
- end
-
- # Return the path into which shared files should be stored.
- def shared_path
- File.join(deploy_to, shared_dir)
- end
-
- # Return the full path to the named release. If a release is not specified,
- # +now+ is used (the time at which the configuration was created).
- def release_path(release=now.strftime("%Y%m%d%H%M%S"))
- File.join(releases_path, release)
- end
-
- def respond_to?(sym) #:nodoc:
- @variables.has_key?(sym) || super
- end
-
- def method_missing(sym, *args, &block) #:nodoc:
- if args.length == 0 && block.nil? && @variables.has_key?(sym)
- self[sym]
- else
- super
- end
- end
- end
-end
diff --git a/switchtower/lib/switchtower/gateway.rb b/switchtower/lib/switchtower/gateway.rb
deleted file mode 100644
index 46f8361e9a..0000000000
--- a/switchtower/lib/switchtower/gateway.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-require 'thread'
-require 'switchtower/ssh'
-
-Thread.abort_on_exception = true
-
-module SwitchTower
-
- # Black magic. It uses threads and Net::SSH to set up a connection to a
- # gateway server, through which connections to other servers may be
- # tunnelled.
- #
- # It is used internally by Actor, but may be useful on its own, as well.
- #
- # Usage:
- #
- # config = SwitchTower::Configuration.new
- # gateway = SwitchTower::Gateway.new('gateway.example.com', config)
- #
- # sess1 = gateway.connect_to('hidden.example.com')
- # sess2 = gateway.connect_to('other.example.com')
- class Gateway
- # The thread inside which the gateway connection itself is running.
- attr_reader :thread
-
- # The Net::SSH session representing the gateway connection.
- attr_reader :session
-
- def initialize(server, config) #:nodoc:
- @config = config
- @pending_forward_requests = {}
- @mutex = Mutex.new
- @next_port = 31310
- @terminate_thread = false
-
- waiter = ConditionVariable.new
-
- @thread = Thread.new do
- @config.logger.trace "starting connection to gateway #{server}"
- SSH.connect(server, @config) do |@session|
- @config.logger.trace "gateway connection established"
- @mutex.synchronize { waiter.signal }
- connection = @session.registry[:connection][:driver]
- loop do
- break if @terminate_thread
- sleep 0.1 unless connection.reader_ready?
- connection.process true
- Thread.new { process_next_pending_connection_request }
- end
- end
- end
-
- @mutex.synchronize { waiter.wait(@mutex) }
- end
-
- # Shuts down all forwarded connections and terminates the gateway.
- def shutdown!
- # cancel all active forward channels
- @session.forward.active_locals.each do |lport, host, port|
- @session.forward.cancel_local(lport)
- end
-
- # terminate the gateway thread
- @terminate_thread = true
-
- # wait for the gateway thread to stop
- @thread.join
- end
-
- # Connects to the given server by opening a forwarded port from the local
- # host to the server, via the gateway, and then opens and returns a new
- # Net::SSH connection via that port.
- def connect_to(server)
- @mutex.synchronize do
- @pending_forward_requests[server] = ConditionVariable.new
- @pending_forward_requests[server].wait(@mutex)
- @pending_forward_requests.delete(server)
- end
- end
-
- private
-
- def process_next_pending_connection_request
- @mutex.synchronize do
- key = @pending_forward_requests.keys.detect { |k| ConditionVariable === @pending_forward_requests[k] } or return
- var = @pending_forward_requests[key]
-
- @config.logger.trace "establishing connection to #{key} via gateway"
-
- port = @next_port
- @next_port += 1
-
- begin
- @session.forward.local(port, key, 22)
- @pending_forward_requests[key] = SSH.connect('127.0.0.1', @config,
- port)
- @config.logger.trace "connection to #{key} via gateway established"
- rescue Object
- @pending_forward_requests[key] = nil
- raise
- ensure
- var.signal
- end
- end
- end
- end
-end
diff --git a/switchtower/lib/switchtower/logger.rb b/switchtower/lib/switchtower/logger.rb
deleted file mode 100644
index 0cfa0f91e9..0000000000
--- a/switchtower/lib/switchtower/logger.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module SwitchTower
- class Logger #:nodoc:
- attr_accessor :level
-
- IMPORTANT = 0
- INFO = 1
- DEBUG = 2
- TRACE = 3
-
- def initialize(options={})
- output = options[:output] || STDERR
- case
- when 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
- if line_prefix
- message.split(/\r?\n/).each do |line|
- @device.print "[#{line_prefix}] #{line.strip}\n"
- end
- else
- @device.puts message.strip
- 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
diff --git a/switchtower/lib/switchtower/recipes/standard.rb b/switchtower/lib/switchtower/recipes/standard.rb
deleted file mode 100644
index 91e67b0a97..0000000000
--- a/switchtower/lib/switchtower/recipes/standard.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-# Standard tasks that are useful for most recipes. It makes a few assumptions:
-#
-# * The :app role has been defined as the set of machines consisting of the
-# application servers.
-# * The :web role has been defined as the set of machines consisting of the
-# web servers.
-# * The Rails spinner and reaper scripts are being used to manage the FCGI
-# processes.
-# * There is a script in script/ called "reap" that restarts the FCGI processes
-
-desc "Enumerate and describe every available task."
-task :show_tasks do
- keys = tasks.keys.sort_by { |a| a.to_s }
- longest = keys.inject(0) { |len,key| key.to_s.length > len ? key.to_s.length : len } + 2
-
- puts "Available tasks"
- puts "---------------"
- tasks.keys.sort_by { |a| a.to_s }.each do |key|
- desc = (tasks[key].options[:desc] || "").strip.split(/\r?\n/)
- puts "%-#{longest}s %s" % [key, desc.shift]
- puts "%#{longest}s %s" % ["", desc.shift] until desc.empty?
- puts
- end
-end
-
-desc "Set up the expected application directory structure on all boxes"
-task :setup do
- run <<-CMD
- mkdir -p -m 775 #{releases_path} #{shared_path}/system &&
- mkdir -p -m 777 #{shared_path}/log
- CMD
-end
-
-desc <<DESC
-Disable the web server by writing a "maintenance.html" file to the web
-servers. 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.
-DESC
-task :disable_web, :roles => :web do
- on_rollback { delete "#{shared_path}/system/maintenance.html" }
-
- maintenance = render("maintenance", :deadline => ENV['UNTIL'],
- :reason => ENV['REASON'])
- put maintenance, "#{shared_path}/system/maintenance.html", :mode => 0644
-end
-
-desc %(Re-enable the web server by deleting any "maintenance.html" file.)
-task :enable_web, :roles => :web do
- delete "#{shared_path}/system/maintenance.html"
-end
-
-desc <<DESC
-Update all servers with the latest release of the source code. All this does
-is do a checkout (as defined by the selected scm module).
-DESC
-task :update_code do
- on_rollback { delete release_path, :recursive => true }
-
- source.checkout(self)
-
- run <<-CMD
- rm -rf #{release_path}/log #{release_path}/public/system &&
- ln -nfs #{shared_path}/log #{release_path}/log &&
- ln -nfs #{shared_path}/system #{release_path}/public/system
- CMD
-end
-
-desc <<DESC
-Rollback the latest checked-out version to the previous one by fixing the
-symlinks and deleting the current release from all servers.
-DESC
-task :rollback_code do
- if releases.length < 2
- raise "could not rollback the code because there is no prior release"
- else
- run <<-CMD
- ln -nfs #{previous_release} #{current_path} &&
- rm -rf #{current_release}
- CMD
- end
-end
-
-desc <<DESC
-Update the 'current' symlink to point to the latest version of
-the application's code.
-DESC
-task :symlink do
- on_rollback { run "ln -nfs #{previous_release} #{current_path}" }
- run "ln -nfs #{current_release} #{current_path}"
-end
-
-desc "Restart the FCGI processes on the app server."
-task :restart, :roles => :app do
- sudo "#{current_path}/script/reap"
-end
-
-desc <<DESC
-Run the migrate task in the version of the app indicated by the 'current'
-symlink. This means you should not invoke this task until the symlink has
-been updated to the most recent version.
-DESC
-task :migrate, :roles => :db, :only => { :primary => true } do
- run "cd #{current_path} && rake RAILS_ENV=production migrate"
-end
-
-desc <<DESC
-A macro-task that updates the code, fixes the symlink, and restarts the
-application servers.
-DESC
-task :deploy do
- transaction do
- update_code
- symlink
- end
-
- restart
-end
-
-desc "A macro-task that rolls back the code and restarts the application servers."
-task :rollback do
- rollback_code
- restart
-end
diff --git a/switchtower/lib/switchtower/recipes/templates/maintenance.rhtml b/switchtower/lib/switchtower/recipes/templates/maintenance.rhtml
deleted file mode 100644
index 532e51feb0..0000000000
--- a/switchtower/lib/switchtower/recipes/templates/maintenance.rhtml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-
-<head>
- <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
- <title>System down for maintenance</title>
-
- <style type="text/css">
- div.outer {
- position: absolute;
- left: 50%;
- top: 50%;
- width: 500px;
- height: 300px;
- margin-left: -260px;
- margin-top: -150px;
- }
-
- .DialogBody {
- margin: 0;
- padding: 10px;
- text-align: left;
- border: 1px solid #ccc;
- border-right: 1px solid #999;
- border-bottom: 1px solid #999;
- background-color: #fff;
- }
-
- body { background-color: #fff; }
- </style>
-</head>
-
-<body>
-
- <div class="outer">
- <div class="DialogBody" style="text-align: center;">
- <div style="text-align: center; width: 200px; margin: 0 auto;">
- <p style="color: red; font-size: 16px; line-height: 20px;">
- The system is down for <%= reason ? reason : "maintenance" %>
- as of <%= Time.now.strftime("%H:%M %Z") %>.
- </p>
- <p style="color: #666;">
- It'll be back <%= deadline ? "by #{deadline}" : "shortly" %>.
- </p>
- </div>
- </div>
- </div>
-
-</body>
-</html>
diff --git a/switchtower/lib/switchtower/scm/base.rb b/switchtower/lib/switchtower/scm/base.rb
deleted file mode 100644
index 4ca42132f2..0000000000
--- a/switchtower/lib/switchtower/scm/base.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module SwitchTower
- module SCM
-
- # The ancestor class of the various SCM module implementations.
- class Base
- attr_reader :configuration
-
- def initialize(configuration) #:nodoc:
- @configuration = configuration
- end
-
- def latest_revision
- nil
- end
-
- private
-
- def run_checkout(actor, guts, &block)
- log = "#{configuration.deploy_to}/revisions.log"
- directory = File.basename(configuration.release_path)
-
- command = <<-STR
- if [[ ! -d #{configuration.release_path} ]]; then
- #{guts}
- echo `date +"%Y-%m-%d %H:%M:%S"` $USER #{configuration.revision} #{directory} >> #{log};
- chmod 666 #{log};
- fi
- STR
-
- actor.run(command, &block)
- end
- end
-
- end
-end
diff --git a/switchtower/lib/switchtower/scm/cvs.rb b/switchtower/lib/switchtower/scm/cvs.rb
deleted file mode 100644
index cb13987026..0000000000
--- a/switchtower/lib/switchtower/scm/cvs.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require 'time'
-require 'switchtower/scm/base'
-
-module SwitchTower
- module SCM
-
- # An SCM module for using CVS as your source control tool. You can
- # specify it by placing the following line in your configuration:
- #
- # set :scm, :cvs
- #
- # Also, this module accepts a <tt>:cvs</tt> configuration variable,
- # which (if specified) will be used as the full path to the cvs
- # executable on the remote machine:
- #
- # set :cvs, "/opt/local/bin/cvs"
- #
- # You can specify the location of your local copy (used to query
- # the revisions, etc.) via the <tt>:local</tt> variable, which defaults to
- # ".".
- #
- # Also, you can specify the CVS_RSH variable to use on the remote machine(s)
- # via the <tt>:cvs_rsh</tt> variable. This defaults to the value of the
- # CVS_RSH environment variable locally, or if it is not set, to "ssh".
- class Cvs < Base
- # Return a string representing the date of the last revision (CVS is
- # seriously retarded, in that it does not give you a way to query when
- # the last revision was made to the repository, so this is a fairly
- # expensive operation...)
- def latest_revision
- return @latest_revision if @latest_revision
- configuration.logger.debug "querying latest revision..."
- @latest_revision = cvs_log(configuration.local).
- split(/\r?\n/).
- grep(/^date: (.*?);/) { Time.parse($1).strftime("%F %T") }.
- sort.
- last
- end
-
- # Check out (on all servers associated with the current task) the latest
- # revision. Uses the given actor instance to execute the command.
- def checkout(actor)
- cvs = configuration[:cvs] || "cvs"
- cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"
-
- command = <<-CMD
- cd #{configuration.releases_path};
- CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q co -D "#{configuration.revision}" -d #{File.basename(actor.release_path)} #{actor.application};
- CMD
-
- run_checkout(actor, command) do |ch, stream, out|
- prefix = "#{stream} :: #{ch[:host]}"
- actor.logger.info out, prefix
- if out =~ %r{password:}
- actor.logger.info "CVS is asking for a password", prefix
- ch.send_data "#{actor.password}\n"
- elsif out =~ %r{^Enter passphrase}
- message = "CVS needs your key's passphrase and cannot proceed"
- actor.logger.info message, prefix
- raise message
- end
- end
- end
-
- private
-
- def cvs_log(path)
- `cd #{path || "."} && cvs -q log -N -rHEAD`
- end
- end
-
- end
-end
diff --git a/switchtower/lib/switchtower/scm/darcs.rb b/switchtower/lib/switchtower/scm/darcs.rb
deleted file mode 100644
index 8c02f3f99b..0000000000
--- a/switchtower/lib/switchtower/scm/darcs.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'switchtower/scm/base'
-
-module SwitchTower
- module SCM
-
- # An SCM module for using darcs as your source control tool. Use it by
- # specifying the following line in your configuration:
- #
- # set :scm, :darcs
- #
- # Also, this module accepts a <tt>:darcs</tt> configuration variable,
- # which (if specified) will be used as the full path to the darcs
- # executable on the remote machine:
- #
- # set :darcs, "/opt/local/bin/darcs"
- class Darcs < Base
- # Check out (on all servers associated with the current task) the latest
- # revision. Uses the given actor instance to execute the command.
- def checkout(actor)
- darcs = configuration[:darcs] ? configuration[:darcs] : "darcs"
- revision = configuration[:revision] ? %(--to-match "#{configuration.revision}") : ""
- run_checkout(actor, "#{darcs} get -q --set-scripts-executable #{revision} #{configuration.repository} #{actor.release_path};")
- end
- end
-
- end
-end
diff --git a/switchtower/lib/switchtower/scm/subversion.rb b/switchtower/lib/switchtower/scm/subversion.rb
deleted file mode 100644
index 7d95dbeb47..0000000000
--- a/switchtower/lib/switchtower/scm/subversion.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'switchtower/scm/base'
-
-module SwitchTower
- module SCM
-
- # An SCM module for using subversion as your source control tool. This
- # module is used by default, but you can explicitly specify it by
- # placing the following line in your configuration:
- #
- # set :scm, :subversion
- #
- # Also, this module accepts a <tt>:svn</tt> configuration variable,
- # which (if specified) will be used as the full path to the svn
- # executable on the remote machine:
- #
- # set :svn, "/opt/local/bin/svn"
- class Subversion < Base
- # Return an integer identifying the last known revision in the svn
- # repository. (This integer is currently the revision number.) If latest
- # revision does not exist in the given repository, this routine will
- # walk up the directory tree until it finds it.
- def latest_revision
- configuration.logger.debug "querying latest revision..." unless @latest_revision
- repo = configuration.repository
- until @latest_revision
- match = svn_log(repo).scan(/r(\d+)/).first
- @latest_revision = match ? match.first : nil
- if @latest_revision.nil?
- # if a revision number was not reported, move up a level in the path
- # and try again.
- repo = File.dirname(repo)
- end
- end
- @latest_revision
- end
-
- # Check out (on all servers associated with the current task) the latest
- # revision. Uses the given actor instance to execute the command. If
- # svn asks for a password this will automatically provide it (assuming
- # the requested password is the same as the password for logging into the
- # remote server.)
- def checkout(actor)
- svn = configuration[:svn] ? configuration[:svn] : "svn"
-
- command = "#{svn} co -q -r#{configuration.revision} #{configuration.repository} #{actor.release_path};"
-
- run_checkout(actor, command) do |ch, stream, out|
- prefix = "#{stream} :: #{ch[:host]}"
- actor.logger.info out, prefix
- if out =~ /^Password.*:/
- actor.logger.info "subversion is asking for a password", prefix
- ch.send_data "#{actor.password}\n"
- elsif out =~ %r{\(yes/no\)}
- actor.logger.info "subversion is asking whether to connect or not",
- prefix
- ch.send_data "yes\n"
- elsif out =~ %r{passphrase}
- message = "subversion needs your key's passphrase, sending empty string"
- actor.logger.info message, prefix
- ch.send_data "\n"
- elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
- message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
- actor.logger.info message, prefix
- raise message
- end
- end
- end
-
- private
-
- def svn_log(path)
- `svn log -q -rhead #{path}`
- end
- end
-
- end
-end
diff --git a/switchtower/lib/switchtower/ssh.rb b/switchtower/lib/switchtower/ssh.rb
deleted file mode 100644
index b810f20573..0000000000
--- a/switchtower/lib/switchtower/ssh.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'net/ssh'
-
-module SwitchTower
- # A helper class for dealing with SSH connections.
- class SSH
- # 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.
- #
- # If a block is given, the new session is yielded to it, otherwise the new
- # session is returned.
- def self.connect(server, config, port=22, &block)
- methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
- password_value = nil
-
- begin
- Net::SSH.start(server,
- :username => config.user,
- :password => password_value,
- :port => port,
- :auth_methods => methods.shift,
- &block)
- rescue Net::SSH::AuthenticationFailed
- raise if methods.empty?
- password_value = config.password
- retry
- end
- end
- end
-end
diff --git a/switchtower/lib/switchtower/version.rb b/switchtower/lib/switchtower/version.rb
deleted file mode 100644
index a5fef1317d..0000000000
--- a/switchtower/lib/switchtower/version.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module SwitchTower
- module Version #:nodoc:
- MAJOR = 0
- MINOR = 8
- TINY = 0
-
- STRING = [MAJOR, MINOR, TINY].join(".")
- end
-end