aboutsummaryrefslogtreecommitdiffstats
path: root/switchtower
diff options
context:
space:
mode:
authorJamis Buck <jamis@37signals.com>2005-08-30 20:53:32 +0000
committerJamis Buck <jamis@37signals.com>2005-08-30 20:53:32 +0000
commit6b1864a048ffb9343d4658ec11c32494d7f038db (patch)
tree6c1ad3f9131d644afab6cc6188001cfb73775e33 /switchtower
parentbb7f60ca1cd191fd775fef261b4d2bc7af223604 (diff)
downloadrails-6b1864a048ffb9343d4658ec11c32494d7f038db.tar.gz
rails-6b1864a048ffb9343d4658ec11c32494d7f038db.tar.bz2
rails-6b1864a048ffb9343d4658ec11c32494d7f038db.zip
Move switchtower to the tools directory, to decouple it from rails
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2074 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'switchtower')
-rw-r--r--switchtower/CHANGELOG19
-rw-r--r--switchtower/MIT-LICENSE20
-rw-r--r--switchtower/README35
-rw-r--r--switchtower/Rakefile47
-rwxr-xr-xswitchtower/bin/switchtower118
-rw-r--r--switchtower/examples/sample.rb113
-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
-rw-r--r--switchtower/setup.rb1331
-rw-r--r--switchtower/switchtower.gemspec28
-rw-r--r--switchtower/test/actor_test.rb261
-rw-r--r--switchtower/test/command_test.rb43
-rw-r--r--switchtower/test/configuration_test.rb210
-rw-r--r--switchtower/test/fixtures/config.rb5
-rw-r--r--switchtower/test/scm/cvs_test.rb164
-rw-r--r--switchtower/test/scm/subversion_test.rb100
-rw-r--r--switchtower/test/ssh_test.rb104
-rw-r--r--switchtower/test/utils.rb41
30 files changed, 0 insertions, 3857 deletions
diff --git a/switchtower/CHANGELOG b/switchtower/CHANGELOG
deleted file mode 100644
index 935ef01f99..0000000000
--- a/switchtower/CHANGELOG
+++ /dev/null
@@ -1,19 +0,0 @@
-*SVN*
-
-* Specify the revision to release via the :revision variable (defaults to latest revision)
-
-* Allow variables to be set via the cli using the -s switch
-
-* Log checkouts to a "revisions.log" file
-
-* Changed behavior of checkout to use the timestamp as the release name, instead of the revision number
-
-* Added CVS module (very very experimental!)
-
-* Works with public keys now, for passwordless deployment
-
-* Subversion module recognizes the password prompt for HTTP authentication
-
-* Preserve +x on scripts when using darcs #1929 [Scott Barron]
-
-* When executing multiline commands, use a backslash to escape the newline
diff --git a/switchtower/MIT-LICENSE b/switchtower/MIT-LICENSE
deleted file mode 100644
index 7968af4fce..0000000000
--- a/switchtower/MIT-LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2005 Jamis Buck
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/switchtower/README b/switchtower/README
deleted file mode 100644
index 45bad8bbe3..0000000000
--- a/switchtower/README
+++ /dev/null
@@ -1,35 +0,0 @@
-= SwitchTower
-
-SwitchTower is a utility and framework for executing commands in parallel on multiple remote machines, via SSH. It uses a simple DSL (borrowed in part from Rake, http://rake.rubyforge.org/) that allows you to define _tasks_, which may be applied to machines in certain roles. It also supports tunneling connections via some gateway machine to allow operations to be performed behind VPN's and firewalls.
-
-SwitchTower was originally designed to simplify and automate deployment of web applications to distributed environments, and so it comes with many tasks predefined for that ("update_code" and "deploy", for instance).
-
-== Dependencies
-
-SwitchTower depends upon the Net::SSH library by Jamis Buck (http://net-ssh.rubyforge.org). Net::SSH itself depends on the Needle library (http://needle.rubyforge.org), also by Jamis Buck.
-
-== Assumptions
-
-In keeping with Rails' "convention over configuration", SwitchTower makes several assumptions about how you will use it (most, if not all, of which may be explicitly overridden):
-
-* You are writing web applications and want to use SwitchTower to deploy them.
-* You are using Ruby on Rails (http://www.rubyonrails.com) to build your apps.
-* You are using Subversion (http://subversion.tigris.org/) to manage your source code.
-* You are running your apps using FastCGI, together with Rails' spinner/reaper utilities.
-
-As with the rest of Rails, if you can abide by these assumptions, you can use SwitchTower "out of the box". If any of these assumptions do not hold, you'll need to make some adjustments to your deployment recipe files.
-
-== Usage
-
-More documentation is always pending, but you'll want to see the user manual for detailed usage instructions. (The manual is online at http://manuals.rubyonrails.com/read/book/17).
-
-In general, you'll use SwitchTower as follows:
-
-* Create a deployment recipe ("deploy.rb") for your application. You can use the sample recipe in examples/sample.rb as a starting point.
-* Use the +switchtower+ script to execute your recipe (see below).
-
-Use the +switchtower+ script as follows:
-
- switchtower -r deploy -a someaction -vvvv
-
-The <tt>-r</tt> switch specifies the recipe to use, and the <tt>-a</tt> switch specifies which action you want to execute. You can the <tt>-v</tt> switch multiple times (as shown) to increase the verbosity of the output.
diff --git a/switchtower/Rakefile b/switchtower/Rakefile
deleted file mode 100644
index 7a4e2c6351..0000000000
--- a/switchtower/Rakefile
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'rake'
-require 'rake/testtask'
-require 'rake/rdoctask'
-require 'rake/gempackagetask'
-require 'rake/contrib/rubyforgepublisher'
-
-require "./lib/switchtower/version"
-
-PKG_NAME = "switchtower"
-PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
-PKG_VERSION = SwitchTower::Version::STRING + PKG_BUILD
-PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
-
-desc "Default task"
-task :default => [ :test ]
-
-desc "Build documentation"
-task :doc => [ :rdoc ]
-
-Rake::TestTask.new do |t|
- t.test_files = Dir["test/**/*_test.rb"]
- t.verbose = true
-end
-
-GEM_SPEC = eval(File.read("#{File.dirname(__FILE__)}/#{PKG_NAME}.gemspec"))
-
-Rake::GemPackageTask.new(GEM_SPEC) do |p|
- p.gem_spec = GEM_SPEC
- p.need_tar = true
- p.need_zip = true
-end
-
-desc "Build the RDoc API documentation"
-Rake::RDocTask.new do |rdoc|
- rdoc.rdoc_dir = "doc"
- rdoc.title = "SwitchTower -- A framework for remote command execution"
- rdoc.options << '--line-numbers --inline-source --main README'
- rdoc.rdoc_files.include 'README'
- rdoc.rdoc_files.include 'lib/**/*.rb'
- rdoc.template = "jamis"
-end
-
-desc "Publish the beta gem"
-task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
-end \ No newline at end of file
diff --git a/switchtower/bin/switchtower b/switchtower/bin/switchtower
deleted file mode 100755
index d567ad7555..0000000000
--- a/switchtower/bin/switchtower
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'optparse'
-require 'switchtower'
-
-begin
- if !defined?(USE_TERMIOS) || USE_TERMIOS
- require 'termios'
- else
- raise LoadError
- end
-
- # Enable or disable stdin echoing to the terminal.
- def echo(enable)
- term = Termios::getattr(STDIN)
-
- if enable
- term.c_lflag |= (Termios::ECHO | Termios::ICANON)
- else
- term.c_lflag &= ~Termios::ECHO
- end
-
- Termios::setattr(STDIN, Termios::TCSANOW, term)
- end
-rescue LoadError
- def echo(enable)
- end
-end
-
-options = { :verbose => 0, :recipes => [], :actions => [], :vars => {} }
-
-OptionParser.new do |opts|
- opts.banner = "Usage: #{$0} [options]"
- opts.separator ""
-
- opts.on("-a", "--action ACTION",
- "An action to execute. Multiple actions may",
- "be specified, and are loaded in the given order."
- ) { |value| options[:actions] << value }
-
- opts.on("-p", "--password PASSWORD",
- "The password to use when connecting.",
- "(Default: prompt for password)"
- ) { |value| options[:password] = value }
-
- opts.on("-P", "--[no-]pretend",
- "Run the task(s), but don't actually connect to or",
- "execute anything on the servers. (For various reasons",
- "this will not necessarily be an accurate depiction",
- "of the work that will actually be performed.",
- "Default: don't pretend.)"
- ) { |value| options[:pretend] = value }
-
- opts.on("-r", "--recipe RECIPE",
- "A recipe file to load. Multiple recipes may",
- "be specified, and are loaded in the given order."
- ) { |value| options[:recipes] << value }
-
- opts.on("-s", "--set NAME=VALUE",
- "Specify a variable and it's value to set. This",
- "will be set after loading all recipe files."
- ) do |pair|
- name, value = pair.split(/=/)
- options[:vars][name.to_sym] = value
- end
-
- opts.on("-v", "--verbose",
- "Specify the verbosity of the output.",
- "May be given multiple times. (Default: silent)"
- ) { options[:verbose] += 1 }
-
- opts.separator ""
- opts.on_tail("-h", "--help", "Display this help message") do
- puts opts
- exit
- end
- opts.on_tail("-V", "--version",
- "Display the version info for this utility"
- ) do
- require 'switchtower/version'
- puts "SwitchTower v#{SwitchTower::Version::STRING}"
- exit
- end
-
- opts.parse!
-end
-
-abort "You must specify at least one recipe" if options[:recipes].empty?
-abort "You must specify at least one action" if options[:actions].empty?
-
-unless options.has_key?(:password)
- options[:password] = Proc.new do
- sync = STDOUT.sync
- begin
- echo false
- STDOUT.sync = true
- print "Password: "
- STDIN.gets.chomp
- ensure
- echo true
- STDOUT.sync = sync
- puts
- end
- end
-end
-
-config = SwitchTower::Configuration.new
-config.logger.level = options[:verbose]
-config.set :password, options[:password]
-config.set :pretend, options[:pretend]
-
-config.load "standard" # load the standard recipe definition
-
-options[:recipes].each { |recipe| config.load(recipe) }
-options[:vars].each { |name, value| config.set(name, value) }
-
-actor = config.actor
-options[:actions].each { |action| actor.send action }
diff --git a/switchtower/examples/sample.rb b/switchtower/examples/sample.rb
deleted file mode 100644
index 94d430430a..0000000000
--- a/switchtower/examples/sample.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# You must always specify the application and repository for every recipe. The
-# repository must be the URL of the repository you want this recipe to
-# correspond to. The deploy_to path must be the path on each machine that will
-# form the root of the application path.
-
-set :application, "sample"
-set :repository, "http://svn.example.com/#{application}/trunk"
-
-# The deploy_to path is optional, defaulting to "/u/apps/#{application}".
-
-set :deploy_to, "/path/to/app/root"
-
-# The user value is optional, defaulting to user-name of the current user. This
-# is the user name that will be used when logging into the deployment boxes.
-
-set :user, "flippy"
-
-# By default, the source control module (scm) is set to "subversion". You can
-# set it to any supported scm:
-
-set :scm, :subversion
-
-# gateway is optional, but allows you to specify the address of a computer that
-# will be used to tunnel other requests through, such as when your machines are
-# all behind a VPN or something
-
-set :gateway, "gateway.example.com"
-
-# You can define any number of roles, each of which contains any number of
-# machines. Roles might include such things as :web, or :app, or :db, defining
-# what the purpose of each machine is. You can also specify options that can
-# be used to single out a specific subset of boxes in a particular role, like
-# :primary => true.
-
-role :web, "www01.example.com", "www02.example.com"
-role :app, "app01.example.com", "app02.example.com", "app03.example.com"
-role :db, "db01.example.com", :primary => true
-role :db, "db02.example.com", "db03.example.com"
-
-# Define tasks that run on all (or only some) of the machines. You can specify
-# a role (or set of roles) that each task should be executed on. You can also
-# narrow the set of servers to a subset of a role by specifying options, which
-# must match the options given for the servers to select (like :primary => true)
-
-desc <<DESC
-An imaginary backup task. (Execute the 'show_tasks' task to display all
-available tasks.)
-DESC
-
-task :backup, :roles => :db, :only => { :primary => true } do
- # the on_rollback handler is only executed if this task is executed within
- # a transaction (see below), AND it or a subsequent task fails.
- on_rollback { delete "/tmp/dump.sql" }
-
- run "mysqldump -u theuser -p thedatabase > /tmp/dump.sql" do |ch, stream, out|
- ch.send_data "thepassword\n" if out =~ /^Enter password:/
- end
-end
-
-# Tasks may take advantage of several different helper methods to interact
-# with the remote server(s). These are:
-#
-# * run(command, options={}, &block): execute the given command on all servers
-# associated with the current task, in parallel. The block, if given, should
-# accept three parameters: the communication channel, a symbol identifying the
-# type of stream (:err or :out), and the data. The block is invoked for all
-# output from the command, allowing you to inspect output and act
-# accordingly.
-# * sudo(command, options={}, &block): same as run, but it executes the command
-# via sudo.
-# * delete(path, options={}): deletes the given file or directory from all
-# associated servers. If :recursive => true is given in the options, the
-# delete uses "rm -rf" instead of "rm -f".
-# * put(buffer, path, options={}): creates or overwrites a file at "path" on
-# all associated servers, populating it with the contents of "buffer". You
-# can specify :mode as an integer value, which will be used to set the mode
-# on the file.
-# * render(template, options={}) or render(options={}): renders the given
-# template and returns a string. Alternatively, if the :template key is given,
-# it will be treated as the contents of the template to render. Any other keys
-# are treated as local variables, which are made available to the (ERb)
-# template.
-
-desc "Demonstrates the various helper methods available to recipes."
-task :helper_demo do
- # "setup" is a standard task which sets up the directory structure on the
- # remote servers. It is a good idea to run the "setup" task at least once
- # at the beginning of your app's lifetime (it is non-destructive).
- setup
-
- buffer = render("maintenance.rhtml", :deadline => ENV['UNTIL'])
- put buffer, "#{shared_path}/system/maintenance.html", :mode => 0644
- sudo "killall -USR1 dispatch.fcgi"
- run "#{release_path}/script/spin"
- delete "#{shared_path}/system/maintenance.html"
-end
-
-# You can use "transaction" to indicate that if any of the tasks within it fail,
-# all should be rolled back (for each task that specifies an on_rollback
-# handler).
-
-desc "A task demonstrating the use of transactions."
-task :long_deploy do
- transaction do
- update_code
- disable_web
- symlink
- migrate
- end
-
- restart
- enable_web
-end
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
diff --git a/switchtower/setup.rb b/switchtower/setup.rb
deleted file mode 100644
index 5f84ae0bad..0000000000
--- a/switchtower/setup.rb
+++ /dev/null
@@ -1,1331 +0,0 @@
-#
-# setup.rb
-#
-# Copyright (c) 2000-2004 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU Lesser General Public License version 2.1.
-#
-
-#
-# For backward compatibility
-#
-
-unless Enumerable.method_defined?(:map)
- module Enumerable
- alias map collect
- end
-end
-
-unless Enumerable.method_defined?(:detect)
- module Enumerable
- alias detect find
- end
-end
-
-unless Enumerable.method_defined?(:select)
- module Enumerable
- alias select find_all
- end
-end
-
-unless Enumerable.method_defined?(:reject)
- module Enumerable
- def reject
- result = []
- each do |i|
- result.push i unless yield(i)
- end
- result
- end
- end
-end
-
-unless Enumerable.method_defined?(:inject)
- module Enumerable
- def inject(result)
- each do |i|
- result = yield(result, i)
- end
- result
- end
- end
-end
-
-unless Enumerable.method_defined?(:any?)
- module Enumerable
- def any?
- each do |i|
- return true if yield(i)
- end
- false
- end
- end
-end
-
-unless File.respond_to?(:read)
- def File.read(fname)
- open(fname) {|f|
- return f.read
- }
- end
-end
-
-#
-# Application independent utilities
-#
-
-def File.binread(fname)
- open(fname, 'rb') {|f|
- return f.read
- }
-end
-
-# for corrupted windows stat(2)
-def File.dir?(path)
- File.directory?((path[-1,1] == '/') ? path : path + '/')
-end
-
-#
-# Config
-#
-
-if arg = ARGV.detect{|arg| /\A--rbconfig=/ =~ arg }
- ARGV.delete(arg)
- require arg.split(/=/, 2)[1]
- $".push 'rbconfig.rb'
-else
- require 'rbconfig'
-end
-
-def multipackage_install?
- FileTest.directory?(File.dirname($0) + '/packages')
-end
-
-
-class ConfigTable
-
- c = ::Config::CONFIG
-
- rubypath = c['bindir'] + '/' + c['ruby_install_name']
-
- major = c['MAJOR'].to_i
- minor = c['MINOR'].to_i
- teeny = c['TEENY'].to_i
- version = "#{major}.#{minor}"
-
- # ruby ver. >= 1.4.4?
- newpath_p = ((major >= 2) or
- ((major == 1) and
- ((minor >= 5) or
- ((minor == 4) and (teeny >= 4)))))
-
- subprefix = lambda {|path|
- path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix')
- }
-
- if c['rubylibdir']
- # V < 1.6.3
- stdruby = subprefix.call(c['rubylibdir'])
- siteruby = subprefix.call(c['sitedir'])
- versite = subprefix.call(c['sitelibdir'])
- sodir = subprefix.call(c['sitearchdir'])
- elsif newpath_p
- # 1.4.4 <= V <= 1.6.3
- stdruby = "$prefix/lib/ruby/#{version}"
- siteruby = subprefix.call(c['sitedir'])
- versite = siteruby + '/' + version
- sodir = "$site-ruby/#{c['arch']}"
- else
- # V < 1.4.4
- stdruby = "$prefix/lib/ruby/#{version}"
- siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
- versite = siteruby
- sodir = "$site-ruby/#{c['arch']}"
- end
-
- if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
- makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
- else
- makeprog = 'make'
- end
-
- common_descripters = [
- [ 'prefix', [ c['prefix'],
- 'path',
- 'path prefix of target environment' ] ],
- [ 'std-ruby', [ stdruby,
- 'path',
- 'the directory for standard ruby libraries' ] ],
- [ 'site-ruby-common', [ siteruby,
- 'path',
- 'the directory for version-independent non-standard ruby libraries' ] ],
- [ 'site-ruby', [ versite,
- 'path',
- 'the directory for non-standard ruby libraries' ] ],
- [ 'bin-dir', [ '$prefix/bin',
- 'path',
- 'the directory for commands' ] ],
- [ 'rb-dir', [ '$site-ruby',
- 'path',
- 'the directory for ruby scripts' ] ],
- [ 'so-dir', [ sodir,
- 'path',
- 'the directory for ruby extentions' ] ],
- [ 'data-dir', [ '$prefix/share',
- 'path',
- 'the directory for shared data' ] ],
- [ 'ruby-path', [ rubypath,
- 'path',
- 'path to set to #! line' ] ],
- [ 'ruby-prog', [ rubypath,
- 'name',
- 'the ruby program using for installation' ] ],
- [ 'make-prog', [ makeprog,
- 'name',
- 'the make program to compile ruby extentions' ] ],
- [ 'without-ext', [ 'no',
- 'yes/no',
- 'does not compile/install ruby extentions' ] ]
- ]
- multipackage_descripters = [
- [ 'with', [ '',
- 'name,name...',
- 'package names that you want to install',
- 'ALL' ] ],
- [ 'without', [ '',
- 'name,name...',
- 'package names that you do not want to install',
- 'NONE' ] ]
- ]
- if multipackage_install?
- DESCRIPTER = common_descripters + multipackage_descripters
- else
- DESCRIPTER = common_descripters
- end
-
- SAVE_FILE = 'config.save'
-
- def ConfigTable.each_name(&block)
- keys().each(&block)
- end
-
- def ConfigTable.keys
- DESCRIPTER.map {|name, *dummy| name }
- end
-
- def ConfigTable.each_definition(&block)
- DESCRIPTER.each(&block)
- end
-
- def ConfigTable.get_entry(name)
- name, ent = DESCRIPTER.assoc(name)
- ent
- end
-
- def ConfigTable.get_entry!(name)
- get_entry(name) or raise ArgumentError, "no such config: #{name}"
- end
-
- def ConfigTable.add_entry(name, vals)
- ConfigTable::DESCRIPTER.push [name,vals]
- end
-
- def ConfigTable.remove_entry(name)
- get_entry(name) or raise ArgumentError, "no such config: #{name}"
- DESCRIPTER.delete_if {|n, arr| n == name }
- end
-
- def ConfigTable.config_key?(name)
- get_entry(name) ? true : false
- end
-
- def ConfigTable.bool_config?(name)
- ent = get_entry(name) or return false
- ent[1] == 'yes/no'
- end
-
- def ConfigTable.value_config?(name)
- ent = get_entry(name) or return false
- ent[1] != 'yes/no'
- end
-
- def ConfigTable.path_config?(name)
- ent = get_entry(name) or return false
- ent[1] == 'path'
- end
-
-
- class << self
- alias newobj new
- end
-
- def ConfigTable.new
- c = newobj()
- c.initialize_from_table
- c
- end
-
- def ConfigTable.load
- c = newobj()
- c.initialize_from_file
- c
- end
-
- def initialize_from_table
- @table = {}
- DESCRIPTER.each do |k, (default, vname, desc, default2)|
- @table[k] = default
- end
- end
-
- def initialize_from_file
- raise InstallError, "#{File.basename $0} config first"\
- unless File.file?(SAVE_FILE)
- @table = {}
- File.foreach(SAVE_FILE) do |line|
- k, v = line.split(/=/, 2)
- @table[k] = v.strip
- end
- end
-
- def save
- File.open(SAVE_FILE, 'w') {|f|
- @table.each do |k, v|
- f.printf "%s=%s\n", k, v if v
- end
- }
- end
-
- def []=(k, v)
- raise InstallError, "unknown config option #{k}"\
- unless ConfigTable.config_key?(k)
- @table[k] = v
- end
-
- def [](key)
- return nil unless @table[key]
- @table[key].gsub(%r<\$([^/]+)>) { self[$1] }
- end
-
- def set_raw(key, val)
- @table[key] = val
- end
-
- def get_raw(key)
- @table[key]
- end
-
-end
-
-
-module MetaConfigAPI
-
- def eval_file_ifexist(fname)
- instance_eval File.read(fname), fname, 1 if File.file?(fname)
- end
-
- def config_names
- ConfigTable.keys
- end
-
- def config?(name)
- ConfigTable.config_key?(name)
- end
-
- def bool_config?(name)
- ConfigTable.bool_config?(name)
- end
-
- def value_config?(name)
- ConfigTable.value_config?(name)
- end
-
- def path_config?(name)
- ConfigTable.path_config?(name)
- end
-
- def add_config(name, argname, default, desc)
- ConfigTable.add_entry name,[default,argname,desc]
- end
-
- def add_path_config(name, default, desc)
- add_config name, 'path', default, desc
- end
-
- def add_bool_config(name, default, desc)
- add_config name, 'yes/no', default ? 'yes' : 'no', desc
- end
-
- def set_config_default(name, default)
- if bool_config?(name)
- ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no')
- else
- ConfigTable.get_entry!(name)[0] = default
- end
- end
-
- def remove_config(name)
- ent = ConfigTable.get_entry(name)
- ConfigTable.remove_entry name
- ent
- end
-
-end
-
-#
-# File Operations
-#
-
-module FileOperations
-
- def mkdir_p(dirname, prefix = nil)
- dirname = prefix + dirname if prefix
- $stderr.puts "mkdir -p #{dirname}" if verbose?
- return if no_harm?
-
- # does not check '/'... it's too abnormal case
- dirs = dirname.split(%r<(?=/)>)
- if /\A[a-z]:\z/i =~ dirs[0]
- disk = dirs.shift
- dirs[0] = disk + dirs[0]
- end
- dirs.each_index do |idx|
- path = dirs[0..idx].join('')
- Dir.mkdir path unless File.dir?(path)
- end
- end
-
- def rm_f(fname)
- $stderr.puts "rm -f #{fname}" if verbose?
- return if no_harm?
-
- if File.exist?(fname) or File.symlink?(fname)
- File.chmod 0777, fname
- File.unlink fname
- end
- end
-
- def rm_rf(dn)
- $stderr.puts "rm -rf #{dn}" if verbose?
- return if no_harm?
-
- Dir.chdir dn
- Dir.foreach('.') do |fn|
- next if fn == '.'
- next if fn == '..'
- if File.dir?(fn)
- verbose_off {
- rm_rf fn
- }
- else
- verbose_off {
- rm_f fn
- }
- end
- end
- Dir.chdir '..'
- Dir.rmdir dn
- end
-
- def move_file(src, dest)
- File.unlink dest if File.exist?(dest)
- begin
- File.rename src, dest
- rescue
- File.open(dest, 'wb') {|f| f.write File.binread(src) }
- File.chmod File.stat(src).mode, dest
- File.unlink src
- end
- end
-
- def install(from, dest, mode, prefix = nil)
- $stderr.puts "install #{from} #{dest}" if verbose?
- return if no_harm?
-
- realdest = prefix + dest if prefix
- realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
- str = File.binread(from)
- if diff?(str, realdest)
- verbose_off {
- rm_f realdest if File.exist?(realdest)
- }
- File.open(realdest, 'wb') {|f|
- f.write str
- }
- File.chmod mode, realdest
-
- File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
- if prefix
- f.puts realdest.sub(prefix, '')
- else
- f.puts realdest
- end
- }
- end
- end
-
- def diff?(new_content, path)
- return true unless File.exist?(path)
- new_content != File.binread(path)
- end
-
- def command(str)
- $stderr.puts str if verbose?
- system str or raise RuntimeError, "'system #{str}' failed"
- end
-
- def ruby(str)
- command config('ruby-prog') + ' ' + str
- end
-
- def make(task = '')
- command config('make-prog') + ' ' + task
- end
-
- def extdir?(dir)
- File.exist?(dir + '/MANIFEST')
- end
-
- def all_files_in(dirname)
- Dir.open(dirname) {|d|
- return d.select {|ent| File.file?("#{dirname}/#{ent}") }
- }
- end
-
- REJECT_DIRS = %w(
- CVS SCCS RCS CVS.adm .svn
- )
-
- def all_dirs_in(dirname)
- Dir.open(dirname) {|d|
- return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
- }
- end
-
-end
-
-#
-# Main Installer
-#
-
-class InstallError < StandardError; end
-
-
-module HookUtils
-
- def run_hook(name)
- try_run_hook "#{curr_srcdir()}/#{name}" or
- try_run_hook "#{curr_srcdir()}/#{name}.rb"
- end
-
- def try_run_hook(fname)
- return false unless File.file?(fname)
- begin
- instance_eval File.read(fname), fname, 1
- rescue
- raise InstallError, "hook #{fname} failed:\n" + $!.message
- end
- true
- end
-
-end
-
-
-module HookScriptAPI
-
- def get_config(key)
- @config[key]
- end
-
- alias config get_config
-
- def set_config(key, val)
- @config[key] = val
- end
-
- #
- # srcdir/objdir (works only in the package directory)
- #
-
- #abstract srcdir_root
- #abstract objdir_root
- #abstract relpath
-
- def curr_srcdir
- "#{srcdir_root()}/#{relpath()}"
- end
-
- def curr_objdir
- "#{objdir_root()}/#{relpath()}"
- end
-
- def srcfile(path)
- "#{curr_srcdir()}/#{path}"
- end
-
- def srcexist?(path)
- File.exist?(srcfile(path))
- end
-
- def srcdirectory?(path)
- File.dir?(srcfile(path))
- end
-
- def srcfile?(path)
- File.file? srcfile(path)
- end
-
- def srcentries(path = '.')
- Dir.open("#{curr_srcdir()}/#{path}") {|d|
- return d.to_a - %w(. ..)
- }
- end
-
- def srcfiles(path = '.')
- srcentries(path).select {|fname|
- File.file?(File.join(curr_srcdir(), path, fname))
- }
- end
-
- def srcdirectories(path = '.')
- srcentries(path).select {|fname|
- File.dir?(File.join(curr_srcdir(), path, fname))
- }
- end
-
-end
-
-
-class ToplevelInstaller
-
- Version = '3.2.4'
- Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
-
- TASKS = [
- [ 'config', 'saves your configurations' ],
- [ 'show', 'shows current configuration' ],
- [ 'setup', 'compiles ruby extentions and others' ],
- [ 'install', 'installs files' ],
- [ 'clean', "does `make clean' for each extention" ],
- [ 'distclean',"does `make distclean' for each extention" ]
- ]
-
- def ToplevelInstaller.invoke
- instance().invoke
- end
-
- @singleton = nil
-
- def ToplevelInstaller.instance
- @singleton ||= new(File.dirname($0))
- @singleton
- end
-
- include MetaConfigAPI
-
- def initialize(ardir_root)
- @config = nil
- @options = { 'verbose' => true }
- @ardir = File.expand_path(ardir_root)
- end
-
- def inspect
- "#<#{self.class} #{__id__()}>"
- end
-
- def invoke
- run_metaconfigs
- task = parsearg_global()
- @config = load_config(task)
- __send__ "parsearg_#{task}"
- init_installers
- __send__ "exec_#{task}"
- end
-
- def run_metaconfigs
- eval_file_ifexist "#{@ardir}/metaconfig"
- end
-
- def load_config(task)
- case task
- when 'config'
- ConfigTable.new
- when 'clean', 'distclean'
- if File.exist?('config.save')
- then ConfigTable.load
- else ConfigTable.new
- end
- else
- ConfigTable.load
- end
- end
-
- def init_installers
- @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
- end
-
- #
- # Hook Script API bases
- #
-
- def srcdir_root
- @ardir
- end
-
- def objdir_root
- '.'
- end
-
- def relpath
- '.'
- end
-
- #
- # Option Parsing
- #
-
- def parsearg_global
- valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
-
- while arg = ARGV.shift
- case arg
- when /\A\w+\z/
- raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg
- return arg
-
- when '-q', '--quiet'
- @options['verbose'] = false
-
- when '--verbose'
- @options['verbose'] = true
-
- when '-h', '--help'
- print_usage $stdout
- exit 0
-
- when '-v', '--version'
- puts "#{File.basename($0)} version #{Version}"
- exit 0
-
- when '--copyright'
- puts Copyright
- exit 0
-
- else
- raise InstallError, "unknown global option '#{arg}'"
- end
- end
-
- raise InstallError, <<EOS
-No task or global option given.
-Typical installation procedure is:
- $ ruby #{File.basename($0)} config
- $ ruby #{File.basename($0)} setup
- # ruby #{File.basename($0)} install (may require root privilege)
-EOS
- end
-
-
- def parsearg_no_options
- raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"\
- unless ARGV.empty?
- end
-
- alias parsearg_show parsearg_no_options
- alias parsearg_setup parsearg_no_options
- alias parsearg_clean parsearg_no_options
- alias parsearg_distclean parsearg_no_options
-
- def parsearg_config
- re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
- @options['config-opt'] = []
-
- while i = ARGV.shift
- if /\A--?\z/ =~ i
- @options['config-opt'] = ARGV.dup
- break
- end
- m = re.match(i) or raise InstallError, "config: unknown option #{i}"
- name, value = m.to_a[1,2]
- if value
- if ConfigTable.bool_config?(name)
- raise InstallError, "config: --#{name} allows only yes/no for argument"\
- unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value
- value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
- end
- else
- raise InstallError, "config: --#{name} requires argument"\
- unless ConfigTable.bool_config?(name)
- value = 'yes'
- end
- @config[name] = value
- end
- end
-
- def parsearg_install
- @options['no-harm'] = false
- @options['install-prefix'] = ''
- while a = ARGV.shift
- case a
- when /\A--no-harm\z/
- @options['no-harm'] = true
- when /\A--prefix=(.*)\z/
- path = $1
- path = File.expand_path(path) unless path[0,1] == '/'
- @options['install-prefix'] = path
- else
- raise InstallError, "install: unknown option #{a}"
- end
- end
- end
-
- def print_usage(out)
- out.puts 'Typical Installation Procedure:'
- out.puts " $ ruby #{File.basename $0} config"
- out.puts " $ ruby #{File.basename $0} setup"
- out.puts " # ruby #{File.basename $0} install (may require root privilege)"
- out.puts
- out.puts 'Detailed Usage:'
- out.puts " ruby #{File.basename $0} <global option>"
- out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
-
- fmt = " %-20s %s\n"
- out.puts
- out.puts 'Global options:'
- out.printf fmt, '-q,--quiet', 'suppress message outputs'
- out.printf fmt, ' --verbose', 'output messages verbosely'
- out.printf fmt, '-h,--help', 'print this message'
- out.printf fmt, '-v,--version', 'print version and quit'
- out.printf fmt, ' --copyright', 'print copyright and quit'
-
- out.puts
- out.puts 'Tasks:'
- TASKS.each do |name, desc|
- out.printf " %-10s %s\n", name, desc
- end
-
- out.puts
- out.puts 'Options for config:'
- ConfigTable.each_definition do |name, (default, arg, desc, default2)|
- out.printf " %-20s %s [%s]\n",
- '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
- desc,
- default2 || default
- end
- out.printf " %-20s %s [%s]\n",
- '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
-
- out.puts
- out.puts 'Options for install:'
- out.printf " %-20s %s [%s]\n",
- '--no-harm', 'only display what to do if given', 'off'
- out.printf " %-20s %s [%s]\n",
- '--prefix', 'install path prefix', '$prefix'
-
- out.puts
- end
-
- #
- # Task Handlers
- #
-
- def exec_config
- @installer.exec_config
- @config.save # must be final
- end
-
- def exec_setup
- @installer.exec_setup
- end
-
- def exec_install
- @installer.exec_install
- end
-
- def exec_show
- ConfigTable.each_name do |k|
- v = @config.get_raw(k)
- if not v or v.empty?
- v = '(not specified)'
- end
- printf "%-10s %s\n", k, v
- end
- end
-
- def exec_clean
- @installer.exec_clean
- end
-
- def exec_distclean
- @installer.exec_distclean
- end
-
-end
-
-
-class ToplevelInstallerMulti < ToplevelInstaller
-
- include HookUtils
- include HookScriptAPI
- include FileOperations
-
- def initialize(ardir)
- super
- @packages = all_dirs_in("#{@ardir}/packages")
- raise 'no package exists' if @packages.empty?
- end
-
- def run_metaconfigs
- eval_file_ifexist "#{@ardir}/metaconfig"
- @packages.each do |name|
- eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
- end
- end
-
- def init_installers
- @installers = {}
- @packages.each do |pack|
- @installers[pack] = Installer.new(@config, @options,
- "#{@ardir}/packages/#{pack}",
- "packages/#{pack}")
- end
-
- with = extract_selection(config('with'))
- without = extract_selection(config('without'))
- @selected = @installers.keys.select {|name|
- (with.empty? or with.include?(name)) \
- and not without.include?(name)
- }
- end
-
- def extract_selection(list)
- a = list.split(/,/)
- a.each do |name|
- raise InstallError, "no such package: #{name}" \
- unless @installers.key?(name)
- end
- a
- end
-
- def print_usage(f)
- super
- f.puts 'Inluded packages:'
- f.puts ' ' + @packages.sort.join(' ')
- f.puts
- end
-
- #
- # multi-package metaconfig API
- #
-
- attr_reader :packages
-
- def declare_packages(list)
- raise 'package list is empty' if list.empty?
- list.each do |name|
- raise "directory packages/#{name} does not exist"\
- unless File.dir?("#{@ardir}/packages/#{name}")
- end
- @packages = list
- end
-
- #
- # Task Handlers
- #
-
- def exec_config
- run_hook 'pre-config'
- each_selected_installers {|inst| inst.exec_config }
- run_hook 'post-config'
- @config.save # must be final
- end
-
- def exec_setup
- run_hook 'pre-setup'
- each_selected_installers {|inst| inst.exec_setup }
- run_hook 'post-setup'
- end
-
- def exec_install
- run_hook 'pre-install'
- each_selected_installers {|inst| inst.exec_install }
- run_hook 'post-install'
- end
-
- def exec_clean
- rm_f 'config.save'
- run_hook 'pre-clean'
- each_selected_installers {|inst| inst.exec_clean }
- run_hook 'post-clean'
- end
-
- def exec_distclean
- rm_f 'config.save'
- run_hook 'pre-distclean'
- each_selected_installers {|inst| inst.exec_distclean }
- run_hook 'post-distclean'
- end
-
- #
- # lib
- #
-
- def each_selected_installers
- Dir.mkdir 'packages' unless File.dir?('packages')
- @selected.each do |pack|
- $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
- Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
- Dir.chdir "packages/#{pack}"
- yield @installers[pack]
- Dir.chdir '../..'
- end
- end
-
- def verbose?
- @options['verbose']
- end
-
- def no_harm?
- @options['no-harm']
- end
-
-end
-
-
-class Installer
-
- FILETYPES = %w( bin lib ext data )
-
- include HookScriptAPI
- include HookUtils
- include FileOperations
-
- def initialize(config, opt, srcroot, objroot)
- @config = config
- @options = opt
- @srcdir = File.expand_path(srcroot)
- @objdir = File.expand_path(objroot)
- @currdir = '.'
- end
-
- def inspect
- "#<#{self.class} #{File.basename(@srcdir)}>"
- end
-
- #
- # Hook Script API bases
- #
-
- def srcdir_root
- @srcdir
- end
-
- def objdir_root
- @objdir
- end
-
- def relpath
- @currdir
- end
-
- #
- # configs/options
- #
-
- def no_harm?
- @options['no-harm']
- end
-
- def verbose?
- @options['verbose']
- end
-
- def verbose_off
- begin
- save, @options['verbose'] = @options['verbose'], false
- yield
- ensure
- @options['verbose'] = save
- end
- end
-
- #
- # TASK config
- #
-
- def exec_config
- exec_task_traverse 'config'
- end
-
- def config_dir_bin(rel)
- end
-
- def config_dir_lib(rel)
- end
-
- def config_dir_ext(rel)
- extconf if extdir?(curr_srcdir())
- end
-
- def extconf
- opt = @options['config-opt'].join(' ')
- command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}"
- end
-
- def config_dir_data(rel)
- end
-
- #
- # TASK setup
- #
-
- def exec_setup
- exec_task_traverse 'setup'
- end
-
- def setup_dir_bin(rel)
- all_files_in(curr_srcdir()).each do |fname|
- adjust_shebang "#{curr_srcdir()}/#{fname}"
- end
- end
-
- # modify: #!/usr/bin/ruby
- # modify: #! /usr/bin/ruby
- # modify: #!ruby
- # not modify: #!/usr/bin/env ruby
- SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
-
- def adjust_shebang(path)
- return if no_harm?
-
- tmpfile = File.basename(path) + '.tmp'
- begin
- File.open(path, 'rb') {|r|
- File.open(tmpfile, 'wb') {|w|
- first = r.gets
- return unless SHEBANG_RE =~ first
-
- $stderr.puts "adjusting shebang: #{File.basename path}" if verbose?
- w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
- w.write r.read
- }
- }
- move_file tmpfile, File.basename(path)
- ensure
- File.unlink tmpfile if File.exist?(tmpfile)
- end
- end
-
- def setup_dir_lib(rel)
- end
-
- def setup_dir_ext(rel)
- make if extdir?(curr_srcdir())
- end
-
- def setup_dir_data(rel)
- end
-
- #
- # TASK install
- #
-
- def exec_install
- exec_task_traverse 'install'
- end
-
- def install_dir_bin(rel)
- install_files collect_filenames_auto(), "#{config('bin-dir')}/#{rel}", 0755
- end
-
- def install_dir_lib(rel)
- install_files ruby_scripts(), "#{config('rb-dir')}/#{rel}", 0644
- end
-
- def install_dir_ext(rel)
- return unless extdir?(curr_srcdir())
- install_files ruby_extentions('.'),
- "#{config('so-dir')}/#{File.dirname(rel)}",
- 0555
- end
-
- def install_dir_data(rel)
- install_files collect_filenames_auto(), "#{config('data-dir')}/#{rel}", 0644
- end
-
- def install_files(list, dest, mode)
- mkdir_p dest, @options['install-prefix']
- list.each do |fname|
- install fname, dest, mode, @options['install-prefix']
- end
- end
-
- def ruby_scripts
- collect_filenames_auto().select {|n| /\.r(b|html)\z/ =~ n}
- end
-
- # picked up many entries from cvs-1.11.1/src/ignore.c
- reject_patterns = %w(
- core RCSLOG tags TAGS .make.state
- .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
- *~ *.old *.bak *.BAK *.orig *.rej _$* *$
-
- *.org *.in .*
- )
- mapping = {
- '.' => '\.',
- '$' => '\$',
- '#' => '\#',
- '*' => '.*'
- }
- REJECT_PATTERNS = Regexp.new('\A(?:' +
- reject_patterns.map {|pat|
- pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
- }.join('|') +
- ')\z')
-
- def collect_filenames_auto
- mapdir((existfiles() - hookfiles()).reject {|fname|
- REJECT_PATTERNS =~ fname
- })
- end
-
- def existfiles
- all_files_in(curr_srcdir()) | all_files_in('.')
- end
-
- def hookfiles
- %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
- %w( config setup install clean ).map {|t| sprintf(fmt, t) }
- }.flatten
- end
-
- def mapdir(filelist)
- filelist.map {|fname|
- if File.exist?(fname) # objdir
- fname
- else # srcdir
- File.join(curr_srcdir(), fname)
- end
- }
- end
-
- def ruby_extentions(dir)
- _ruby_extentions(dir) or
- raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first"
- end
-
- DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
-
- def _ruby_extentions(dir)
- Dir.open(dir) {|d|
- return d.select {|fname| DLEXT =~ fname }
- }
- end
-
- #
- # TASK clean
- #
-
- def exec_clean
- exec_task_traverse 'clean'
- rm_f 'config.save'
- rm_f 'InstalledFiles'
- end
-
- def clean_dir_bin(rel)
- end
-
- def clean_dir_lib(rel)
- end
-
- def clean_dir_ext(rel)
- return unless extdir?(curr_srcdir())
- make 'clean' if File.file?('Makefile')
- end
-
- def clean_dir_data(rel)
- end
-
- #
- # TASK distclean
- #
-
- def exec_distclean
- exec_task_traverse 'distclean'
- rm_f 'config.save'
- rm_f 'InstalledFiles'
- end
-
- def distclean_dir_bin(rel)
- end
-
- def distclean_dir_lib(rel)
- end
-
- def distclean_dir_ext(rel)
- return unless extdir?(curr_srcdir())
- make 'distclean' if File.file?('Makefile')
- end
-
- #
- # lib
- #
-
- def exec_task_traverse(task)
- run_hook "pre-#{task}"
- FILETYPES.each do |type|
- if config('without-ext') == 'yes' and type == 'ext'
- $stderr.puts 'skipping ext/* by user option' if verbose?
- next
- end
- traverse task, type, "#{task}_dir_#{type}"
- end
- run_hook "post-#{task}"
- end
-
- def traverse(task, rel, mid)
- dive_into(rel) {
- run_hook "pre-#{task}"
- __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
- all_dirs_in(curr_srcdir()).each do |d|
- traverse task, "#{rel}/#{d}", mid
- end
- run_hook "post-#{task}"
- }
- end
-
- def dive_into(rel)
- return unless File.dir?("#{@srcdir}/#{rel}")
-
- dir = File.basename(rel)
- Dir.mkdir dir unless File.dir?(dir)
- prevdir = Dir.pwd
- Dir.chdir dir
- $stderr.puts '---> ' + rel if verbose?
- @currdir = rel
- yield
- Dir.chdir prevdir
- $stderr.puts '<--- ' + rel if verbose?
- @currdir = File.dirname(rel)
- end
-
-end
-
-
-if $0 == __FILE__
- begin
- if multipackage_install?
- ToplevelInstallerMulti.invoke
- else
- ToplevelInstaller.invoke
- end
- rescue
- raise if $DEBUG
- $stderr.puts $!.message
- $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
- exit 1
- end
-end
diff --git a/switchtower/switchtower.gemspec b/switchtower/switchtower.gemspec
deleted file mode 100644
index 679153aeb0..0000000000
--- a/switchtower/switchtower.gemspec
+++ /dev/null
@@ -1,28 +0,0 @@
-require './lib/switchtower/version'
-
-Gem::Specification.new do |s|
-
- s.name = 'switchtower'
- s.version = PKG_VERSION
- s.platform = Gem::Platform::RUBY
- s.summary = <<-DESC.strip.gsub(/\n/, " ")
- SwitchTower is a framework and utility for executing commands in parallel
- on multiple remote machines, via SSH. The primary goal is to simplify and
- automate the deployment of web applications.
- DESC
-
- s.files = Dir.glob("{bin,lib,examples,test}/**/*")
- s.files.concat %w(README MIT-LICENSE ChangeLog)
- s.require_path = 'lib'
- s.autorequire = 'switchtower'
-
- s.bindir = "bin"
- s.executables << "switchtower"
-
- s.add_dependency 'net-ssh', '>= 1.0.2'
-
- s.author = "Jamis Buck"
- s.email = "jamis@37signals.com"
- s.homepage = "http://www.rubyonrails.com"
-
-end
diff --git a/switchtower/test/actor_test.rb b/switchtower/test/actor_test.rb
deleted file mode 100644
index ed657ee26e..0000000000
--- a/switchtower/test/actor_test.rb
+++ /dev/null
@@ -1,261 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../lib"
-
-require 'stringio'
-require 'test/unit'
-require 'switchtower/actor'
-require 'switchtower/logger'
-
-class ActorTest < Test::Unit::TestCase
-
- class TestingConnectionFactory
- def initialize(config)
- end
-
- def connect_to(server)
- server
- end
- end
-
- class GatewayConnectionFactory
- def connect_to(server)
- server
- end
- end
-
- class TestingCommand
- def self.invoked!
- @invoked = true
- end
-
- def self.invoked?
- @invoked
- end
-
- def self.reset!
- @invoked = nil
- end
-
- def initialize(*args)
- end
-
- def process!
- self.class.invoked!
- end
- end
-
- class TestActor < SwitchTower::Actor
- attr_reader :factory
-
- self.connection_factory = TestingConnectionFactory
- self.command_factory = TestingCommand
-
- def establish_gateway
- GatewayConnectionFactory.new
- end
- end
-
- class MockConfiguration
- Role = Struct.new(:host, :options)
-
- attr_accessor :gateway, :pretend
-
- def delegated_method
- "result of method"
- end
-
- ROLES = { :db => [ Role.new("01.example.com", :primary => true),
- Role.new("02.example.com", {}),
- Role.new("all.example.com", {})],
- :web => [ Role.new("03.example.com", {}),
- Role.new("04.example.com", {}),
- Role.new("all.example.com", {})],
- :app => [ Role.new("05.example.com", {}),
- Role.new("06.example.com", {}),
- Role.new("07.example.com", {}),
- Role.new("all.example.com", {})] }
-
- def roles
- ROLES
- end
-
- def logger
- @logger ||= SwitchTower::Logger.new(:output => StringIO.new)
- end
- end
-
- def setup
- TestingCommand.reset!
- @actor = TestActor.new(MockConfiguration.new)
- end
-
- def test_define_task_creates_method
- @actor.define_task :hello do
- "result"
- end
- assert @actor.respond_to?(:hello)
- assert_equal "result", @actor.hello
- end
-
- def test_define_task_with_successful_transaction
- class << @actor
- attr_reader :rolled_back
- attr_reader :history
- end
-
- @actor.define_task :hello do
- (@history ||= []) << :hello
- on_rollback { @rolled_back = true }
- "hello"
- end
-
- @actor.define_task :goodbye do
- (@history ||= []) << :goodbye
- transaction do
- hello
- end
- "goodbye"
- end
-
- assert_nothing_raised { @actor.goodbye }
- assert !@actor.rolled_back
- assert_equal [:goodbye, :hello], @actor.history
- end
-
- def test_define_task_with_failed_transaction
- class << @actor
- attr_reader :rolled_back
- attr_reader :history
- end
-
- @actor.define_task :hello do
- (@history ||= []) << :hello
- on_rollback { @rolled_back = true }
- "hello"
- end
-
- @actor.define_task :goodbye do
- (@history ||= []) << :goodbye
- transaction do
- hello
- raise "ouch"
- end
- "goodbye"
- end
-
- assert_raise(RuntimeError) do
- @actor.goodbye
- end
-
- assert @actor.rolled_back
- assert_equal [:goodbye, :hello], @actor.history
- end
-
- def test_delegates_to_configuration
- @actor.define_task :hello do
- delegated_method
- end
- assert_equal "result of method", @actor.hello
- end
-
- def test_task_servers_with_duplicates
- @actor.define_task :foo do
- run "do this"
- end
-
- assert_equal %w(01.example.com 02.example.com 03.example.com 04.example.com 05.example.com 06.example.com 07.example.com all.example.com), @actor.tasks[:foo].servers(@actor.configuration).sort
- end
-
- def test_run_in_task_without_explicit_roles_selects_all_roles
- @actor.define_task :foo do
- run "do this"
- end
-
- @actor.foo
- assert_equal %w(01.example.com 02.example.com 03.example.com 04.example.com 05.example.com 06.example.com 07.example.com all.example.com), @actor.sessions.keys.sort
- end
-
- def test_run_in_task_with_single_role_selects_that_role
- @actor.define_task :foo, :roles => :db do
- run "do this"
- end
-
- @actor.foo
- assert_equal %w(01.example.com 02.example.com all.example.com), @actor.sessions.keys.sort
- end
-
- def test_run_in_task_with_multiple_roles_selects_those_roles
- @actor.define_task :foo, :roles => [:db, :web] do
- run "do this"
- end
-
- @actor.foo
- assert_equal %w(01.example.com 02.example.com 03.example.com 04.example.com all.example.com), @actor.sessions.keys.sort
- end
-
- def test_run_in_task_with_only_restricts_selected_roles
- @actor.define_task :foo, :roles => :db, :only => { :primary => true } do
- run "do this"
- end
-
- @actor.foo
- assert_equal %w(01.example.com), @actor.sessions.keys.sort
- end
-
- def test_establish_connection_uses_gateway_if_specified
- @actor.configuration.gateway = "10.example.com"
- @actor.define_task :foo, :roles => :db do
- run "do this"
- end
-
- @actor.foo
- assert_instance_of GatewayConnectionFactory, @actor.factory
- end
-
- def test_run_when_not_pretend
- @actor.define_task :foo do
- run "do this"
- end
-
- @actor.configuration.pretend = false
- @actor.foo
- assert TestingCommand.invoked?
- end
-
- def test_run_when_pretend
- @actor.define_task :foo do
- run "do this"
- end
-
- @actor.configuration.pretend = true
- @actor.foo
- assert !TestingCommand.invoked?
- end
-
- def test_task_before_hook
- history = []
- @actor.define_task :foo do
- history << "foo"
- end
-
- @actor.define_task :before_foo do
- history << "before_foo"
- end
-
- @actor.foo
- assert_equal %w(before_foo foo), history
- end
-
- def test_task_after_hook
- history = []
- @actor.define_task :foo do
- history << "foo"
- end
-
- @actor.define_task :after_foo do
- history << "after_foo"
- end
-
- @actor.foo
- assert_equal %w(foo after_foo), history
- end
-end
diff --git a/switchtower/test/command_test.rb b/switchtower/test/command_test.rb
deleted file mode 100644
index 7005ba0f2d..0000000000
--- a/switchtower/test/command_test.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../lib"
-
-require 'stringio'
-require 'test/unit'
-require 'switchtower/command'
-
-class CommandTest < Test::Unit::TestCase
- class MockSession
- def open_channel
- { :closed => true, :status => 0 }
- end
- end
-
- class MockActor
- attr_reader :sessions
-
- def initialize
- @sessions = Hash.new { |h,k| h[k] = MockSession.new }
- end
- end
-
- def setup
- @actor = MockActor.new
- end
-
- def test_command_executes_on_all_servers
- command = SwitchTower::Command.new(%w(server1 server2 server3),
- "hello", nil, {}, @actor)
- assert_equal %w(server1 server2 server3), @actor.sessions.keys.sort
- end
-
- def test_command_with_newlines
- command = SwitchTower::Command.new(%w(server1), "hello\nworld", nil, {},
- @actor)
- assert_equal "hello\\\nworld", command.command
- end
-
- def test_command_with_windows_newlines
- command = SwitchTower::Command.new(%w(server1), "hello\r\nworld", nil, {},
- @actor)
- assert_equal "hello\\\nworld", command.command
- end
-end
diff --git a/switchtower/test/configuration_test.rb b/switchtower/test/configuration_test.rb
deleted file mode 100644
index ecbc0a5607..0000000000
--- a/switchtower/test/configuration_test.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../lib"
-
-require 'test/unit'
-require 'switchtower/configuration'
-require 'flexmock'
-
-class ConfigurationTest < Test::Unit::TestCase
- class MockActor
- attr_reader :tasks
-
- def initialize(config)
- end
-
- def define_task(*args, &block)
- (@tasks ||= []).push [args, block].flatten
- end
- end
-
- class MockSCM
- attr_reader :configuration
-
- def initialize(config)
- @configuration = config
- end
- end
-
- def setup
- @config = SwitchTower::Configuration.new(MockActor)
- @config.set :scm, MockSCM
- end
-
- def test_version_dir_default
- assert "releases", @config.version_dir
- end
-
- def test_current_dir_default
- assert "current", @config.current_dir
- end
-
- def test_shared_dir_default
- assert "shared", @config.shared_dir
- end
-
- def test_set_repository
- @config.set :repository, "/foo/bar/baz"
- assert_equal "/foo/bar/baz", @config.repository
- end
-
- def test_set_user
- @config.set :user, "flippy"
- assert_equal "flippy", @config.user
- end
-
- def test_define_single_role
- @config.role :app, "somewhere.example.com"
- assert_equal 1, @config.roles[:app].length
- assert_equal "somewhere.example.com", @config.roles[:app].first.host
- assert_equal Hash.new, @config.roles[:app].first.options
- end
-
- def test_define_single_role_with_options
- @config.role :app, "somewhere.example.com", :primary => true
- assert_equal 1, @config.roles[:app].length
- assert_equal "somewhere.example.com", @config.roles[:app].first.host
- assert_equal({:primary => true}, @config.roles[:app].first.options)
- end
-
- def test_define_multi_role
- @config.role :app, "somewhere.example.com", "else.example.com"
- assert_equal 2, @config.roles[:app].length
- assert_equal "somewhere.example.com", @config.roles[:app].first.host
- assert_equal "else.example.com", @config.roles[:app].last.host
- assert_equal({}, @config.roles[:app].first.options)
- assert_equal({}, @config.roles[:app].last.options)
- end
-
- def test_define_multi_role_with_options
- @config.role :app, "somewhere.example.com", "else.example.com", :primary => true
- assert_equal 2, @config.roles[:app].length
- assert_equal "somewhere.example.com", @config.roles[:app].first.host
- assert_equal "else.example.com", @config.roles[:app].last.host
- assert_equal({:primary => true}, @config.roles[:app].first.options)
- assert_equal({:primary => true}, @config.roles[:app].last.options)
- end
-
- def test_load_string_unnamed
- @config.load :string => "set :repository, __FILE__"
- assert_equal "<eval>", @config.repository
- end
-
- def test_load_string_named
- @config.load :string => "set :repository, __FILE__", :name => "test.rb"
- assert_equal "test.rb", @config.repository
- end
-
- def test_load
- file = File.dirname(__FILE__) + "/fixtures/config.rb"
- @config.load file
- assert_equal "1/2/foo", @config.repository
- assert_equal "./#{file}.example.com", @config.gateway
- assert_equal 1, @config.roles[:web].length
- end
-
- def test_load_explicit_name
- file = File.dirname(__FILE__) + "/fixtures/config.rb"
- @config.load file, :name => "config"
- assert_equal "1/2/foo", @config.repository
- assert_equal "config.example.com", @config.gateway
- assert_equal 1, @config.roles[:web].length
- end
-
- def test_load_file_implied_name
- file = File.dirname(__FILE__) + "/fixtures/config.rb"
- @config.load :file => file
- assert_equal "1/2/foo", @config.repository
- assert_equal "./#{file}.example.com", @config.gateway
- assert_equal 1, @config.roles[:web].length
- end
-
- def test_load_file_explicit_name
- file = File.dirname(__FILE__) + "/fixtures/config.rb"
- @config.load :file => file, :name => "config"
- assert_equal "1/2/foo", @config.repository
- assert_equal "config.example.com", @config.gateway
- assert_equal 1, @config.roles[:web].length
- end
-
- def test_task_without_options
- block = Proc.new { }
- @config.task :hello, &block
- assert_equal 1, @config.actor.tasks.length
- assert_equal :hello, @config.actor.tasks[0][0]
- assert_equal({}, @config.actor.tasks[0][1])
- assert_equal block, @config.actor.tasks[0][2]
- end
-
- def test_task_with_options
- block = Proc.new { }
- @config.task :hello, :roles => :app, &block
- assert_equal 1, @config.actor.tasks.length
- assert_equal :hello, @config.actor.tasks[0][0]
- assert_equal({:roles => :app}, @config.actor.tasks[0][1])
- assert_equal block, @config.actor.tasks[0][2]
- end
-
- def test_source
- @config.set :repository, "/foo/bar/baz"
- assert_equal "/foo/bar/baz", @config.source.configuration.repository
- end
-
- def test_releases_path_default
- @config.set :deploy_to, "/start/of/path"
- assert_equal "/start/of/path/releases", @config.releases_path
- end
-
- def test_releases_path_custom
- @config.set :deploy_to, "/start/of/path"
- @config.set :version_dir, "right/here"
- assert_equal "/start/of/path/right/here", @config.releases_path
- end
-
- def test_current_path_default
- @config.set :deploy_to, "/start/of/path"
- assert_equal "/start/of/path/current", @config.current_path
- end
-
- def test_current_path_custom
- @config.set :deploy_to, "/start/of/path"
- @config.set :current_dir, "right/here"
- assert_equal "/start/of/path/right/here", @config.current_path
- end
-
- def test_shared_path_default
- @config.set :deploy_to, "/start/of/path"
- assert_equal "/start/of/path/shared", @config.shared_path
- end
-
- def test_shared_path_custom
- @config.set :deploy_to, "/start/of/path"
- @config.set :shared_dir, "right/here"
- assert_equal "/start/of/path/right/here", @config.shared_path
- end
-
- def test_release_path_implicit
- @config.set :deploy_to, "/start/of/path"
- assert_equal "/start/of/path/releases/#{@config.now.strftime("%Y%m%d%H%M%S")}", @config.release_path
- end
-
- def test_release_path_explicit
- @config.set :deploy_to, "/start/of/path"
- assert_equal "/start/of/path/releases/silly", @config.release_path("silly")
- end
-
- def test_task_description
- block = Proc.new { }
- @config.desc "A sample task"
- @config.task :hello, &block
- assert_equal "A sample task", @config.actor.tasks[0][1][:desc]
- end
-
- def test_set_scm_to_darcs
- @config.set :scm, :darcs
- assert_equal "SwitchTower::SCM::Darcs", @config.source.class.name
- end
-
- def test_set_scm_to_subversion
- @config.set :scm, :subversion
- assert_equal "SwitchTower::SCM::Subversion", @config.source.class.name
- end
-end
diff --git a/switchtower/test/fixtures/config.rb b/switchtower/test/fixtures/config.rb
deleted file mode 100644
index 0570980bd8..0000000000
--- a/switchtower/test/fixtures/config.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-set :application, "foo"
-set :repository, "1/2/#{application}"
-set :gateway, "#{__FILE__}.example.com"
-
-role :web, "www.example.com", :primary => true
diff --git a/switchtower/test/scm/cvs_test.rb b/switchtower/test/scm/cvs_test.rb
deleted file mode 100644
index 4532870826..0000000000
--- a/switchtower/test/scm/cvs_test.rb
+++ /dev/null
@@ -1,164 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../../lib"
-
-require File.dirname(__FILE__) + "/../utils"
-require 'test/unit'
-require 'switchtower/scm/cvs'
-
-class ScmCvsTest < Test::Unit::TestCase
- class CvsTest < SwitchTower::SCM::Cvs
- attr_accessor :story
- attr_reader :last_path
-
- def cvs_log(path)
- @last_path = path
- story.shift
- end
- end
-
- class MockChannel
- attr_reader :sent_data
-
- def send_data(data)
- @sent_data ||= []
- @sent_data << data
- end
-
- def [](name)
- "value"
- end
- end
-
- class MockActor
- attr_reader :command
- attr_reader :channels
- attr_accessor :story
-
- def initialize(config)
- @config = config
- end
-
- def run(command)
- @command = command
- @channels ||= []
- @channels << MockChannel.new
- story.each { |stream, line| yield @channels.last, stream, line }
- end
-
- def release_path
- (@config[:now] || Time.now.utc).strftime("%Y%m%d%H%M%S")
- end
-
- def method_missing(sym, *args)
- @config.send(sym, *args)
- end
- end
-
- def setup
- @config = MockConfiguration.new
- @config[:repository] = ":ext:joetester@rubyforge.org:/hello/world"
- @config[:local] = "/hello/world"
- @config[:cvs] = "/path/to/cvs"
- @config[:password] = "chocolatebrownies"
- @config[:now] = Time.utc(2005,8,24,12,0,0)
- @scm = CvsTest.new(@config)
- @actor = MockActor.new(@config)
- @log_msg = <<MSG.strip
-RCS file: /var/cvs/copland/copland/LICENSE,v
-Working file: LICENSE
-head: 1.1
-branch:
-locks: strict
-access list:
-keyword substitution: kv
-total revisions: 1; selected revisions: 1
-description:
-----------------------------
-revision 1.1
-date: 2004/08/29 04:23:36; author: minam; state: Exp;
-New implementation.
-=============================================================================
-
-RCS file: /var/cvs/copland/copland/Rakefile,v
-Working file: Rakefile
-head: 1.7
-branch:
-locks: strict
-access list:
-keyword substitution: kv
-total revisions: 7; selected revisions: 1
-description:
-----------------------------
-revision 1.7
-date: 2004/09/15 16:35:01; author: minam; state: Exp; lines: +2 -1
-Rakefile now publishes package documentation from doc/packages instead of
-doc/packrat. Updated "latest updates" in manual.
-=============================================================================
-
-RCS file: /var/cvs/copland/copland/TODO,v
-Working file: TODO
-head: 1.18
-branch:
-locks: strict
-access list:
-keyword substitution: kv
-total revisions: 18; selected revisions: 1
-description:
-----------------------------
-revision 1.18
-date: 2004/10/12 02:21:02; author: minam; state: Exp; lines: +4 -1
-Added RubyConf 2004 presentation.
-=============================================================================
-
-RCS file: /var/cvs/copland/copland/Attic/build-gemspec.rb,v
-Working file: build-gemspec.rb
-head: 1.5
-branch:
-locks: strict
-access list:
-keyword substitution: kv
-total revisions: 5; selected revisions: 1
-description:
-----------------------------
-revision 1.5
-date: 2004/08/29 04:10:17; author: minam; state: dead; lines: +0 -0
-Here we go -- point of no return. Deleting existing implementation to make
-way for new implementation.
-=============================================================================
-
-RCS file: /var/cvs/copland/copland/copland.gemspec,v
-Working file: copland.gemspec
-head: 1.12
-branch:
-locks: strict
-access list:
-keyword substitution: kv
-total revisions: 13; selected revisions: 1
-description:
-----------------------------
-revision 1.12
-date: 2004/09/11 21:45:58; author: minam; state: Exp; lines: +4 -4
-Minor change in how version is communicated to gemspec.
-=============================================================================
-MSG
- @scm.story = [ @log_msg ]
- end
-
- def test_latest_revision
- @scm.story = [ @log_msg ]
- assert_equal "2004-10-12 02:21:02", @scm.latest_revision
- assert_equal "/hello/world", @scm.last_path
- end
-
- def test_checkout
- @actor.story = []
- assert_nothing_raised { @scm.checkout(@actor) }
- assert_nil @actor.channels.last.sent_data
- assert_match %r{/path/to/cvs}, @actor.command
- end
-
- def test_checkout_needs_ssh_password
- @actor.story = [[:out, "joetester@rubyforge.org's password: "]]
- assert_nothing_raised { @scm.checkout(@actor) }
- assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
- end
-end
diff --git a/switchtower/test/scm/subversion_test.rb b/switchtower/test/scm/subversion_test.rb
deleted file mode 100644
index fa65714b6b..0000000000
--- a/switchtower/test/scm/subversion_test.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../../lib"
-
-require File.dirname(__FILE__) + "/../utils"
-require 'test/unit'
-require 'switchtower/scm/subversion'
-
-class ScmSubversionTest < Test::Unit::TestCase
- class SubversionTest < SwitchTower::SCM::Subversion
- attr_accessor :story
- attr_reader :last_path
-
- def svn_log(path)
- @last_path = path
- story.shift
- end
- end
-
- class MockChannel
- attr_reader :sent_data
-
- def send_data(data)
- @sent_data ||= []
- @sent_data << data
- end
-
- def [](name)
- "value"
- end
- end
-
- class MockActor
- attr_reader :command
- attr_reader :channels
- attr_accessor :story
-
- def initialize(config)
- @config = config
- end
-
- def run(command)
- @command = command
- @channels ||= []
- @channels << MockChannel.new
- story.each { |stream, line| yield @channels.last, stream, line }
- end
-
- def method_missing(sym, *args)
- @config.send(sym, *args)
- end
- end
-
- def setup
- @config = MockConfiguration.new
- @config[:repository] = "/hello/world"
- @config[:svn] = "/path/to/svn"
- @config[:password] = "chocolatebrownies"
- @scm = SubversionTest.new(@config)
- @actor = MockActor.new(@config)
- @log_msg = <<MSG.strip
-------------------------------------------------------------------------
-r1967 | minam | 2005-08-03 06:59:03 -0600 (Wed, 03 Aug 2005) | 2 lines
-
-Initial commit of the new switchtower utility
-
-------------------------------------------------------------------------
-MSG
- @scm.story = [ @log_msg ]
- end
-
- def test_latest_revision
- @scm.story = [ @log_msg ]
- assert_equal "1967", @scm.latest_revision
- assert_equal "/hello/world", @scm.last_path
- end
-
- def test_latest_revision_searching_upwards
- @scm.story = [ "-----------------------------\n", @log_msg ]
- assert_equal "1967", @scm.latest_revision
- assert_equal "/hello", @scm.last_path
- end
-
- def test_checkout
- @actor.story = []
- assert_nothing_raised { @scm.checkout(@actor) }
- assert_nil @actor.channels.last.sent_data
- assert_match %r{/path/to/svn}, @actor.command
- end
-
- def test_checkout_needs_ssh_password
- @actor.story = [[:out, "Password: "]]
- assert_nothing_raised { @scm.checkout(@actor) }
- assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
- end
-
- def test_checkout_needs_http_password
- @actor.story = [[:out, "Password for (something): "]]
- assert_nothing_raised { @scm.checkout(@actor) }
- assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
- end
-end
diff --git a/switchtower/test/ssh_test.rb b/switchtower/test/ssh_test.rb
deleted file mode 100644
index 60e791beb3..0000000000
--- a/switchtower/test/ssh_test.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../lib"
-
-require File.dirname(__FILE__) + "/utils"
-require 'test/unit'
-require 'switchtower/ssh'
-
-class SSHTest < Test::Unit::TestCase
- class MockSSH
- AuthenticationFailed = Net::SSH::AuthenticationFailed
-
- class <<self
- attr_accessor :story
- attr_accessor :invocations
- end
-
- def self.start(server, opts, &block)
- @invocations << [server, opts, block]
- err = story.shift
- raise err if err
- end
- end
-
- def setup
- @config = MockConfiguration.new
- @config[:user] = 'demo'
- @config[:password] = 'c0c0nutfr0st1ng'
- MockSSH.story = []
- MockSSH.invocations = []
- end
-
- def test_publickey_auth_succeeds_default_port_no_block
- Net.const_during(:SSH, MockSSH) do
- SwitchTower::SSH.connect('demo.server.i', @config)
- end
-
- assert_equal 1, MockSSH.invocations.length
- assert_equal 'demo.server.i', MockSSH.invocations.first[0]
- assert_equal 22, MockSSH.invocations.first[1][:port]
- assert_equal 'demo', MockSSH.invocations.first[1][:username]
- assert_nil MockSSH.invocations.first[1][:password]
- assert_equal %w(publickey hostbased),
- MockSSH.invocations.first[1][:auth_methods]
- assert_nil MockSSH.invocations.first[2]
- end
-
- def test_publickey_auth_succeeds_explicit_port_no_block
- Net.const_during(:SSH, MockSSH) do
- SwitchTower::SSH.connect('demo.server.i', @config, 23)
- end
-
- assert_equal 1, MockSSH.invocations.length
- assert_equal 23, MockSSH.invocations.first[1][:port]
- assert_nil MockSSH.invocations.first[2]
- end
-
- def test_publickey_auth_succeeds_with_block
- Net.const_during(:SSH, MockSSH) do
- SwitchTower::SSH.connect('demo.server.i', @config) do |session|
- end
- end
-
- assert_equal 1, MockSSH.invocations.length
- assert_instance_of Proc, MockSSH.invocations.first[2]
- end
-
- def test_publickey_auth_fails
- MockSSH.story << Net::SSH::AuthenticationFailed
-
- Net.const_during(:SSH, MockSSH) do
- SwitchTower::SSH.connect('demo.server.i', @config)
- end
-
- assert_equal 2, MockSSH.invocations.length
-
- assert_nil MockSSH.invocations.first[1][:password]
- assert_equal %w(publickey hostbased),
- MockSSH.invocations.first[1][:auth_methods]
-
- assert_equal 'c0c0nutfr0st1ng', MockSSH.invocations.last[1][:password]
- assert_equal %w(password keyboard-interactive),
- MockSSH.invocations.last[1][:auth_methods]
- end
-
- def test_password_auth_fails
- MockSSH.story << Net::SSH::AuthenticationFailed
- MockSSH.story << Net::SSH::AuthenticationFailed
-
- Net.const_during(:SSH, MockSSH) do
- assert_raises(Net::SSH::AuthenticationFailed) do
- SwitchTower::SSH.connect('demo.server.i', @config)
- end
- end
-
- assert_equal 2, MockSSH.invocations.length
-
- assert_nil MockSSH.invocations.first[1][:password]
- assert_equal %w(publickey hostbased),
- MockSSH.invocations.first[1][:auth_methods]
-
- assert_equal 'c0c0nutfr0st1ng', MockSSH.invocations.last[1][:password]
- assert_equal %w(password keyboard-interactive),
- MockSSH.invocations.last[1][:auth_methods]
- end
-end
diff --git a/switchtower/test/utils.rb b/switchtower/test/utils.rb
deleted file mode 100644
index 4ddfbdbad1..0000000000
--- a/switchtower/test/utils.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-class Module
- def const_during(constant, value)
- if const_defined?(constant)
- overridden = true
- saved = const_get(constant)
- remove_const(constant)
- end
-
- const_set(constant, value)
- yield
- ensure
- if overridden
- remove_const(constant)
- const_set(constant, saved)
- end
- end
-end
-
-class MockLogger
- def info(msg,pfx=nil) end
- def debug(msg,pfx=nil) end
-end
-
-class MockConfiguration < Hash
- def initialize(*args)
- super
- self[:release_path] = "/path/to/releases/version"
- end
-
- def logger
- @logger ||= MockLogger.new
- end
-
- def method_missing(sym, *args)
- if args.length == 0
- self[sym]
- else
- super
- end
- end
-end