class Rake::RemoteTask
Rake::RemoteTask
is a subclass of Rake::Task that adds remote_actions
that execute in parallel on multiple hosts via ssh.
Constants
- Status
- VERSION
Attributes
Options for execution of this task.
An Array of Actions this host will perform during execution. Use enhance to add new actions to a task.
The directory on the host this task is running in during execution.
The host this task is running on during execution.
Public Class Methods
Returns an Array with every host configured.
# File lib/rake/remote_task.rb, line 250 def self.all_hosts hosts_for(roles.keys) end
Append value
or default_block
to environment variable name
To initialize an empty array, just do append name
If default_block
is defined, the block will be executed the first time the variable is fetched, and the value will be used for every subsequent fetch.
# File lib/rake/remote_task.rb, line 477 def self.append name, value = nil, &default_block raise ArgumentError, "cannot provide both a value and a block" if value and default_block unless value == :per_thread raise ArgumentError, "cannot set reserved name: '#{name}'" if Rake::RemoteTask.reserved_name?(name) unless $TESTING name = name.to_s set(name, []) unless @@is_array[name] Rake::RemoteTask.is_array[name] = true Rake::RemoteTask.per_thread[name] ||= default_block && value == :per_thread v = default_block || value if v then Rake::RemoteTask.default_env[name] << v Rake::RemoteTask.env[name] << v end end
# File lib/rake/remote_task.rb, line 75 def self.current_roles @@current_roles end
The default environment values. Used for resetting (mostly for tests).
# File lib/rake/remote_task.rb, line 258 def self.default_env @@default_env end
The vlad environment.
# File lib/rake/remote_task.rb, line 269 def self.env @@env end
Fetches environment variable name
from the environment using default default
.
# File lib/rake/remote_task.rb, line 281 def self.fetch name, default = nil name = name.to_s if Symbol === name if @@env.has_key? name then protect_env(name) do v = @@env[name] if @@is_array[name] then v = v.map do |item| Proc === item ? item.call : item end unless per_thread[name] then @@env[name] = v @@is_array[name] = false end elsif Proc === v then v = v.call @@env[name] = v unless per_thread[name] end v end elsif default || default == false then @@env[name] = default else raise Rake::FetchError end end
Add host host_name
that belongs to roles
. Extra arguments may be specified for the host as a hash as the last argument.
host is the inversion of role:
host 'db1.example.com', :db, :master_db
Is equivalent to:
role :db, 'db1.example.com' role :master_db, 'db1.example.com'
# File lib/rake/remote_task.rb, line 320 def self.host host_name, *roles opts = Hash === roles.last ? roles.pop : {} roles.each do |role_name| role role_name, host_name, opts.dup end end
Returns an Array of all hosts in roles
.
# File lib/rake/remote_task.rb, line 331 def self.hosts_for *roles roles.flatten.map { |r| self.roles[r].keys }.flatten.uniq.sort end
# File lib/rake/remote_task.rb, line 273 def self.is_array @@is_array end
Create a new task named task_name
attached to Rake::Application app
.
# File lib/rake/remote_task.rb, line 82 def initialize(task_name, app) super @remote_actions = [] @happy = false # used for deprecation warnings on get/put/rsync end
# File lib/rake/remote_task.rb, line 262 def self.per_thread @@per_thread end
Adds a remote task named name
with options options
that will execute block
.
# File lib/rake/remote_task.rb, line 357 def self.remote_task name, *args, &block options = (Hash === args.last) ? args.pop : {} t = Rake::RemoteTask.define_task(name, *args, &block) options[:roles] = Array options[:roles] options[:roles] |= @@current_roles t.options = options t end
Resets vlad, restoring all roles, tasks and environment variables to the defaults.
# File lib/rake/remote_task.rb, line 377 def self.reset @@def_role_hash = {} # official default role value @@env = {} @@is_array = {} @@tasks = {} @@roles = Hash.new { |h,k| h[k] = @@def_role_hash } @@env_locks = Hash.new { |h,k| h[k] = Mutex.new } @@default_env.each do |k,v| @@env[k] = safe_dup(v) end end
Adds role role_name
with host
and args
for that host. TODO: merge: Declare a role and assign a remote host to it. Equivalent to the host
method; provided for capistrano compatibility.
# File lib/rake/remote_task.rb, line 405 def self.role role_name, host = nil, args = {} if block_given? then raise ArgumentError, 'host not allowed with block' unless host.nil? begin current_roles << role_name yield ensure current_roles.delete role_name end else raise ArgumentError, 'host required' if host.nil? [*host].each do |hst| raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty? end @@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name] @@roles[role_name][host] = args end end
The configured roles.
# File lib/rake/remote_task.rb, line 429 def self.roles host domain, :app, :web, :db if @@roles.empty? @@roles end
Set environment variable name
to value
or default_block
.
If default_block
is defined, the block will be executed the first time the variable is fetched, and the value will be used for every subsequent fetch.
# File lib/rake/remote_task.rb, line 442 def self.set name, value = nil, &default_block raise ArgumentError, "cannot provide both a value and a block" if value and default_block unless value == :per_thread raise ArgumentError, "cannot set reserved name: '#{name}'" if Rake::RemoteTask.reserved_name?(name) unless $TESTING name = name.to_s Rake::RemoteTask.per_thread[name] = true if default_block && value == :per_thread Rake::RemoteTask.default_env[name] = default_block || safe_dup(value) Rake::RemoteTask.env[name] = default_block || safe_dup(value) if Object.private_instance_methods.include? name.to_sym then Object.send :alias_method, :"old_#{name}", name end Object.send :define_method, name do Rake::RemoteTask.fetch name end Object.send :private, name end
Sets all the default values. Should only be called once. Use reset if you need to restore values.
# File lib/rake/remote_task.rb, line 501 def self.set_defaults @@default_env ||= {} @@per_thread ||= {} self.reset mandatory :repository, "repository path" mandatory :deploy_to, "deploy path" mandatory :domain, "server domain" simple_set(:deploy_timestamped, true, :deploy_via, :export, :keep_releases, 5, :rake_cmd, "rake", :rsync_cmd, "rsync", :rsync_flags, ['-azP', '--delete'], :ssh_cmd, "ssh", :ssh_flags, [], :sudo_cmd, "sudo", :sudo_flags, ['-p Password:'], :sudo_prompt, /^Password:/, :umask, nil, :mkdirs, [], :shared_paths, {}, :perm_owner, nil, :perm_group, nil) append :command_prefix, [] set(:current_release) { (releases.any?) ? File.join(releases_path, releases[-1]) : nil } set(:latest_release) { deploy_timestamped ? release_path : current_release } set(:previous_release) { File.join(releases_path, releases[-2]) } set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") } set(:release_path) { File.join(releases_path, release_name) } set(:releases) { task.run("ls -1 #{releases_path}").split("\n").select { |l| l =~ /^\d+$/ }.sort } set_path :current_path, "current" set_path :releases_path, "releases" set_path :scm_path, "scm" set_path :shared_path, "shared" set(:sudo_password) do state = `stty -g` raise Rake::Error, "stty(1) not found" unless $?.success? begin system "stty -echo" $stdout.print "sudo password: " $stdout.flush sudo_password = $stdin.gets $stdout.puts ensure system "stty #{state}" end sudo_password end end
The Rake::RemoteTask
executing in this Thread.
# File lib/rake/remote_task.rb, line 575 def self.task Thread.current[:task] end
The configured Rake::RemoteTasks.
# File lib/rake/remote_task.rb, line 582 def self.tasks @@tasks end
Public Instance Methods
Similar to target_hosts
, but returns true if user defined any hosts, even an empty list.
# File lib/rake/remote_task.rb, line 636 def defined_target_hosts? return true if ENV["HOSTS"] roles = Array options[:roles] return true if roles.empty? # borrowed from hosts_for: roles.flatten.each { |r| return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r] } return false end
Add remote action block
to this task with dependencies deps
. See Rake::Task#enhance.
# File lib/rake/remote_task.rb, line 98 def enhance(deps=nil, &block) original_enhance(deps) # can't use super because block passed regardless. @remote_actions << Action.new(self, block) if block_given? self end
Execute this action. Local actions will be performed first, then remote actions will be performed in parallel on each host configured for this RemoteTask
.
# File lib/rake/remote_task.rb, line 109 def execute(args = nil) raise(Rake::ConfigurationError, "No target hosts specified on task #{self.name} for roles #{options[:roles].inspect}") unless defined_target_hosts? super args @remote_actions.each { |act| act.execute(target_hosts, self, args) } end
Pull files
from the remote host
using rsync to local_dir
. TODO: what if role has multiple hosts & the files overlap? subdirs?
# File lib/rake/remote_task.rb, line 123 def get local_dir, *files @happy = true host = target_host rsync files.map { |f| "#{host}:#{f}" }, local_dir @happy = false end
Add a local action to this task. This calls Rake::Task#enhance.
# File lib/rake/test_case.rb, line 39 def popen4 *command @commands << command @input = StringIO.new out = StringIO.new @output.shift.to_s err = StringIO.new @error.shift.to_s raise if block_given? status = self.action ? self.action[command.join(' ')] : 0 Process.expected Status.new(status) return 42, @input, out, err end
Copy a (usually generated) file to remote_path
. Contents of block are copied to remote_path
and you may specify an optional base_name for the tempfile (aids in debugging).
# File lib/rake/remote_task.rb, line 135 def put remote_path, base_name = File.basename(remote_path) require 'tempfile' Tempfile.open base_name do |fp| fp.puts yield fp.flush @happy = true rsync fp.path, "#{target_host}:#{remote_path}" @happy = false end end
Execute rsync with args
. Tacks on pre-specified rsync_cmd
and rsync_flags
.
Favor get
and put
for most tasks. Old-style direct use where the target_host
was implicit is now deprecated.
# File lib/rake/remote_task.rb, line 153 def rsync *args unless @happy || args[-1] =~ /:/ then warn "rsync deprecation: pass target_host:remote_path explicitly" args[-1] = "#{target_host}:#{args[-1]}" end cmd = [rsync_cmd, rsync_flags, args].flatten.compact cmdstr = cmd.join ' ' warn cmdstr if $TRACE success = system(*cmd) raise Rake::CommandFailedError.new($?), "execution failed: #{cmdstr}" unless success end
Use ssh to execute command
on target_host. If command
uses sudo, the sudo password will be prompted for then saved for subsequent sudo commands.
If command_prefix
has been filled up with one or several commands, they will be run on target_host
before command
.
Yields input channel, :out or :err, and data.
# File lib/rake/remote_task.rb, line 178 def run command commands = [] commands << "cd #{target_dir}" if target_dir commands += command_prefix.flatten if command_prefix commands << "#{command}" command = commands.join(" && ") cmd = [ssh_cmd, ssh_flags, target_host, command].flatten result = [] trace = [ssh_cmd, ssh_flags, target_host, "'#{command}'"].flatten.join(' ') warn trace if $TRACE pid, inn, out, err = popen4(*cmd) inn.sync = true streams = [out, err] out_stream = { out => $stdout, err => $stderr, } # Handle process termination ourselves status = nil Thread.start do status = Process.waitpid2(pid).last end until streams.empty? do # don't busy loop selected, = select streams, nil, nil, 0.1 next if selected.nil? or selected.empty? selected.each do |stream| if stream.eof? then streams.delete stream if status # we've quit, so no more writing next end data = stream.readpartial(1024) out_stream[stream].write data if stream == err and data =~ sudo_prompt then inn.puts sudo_password data << "\n" $stderr.write "\n" end yield inn, stream == out ? :out : :err, data if block_given? result << data end end unless status.success? then raise(Rake::CommandFailedError.new(status), "execution failed with status #{status.exitstatus}: #{cmd.join ' '}") end result.join ensure inn.close rescue nil out.close rescue nil err.close rescue nil end
# File lib/rake/test_case.rb, line 54 def select reads, writes, errs, timeout [reads, writes, errs] end
Execute command
under sudo using run.
# File lib/rake/remote_task.rb, line 589 def sudo command run [sudo_cmd, sudo_flags, command].flatten.compact.join(" ") end
# File lib/rake/test_case.rb, line 34 def system *command @commands << command self.action ? self.action[command.join(' ')] : true end
Sets the target host. Allows you to set an optional directory using the format:
host.domain:/dir
# File lib/rake/remote_task.rb, line 599 def target_host= host if host =~ /^(.+):(.+?)$/ @target_host = $1 @target_dir = $2 else @target_host = host @target_dir = nil end end
The hosts this task will execute on. The hosts are determined from the role this task belongs to.
The target hosts may be overridden by providing a comma-separated list of commands to the HOSTS environment variable:
rake my_task HOSTS=app1.example.com,app2.example.com
# File lib/rake/remote_task.rb, line 618 def target_hosts if hosts = ENV["HOSTS"] then hosts.strip.gsub(/\s+/, '').split(",") else roles = Array options[:roles] if roles.empty? then Rake::RemoteTask.all_hosts else Rake::RemoteTask.hosts_for roles end end end