# =std.rb: Capistrano Standard Methods # Standard library of procedures and functions that you can use with Capistrano. # # ---- # Copyright (c) 2007 Neil Wilson, Aldur Systems Ltd # # Licensed under the GNU Public License v2. No warranty is provided. require 'capistrano' # = Purpose # Std is a Capistrano plugin that provides a set of standard methods refactored # out of several Capistrano task libraries. # # Installs within Capistrano as the plugin _std_ # # = Usage # # require 'vmbuilder_plugins/std' # # Prefix all calls to the library with std. module Std begin # Use the Mmap class if it is available # http://moulon.inra.fr/ruby/mmap.html require 'mmap' MMAP=true #:nodoc: rescue LoadError # no MMAP class, use normal reads instead MMAP=false #:nodoc: end # Copies the files specified by +file_pattern+ to +destination+ # # Error checking is minimal - a pattern onto a single file will result in +destination+ # containing the data from the last file only. # # Installs via *sudo*, +options+ are as for *put*. def fput(file_pattern, destination, options={}) logger.info file_pattern Dir.glob(file_pattern) do |fname| if File.readable?(fname) then if MMAP logger.debug "Using Memory Mapped File Upload" fdata=Mmap.new(fname,"r", Mmap::MAP_SHARED, :advice => Mmap::MADV_SEQUENTIAL) else fdata=File.open(fname).read end su_put(fdata, destination, File.join('/tmp',File.basename(fname)), options) else logger.error "Unable to read file #{fname}" end end end # Upload +data+ to +temporary_area+ before installing it in # +destination+ using sudo. # # +options+ are as for *put* # def su_put(data, destination, temporary_area='/tmp', options={}) temporary_area = File.join(temporary_area,"#{File.basename(destination)}-$CAPISTRANO:HOST$") put(data, temporary_area, options) send run_method, <<-CMD sh -c "install -m#{sprintf("%3o",options[:mode]||0755)} #{temporary_area} #{destination} && rm -f #{temporary_area}" CMD end # Copies the +file_pattern+, which is assumed to be a tar # file of some description (gzipped or plain), and unpacks it into # +destination+. def unzip(file_pattern, destination, options={}) Dir.glob(file_pattern) do |fname| if File.readable?(fname) then target="/tmp/#{File.basename(fname)}" if MMAP logger.debug "Using Memory Mapped File Upload" fdata=Mmap.new(fname,"r", Mmap::MAP_SHARED, :advice => Mmap::MADV_SEQUENTIAL) else fdata=File.open(fname).read end put(fdata, target, options) send run_method, <<-CMD sh -c "cd #{destination} && zcat -f #{target} | tar xvf - && rm -f #{target}" CMD end end end # Wrap this around your task calls to catch the no servers error and # ignore it # # std.ignore_no_servers_error do # activate_mysql # end # def ignore_no_servers_error (&block) begin yield rescue RuntimeError => failure if failure.message =~ /no servers matched/ logger.debug "Ignoring 'no servers matched' error in task #{current_task.name}" else raise end end end # Wrap this around your task to force a connection as root. # Flushes the session cache before and after the connection. # # std.connect_as_root do # install_sudo # end # def connect_as_root (&block) begin tempuser = user set :user, "root" actor.sessions.delete_if { true } yield tempuser ensure set :user, tempuser if tempuser actor.sessions.delete_if { true } end end #Returns a random string of alphanumeric characters of size +size+ #Useful for passwords, usernames and the like. def random_string(size=10) s = "" size.times { s << (i = rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr } s end # Return a relative path from the destination directory +from_str+ # to the target file/directory +to_str+. Used to create relative # symbolic link paths. def relative_path (from_str, to_str) require 'pathname' Pathname.new(to_str).relative_path_from(Pathname.new(from_str)).to_s end # Run a ruby command file on the servers # def ruby(cmd, options={}, &block) temp_name = random_string + ".rb" begin put(cmd, temp_name, :mode => 0700) send(run_method, "ruby #{temp_name}", options, &block) ensure delete temp_name end end # Run a patchfile on the servers # Ignores reverses and rejects. # def patch(patchfile, level = '0', where = '/') temp_name = random_string begin fput(patchfile, temp_name, :mode => 0600) send(run_method, %{ patch -p#{level} -tNd #{where} -r /dev/null < #{temp_name} || true }) ensure delete temp_name end end # Deletes the given file(s) from all servers targetted by the current # task, but runs the +delete+ command according to the current setting # of :use_sudo. # # If :recursive => true is specified, it may be used to remove # directories. def su_delete(path, options={}) cmd = "rm -%sf #{path}" % (options[:recursive] ? "r" : "") send(run_method, cmd, options) end # Render a template file and upload it to the servers # def put_template(template, destination, options={}) if MMAP logger.debug "Using Memory Mapped File Upload" fdata=Mmap.new(template,"r", Mmap::MAP_SHARED, :advice => Mmap::MADV_SEQUENTIAL) else fdata=File.read(template) end put(render(:template => fdata), destination, options) end end Capistrano.plugin :std, Std # # vim: nowrap sw=2 sts=2 ts=8 ff=unix ft=ruby: