rake-remote_task 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +0 -0
- data/.autotest +24 -0
- data/History.txt +7 -0
- data/Manifest.txt +8 -0
- data/README.txt +56 -0
- data/Rakefile +25 -0
- data/lib/rake/remote_task.rb +630 -0
- data/lib/rake/test_case.rb +72 -0
- data/test/test_rake_remote_task.rb +272 -0
- metadata +200 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
Binary file
|
data/.autotest
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
Autotest.add_hook :initialize do |at|
|
6
|
+
at.testlib = "minitest/autorun"
|
7
|
+
# at.extra_files << "../some/external/dependency.rb"
|
8
|
+
#
|
9
|
+
# at.libs << ":../some/external"
|
10
|
+
#
|
11
|
+
# at.add_exception 'vendor'
|
12
|
+
#
|
13
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
14
|
+
# at.files_matching(/test_.*rb$/)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# %w(TestA TestB).each do |klass|
|
18
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Autotest.add_hook :run_command do |at|
|
23
|
+
# system "rake build"
|
24
|
+
# end
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= rake-remote_task
|
2
|
+
|
3
|
+
* http://rubyhitsquad.com/
|
4
|
+
* http://rubyforge.org/projects/hitsquad/
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
Vlad the Deployer's sexy brainchild is rake-remote_task, extending
|
9
|
+
Rake with remote task goodness.
|
10
|
+
|
11
|
+
== FEATURES/PROBLEMS:
|
12
|
+
|
13
|
+
* Run remote commands on one or more servers.
|
14
|
+
* Mix and match local and remote tasks.
|
15
|
+
* Uses ssh with your ssh settings already in place.
|
16
|
+
* Uses rsync for efficient transfers.
|
17
|
+
|
18
|
+
== SYNOPSIS:
|
19
|
+
|
20
|
+
remote_task :setup_app, :roles => :app do
|
21
|
+
# ...
|
22
|
+
run "umask #{umask} && mkdir -p #{dirs.join(' ')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
== REQUIREMENTS:
|
26
|
+
|
27
|
+
* rake, >= 0.8
|
28
|
+
|
29
|
+
== INSTALL:
|
30
|
+
|
31
|
+
* sudo gem install rake-remote_task
|
32
|
+
|
33
|
+
== LICENSE:
|
34
|
+
|
35
|
+
(The MIT License)
|
36
|
+
|
37
|
+
Copyright (c) Ryan Davis, RubyHitSquad
|
38
|
+
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
40
|
+
a copy of this software and associated documentation files (the
|
41
|
+
'Software'), to deal in the Software without restriction, including
|
42
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
43
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
44
|
+
permit persons to whom the Software is furnished to do so, subject to
|
45
|
+
the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be
|
48
|
+
included in all copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
51
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
52
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
53
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
54
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
55
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
56
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
# HACK Hoe.plugin :isolate # fudging releases - work it out w/ release script
|
7
|
+
Hoe.plugin :seattlerb
|
8
|
+
|
9
|
+
Hoe.spec 'rake-remote_task' do
|
10
|
+
developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
|
11
|
+
developer 'Eric Hodel', 'drbrain@segment7.net'
|
12
|
+
developer 'Wilson Bilkovich', 'wilson@supremetyrant.com'
|
13
|
+
|
14
|
+
self.rubyforge_name = 'hitsquad'
|
15
|
+
|
16
|
+
extra_deps << ['rake', '~> 0.8.0']
|
17
|
+
extra_deps << ['open4', '~> 0.9.0']
|
18
|
+
|
19
|
+
extra_dev_deps << ['minitest', '~> 1.7.0']
|
20
|
+
|
21
|
+
# TODO: remove 1.9
|
22
|
+
multiruby_skip << "1.9" << "rubinius"
|
23
|
+
end
|
24
|
+
|
25
|
+
# vim: syntax=ruby
|
@@ -0,0 +1,630 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'open4'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
$TESTING ||= false
|
6
|
+
$TRACE = Rake.application.options.trace
|
7
|
+
$-w = true if $TRACE # asshat, don't mess with my warn.
|
8
|
+
|
9
|
+
[
|
10
|
+
["Thread.current[:task]", :get, :put, :rsync, :run, :sudo, :target_host],
|
11
|
+
["Rake::RemoteTask", :host, :remote_task, :role, :set]
|
12
|
+
].each do |methods|
|
13
|
+
receiver = methods.shift
|
14
|
+
methods.each do |method|
|
15
|
+
eval "def #{method} *args, █ #{receiver}.#{method}(*args, &block);end"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Rake
|
20
|
+
##
|
21
|
+
# Base error class for all Vlad errors.
|
22
|
+
class Error < RuntimeError; end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Raised when you have incorrectly configured Vlad.
|
26
|
+
class ConfigurationError < Error; end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Raised when a remote command fails.
|
30
|
+
class CommandFailedError < Error; end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Raised when an environment variable hasn't been set.
|
34
|
+
class FetchError < Error; end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Rake::RemoteTask is a subclass of Rake::Task that adds
|
39
|
+
# remote_actions that execute in parallel on multiple hosts via ssh.
|
40
|
+
|
41
|
+
class Rake::RemoteTask < Rake::Task
|
42
|
+
|
43
|
+
VERSION = "2.0.0"
|
44
|
+
|
45
|
+
@@current_roles = []
|
46
|
+
|
47
|
+
include Open4
|
48
|
+
|
49
|
+
##
|
50
|
+
# Options for execution of this task.
|
51
|
+
|
52
|
+
attr_accessor :options
|
53
|
+
|
54
|
+
##
|
55
|
+
# The host this task is running on during execution.
|
56
|
+
|
57
|
+
attr_reader :target_host
|
58
|
+
|
59
|
+
##
|
60
|
+
# The directory on the host this task is running in during execution.
|
61
|
+
|
62
|
+
attr_reader :target_dir
|
63
|
+
|
64
|
+
##
|
65
|
+
# An Array of Actions this host will perform during execution. Use
|
66
|
+
# enhance to add new actions to a task.
|
67
|
+
|
68
|
+
attr_reader :remote_actions
|
69
|
+
|
70
|
+
def self.current_roles
|
71
|
+
@@current_roles
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Create a new task named +task_name+ attached to Rake::Application +app+.
|
76
|
+
|
77
|
+
def initialize(task_name, app)
|
78
|
+
super
|
79
|
+
|
80
|
+
@remote_actions = []
|
81
|
+
@happy = false # used for deprecation warnings on get/put/rsync
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Add a local action to this task. This calls Rake::Task#enhance.
|
86
|
+
|
87
|
+
alias_method :original_enhance, :enhance
|
88
|
+
|
89
|
+
##
|
90
|
+
# Add remote action +block+ to this task with dependencies +deps+. See
|
91
|
+
# Rake::Task#enhance.
|
92
|
+
|
93
|
+
def enhance(deps=nil, &block)
|
94
|
+
original_enhance(deps) # can't use super because block passed regardless.
|
95
|
+
@remote_actions << Action.new(self, block) if block_given?
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Execute this action. Local actions will be performed first, then remote
|
101
|
+
# actions will be performed in parallel on each host configured for this
|
102
|
+
# RemoteTask.
|
103
|
+
|
104
|
+
def execute(args = nil)
|
105
|
+
raise(Rake::ConfigurationError,
|
106
|
+
"No target hosts specified on task #{self.name} for roles #{options[:roles].inspect}") unless
|
107
|
+
defined_target_hosts?
|
108
|
+
|
109
|
+
super args
|
110
|
+
|
111
|
+
@remote_actions.each { |act| act.execute(target_hosts, self, args) }
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Pull +files+ from the remote +host+ using rsync to +local_dir+.
|
116
|
+
# TODO: what if role has multiple hosts & the files overlap? subdirs?
|
117
|
+
|
118
|
+
def get local_dir, *files
|
119
|
+
@happy = true
|
120
|
+
host = target_host
|
121
|
+
rsync files.map { |f| "#{host}:#{f}" }, local_dir
|
122
|
+
@happy = false
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Copy a (usually generated) file to +remote_path+. Contents of block
|
127
|
+
# are copied to +remote_path+ and you may specify an optional
|
128
|
+
# base_name for the tempfile (aids in debugging).
|
129
|
+
|
130
|
+
def put remote_path, base_name = File.basename(remote_path)
|
131
|
+
require 'tempfile'
|
132
|
+
Tempfile.open base_name do |fp|
|
133
|
+
fp.puts yield
|
134
|
+
fp.flush
|
135
|
+
@happy = true
|
136
|
+
rsync fp.path, "#{target_host}:#{remote_path}"
|
137
|
+
@happy = false
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Execute rsync with +args+. Tacks on pre-specified +rsync_cmd+ and
|
143
|
+
# +rsync_flags+.
|
144
|
+
#
|
145
|
+
# Favor #get and #put for most tasks. Old-style direct use where the
|
146
|
+
# target_host was implicit is now deprecated.
|
147
|
+
|
148
|
+
def rsync *args
|
149
|
+
unless @happy || args[-1] =~ /:/ then
|
150
|
+
warn "rsync deprecation: pass target_host:remote_path explicitly"
|
151
|
+
args[-1] = "#{target_host}:#{args[-1]}"
|
152
|
+
end
|
153
|
+
|
154
|
+
cmd = [rsync_cmd, rsync_flags, args].flatten.compact
|
155
|
+
cmdstr = cmd.join ' '
|
156
|
+
|
157
|
+
warn cmdstr if $TRACE
|
158
|
+
|
159
|
+
success = system(*cmd)
|
160
|
+
|
161
|
+
raise Rake::CommandFailedError, "execution failed: #{cmdstr}" unless success
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
|
166
|
+
# sudo password will be prompted for then saved for subsequent sudo commands.
|
167
|
+
|
168
|
+
def run command
|
169
|
+
command = "cd #{target_dir} && #{command}" if target_dir
|
170
|
+
cmd = [ssh_cmd, ssh_flags, target_host, command].flatten
|
171
|
+
result = []
|
172
|
+
|
173
|
+
trace = [ssh_cmd, ssh_flags, target_host, "'#{command}'"].flatten.join(' ')
|
174
|
+
warn trace if $TRACE
|
175
|
+
|
176
|
+
pid, inn, out, err = popen4(*cmd)
|
177
|
+
|
178
|
+
inn.sync = true
|
179
|
+
streams = [out, err]
|
180
|
+
out_stream = {
|
181
|
+
out => $stdout,
|
182
|
+
err => $stderr,
|
183
|
+
}
|
184
|
+
|
185
|
+
# Handle process termination ourselves
|
186
|
+
status = nil
|
187
|
+
Thread.start do
|
188
|
+
status = Process.waitpid2(pid).last
|
189
|
+
end
|
190
|
+
|
191
|
+
until streams.empty? do
|
192
|
+
# don't busy loop
|
193
|
+
selected, = select streams, nil, nil, 0.1
|
194
|
+
|
195
|
+
next if selected.nil? or selected.empty?
|
196
|
+
|
197
|
+
selected.each do |stream|
|
198
|
+
if stream.eof? then
|
199
|
+
streams.delete stream if status # we've quit, so no more writing
|
200
|
+
next
|
201
|
+
end
|
202
|
+
|
203
|
+
data = stream.readpartial(1024)
|
204
|
+
out_stream[stream].write data
|
205
|
+
|
206
|
+
if stream == err and data =~ sudo_prompt then
|
207
|
+
inn.puts sudo_password
|
208
|
+
data << "\n"
|
209
|
+
$stderr.write "\n"
|
210
|
+
end
|
211
|
+
|
212
|
+
result << data
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
unless status.success? then
|
217
|
+
raise(Rake::CommandFailedError,
|
218
|
+
"execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
|
219
|
+
end
|
220
|
+
|
221
|
+
result.join
|
222
|
+
ensure
|
223
|
+
inn.close rescue nil
|
224
|
+
out.close rescue nil
|
225
|
+
err.close rescue nil
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Returns an Array with every host configured.
|
230
|
+
|
231
|
+
def self.all_hosts
|
232
|
+
hosts_for(roles.keys)
|
233
|
+
end
|
234
|
+
|
235
|
+
##
|
236
|
+
# The default environment values. Used for resetting (mostly for
|
237
|
+
# tests).
|
238
|
+
|
239
|
+
def self.default_env
|
240
|
+
@@default_env
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.per_thread
|
244
|
+
@@per_thread
|
245
|
+
end
|
246
|
+
|
247
|
+
##
|
248
|
+
# The vlad environment.
|
249
|
+
|
250
|
+
def self.env
|
251
|
+
@@env
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# Fetches environment variable +name+ from the environment using
|
256
|
+
# default +default+.
|
257
|
+
|
258
|
+
def self.fetch name, default = nil
|
259
|
+
name = name.to_s if Symbol === name
|
260
|
+
if @@env.has_key? name then
|
261
|
+
protect_env(name) do
|
262
|
+
v = @@env[name]
|
263
|
+
v = @@env[name] = v.call if Proc === v unless per_thread[name]
|
264
|
+
v = v.call if Proc === v
|
265
|
+
v
|
266
|
+
end
|
267
|
+
elsif default || default == false
|
268
|
+
v = @@env[name] = default
|
269
|
+
else
|
270
|
+
raise Rake::FetchError
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
##
|
275
|
+
# Add host +host_name+ that belongs to +roles+. Extra arguments may
|
276
|
+
# be specified for the host as a hash as the last argument.
|
277
|
+
#
|
278
|
+
# host is the inversion of role:
|
279
|
+
#
|
280
|
+
# host 'db1.example.com', :db, :master_db
|
281
|
+
#
|
282
|
+
# Is equivalent to:
|
283
|
+
#
|
284
|
+
# role :db, 'db1.example.com'
|
285
|
+
# role :master_db, 'db1.example.com'
|
286
|
+
|
287
|
+
def self.host host_name, *roles
|
288
|
+
opts = Hash === roles.last ? roles.pop : {}
|
289
|
+
|
290
|
+
roles.each do |role_name|
|
291
|
+
role role_name, host_name, opts.dup
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Returns an Array of all hosts in +roles+.
|
297
|
+
|
298
|
+
def self.hosts_for *roles
|
299
|
+
roles.flatten.map { |r|
|
300
|
+
self.roles[r].keys
|
301
|
+
}.flatten.uniq.sort
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.mandatory name, desc # :nodoc:
|
305
|
+
self.set(name) do
|
306
|
+
raise(Rake::ConfigurationError,
|
307
|
+
"Please specify the #{desc} via the #{name.inspect} variable")
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
##
|
312
|
+
# Ensures exclusive access to +name+.
|
313
|
+
|
314
|
+
def self.protect_env name # :nodoc:
|
315
|
+
@@env_locks[name].synchronize do
|
316
|
+
yield
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Adds a remote task named +name+ with options +options+ that will
|
322
|
+
# execute +block+.
|
323
|
+
|
324
|
+
def self.remote_task name, *args, &block
|
325
|
+
options = (Hash === args.last) ? args.pop : {}
|
326
|
+
t = Rake::RemoteTask.define_task(name, *args, &block)
|
327
|
+
options[:roles] = Array options[:roles]
|
328
|
+
options[:roles] |= @@current_roles
|
329
|
+
t.options = options
|
330
|
+
t
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
# Ensures +name+ does not conflict with an existing method.
|
335
|
+
|
336
|
+
def self.reserved_name? name # :nodoc:
|
337
|
+
!@@env.has_key?(name.to_s) && self.respond_to?(name)
|
338
|
+
end
|
339
|
+
|
340
|
+
##
|
341
|
+
# Resets vlad, restoring all roles, tasks and environment variables
|
342
|
+
# to the defaults.
|
343
|
+
|
344
|
+
def self.reset
|
345
|
+
@@def_role_hash = {} # official default role value
|
346
|
+
@@env = {}
|
347
|
+
@@tasks = {}
|
348
|
+
@@roles = Hash.new { |h,k| h[k] = @@def_role_hash }
|
349
|
+
@@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
|
350
|
+
|
351
|
+
@@default_env.each do |k,v|
|
352
|
+
case v
|
353
|
+
when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
|
354
|
+
@@env[k] = v
|
355
|
+
else
|
356
|
+
@@env[k] = v.dup
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
##
|
362
|
+
# Adds role +role_name+ with +host+ and +args+ for that host.
|
363
|
+
# TODO: merge:
|
364
|
+
# Declare a role and assign a remote host to it. Equivalent to the
|
365
|
+
# <tt>host</tt> method; provided for capistrano compatibility.
|
366
|
+
|
367
|
+
def self.role role_name, host = nil, args = {}
|
368
|
+
if block_given? then
|
369
|
+
raise ArgumentError, 'host not allowed with block' unless host.nil?
|
370
|
+
|
371
|
+
begin
|
372
|
+
current_roles << role_name
|
373
|
+
yield
|
374
|
+
ensure
|
375
|
+
current_roles.delete role_name
|
376
|
+
end
|
377
|
+
else
|
378
|
+
raise ArgumentError, 'host required' if host.nil?
|
379
|
+
|
380
|
+
[*host].each do |hst|
|
381
|
+
raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty?
|
382
|
+
end
|
383
|
+
@@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name]
|
384
|
+
@@roles[role_name][host] = args
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# The configured roles.
|
390
|
+
|
391
|
+
def self.roles
|
392
|
+
host domain, :app, :web, :db if @@roles.empty?
|
393
|
+
|
394
|
+
@@roles
|
395
|
+
end
|
396
|
+
|
397
|
+
##
|
398
|
+
# Set environment variable +name+ to +value+ or +default_block+.
|
399
|
+
#
|
400
|
+
# If +default_block+ is defined, the block will be executed the
|
401
|
+
# first time the variable is fetched, and the value will be used for
|
402
|
+
# every subsequent fetch.
|
403
|
+
|
404
|
+
def self.set name, value = nil, &default_block
|
405
|
+
raise ArgumentError, "cannot provide both a value and a block" if
|
406
|
+
value and default_block unless
|
407
|
+
value == :per_thread
|
408
|
+
raise ArgumentError, "cannot set reserved name: '#{name}'" if
|
409
|
+
Rake::RemoteTask.reserved_name?(name) unless $TESTING
|
410
|
+
|
411
|
+
name = name.to_s
|
412
|
+
|
413
|
+
Rake::RemoteTask.per_thread[name] = true if
|
414
|
+
default_block && value == :per_thread
|
415
|
+
|
416
|
+
Rake::RemoteTask.default_env[name] = Rake::RemoteTask.env[name] =
|
417
|
+
default_block || value
|
418
|
+
|
419
|
+
Object.send :define_method, name do
|
420
|
+
Rake::RemoteTask.fetch name
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
##
|
425
|
+
# Sets all the default values. Should only be called once. Use reset
|
426
|
+
# if you need to restore values.
|
427
|
+
|
428
|
+
def self.set_defaults
|
429
|
+
@@default_env ||= {}
|
430
|
+
@@per_thread ||= {}
|
431
|
+
self.reset
|
432
|
+
|
433
|
+
mandatory :repository, "repository path"
|
434
|
+
mandatory :deploy_to, "deploy path"
|
435
|
+
mandatory :domain, "server domain"
|
436
|
+
|
437
|
+
simple_set(:deploy_timestamped, true,
|
438
|
+
:deploy_via, :export,
|
439
|
+
:keep_releases, 5,
|
440
|
+
:rake_cmd, "rake",
|
441
|
+
:revision, "head",
|
442
|
+
:rsync_cmd, "rsync",
|
443
|
+
:rsync_flags, ['-azP', '--delete'],
|
444
|
+
:ssh_cmd, "ssh",
|
445
|
+
:ssh_flags, [],
|
446
|
+
:sudo_cmd, "sudo",
|
447
|
+
:sudo_flags, ['-p Password:'],
|
448
|
+
:sudo_prompt, /^Password:/,
|
449
|
+
:umask, '02',
|
450
|
+
:mkdirs, [],
|
451
|
+
:shared_paths, {})
|
452
|
+
|
453
|
+
set(:current_release) { File.join(releases_path, releases[-1]) }
|
454
|
+
set(:latest_release) { deploy_timestamped?release_path:current_release}
|
455
|
+
set(:previous_release) { File.join(releases_path, releases[-2]) }
|
456
|
+
set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
|
457
|
+
set(:release_path) { File.join(releases_path, release_name) }
|
458
|
+
set(:releases) { task.run("ls -x #{releases_path}").split.sort }
|
459
|
+
|
460
|
+
set_path :current_path, "current"
|
461
|
+
set_path :releases_path, "releases"
|
462
|
+
set_path :scm_path, "scm"
|
463
|
+
set_path :shared_path, "shared"
|
464
|
+
|
465
|
+
set(:sudo_password) do
|
466
|
+
state = `stty -g`
|
467
|
+
|
468
|
+
raise Rake::Error, "stty(1) not found" unless $?.success?
|
469
|
+
|
470
|
+
begin
|
471
|
+
system "stty -echo"
|
472
|
+
$stdout.print "sudo password: "
|
473
|
+
$stdout.flush
|
474
|
+
sudo_password = $stdin.gets
|
475
|
+
$stdout.puts
|
476
|
+
ensure
|
477
|
+
system "stty #{state}"
|
478
|
+
end
|
479
|
+
sudo_password
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def self.set_path(name, subdir) # :nodoc:
|
484
|
+
set(name) { File.join(deploy_to, subdir) }
|
485
|
+
end
|
486
|
+
|
487
|
+
def self.simple_set(*args) # :nodoc:
|
488
|
+
args = Hash[*args]
|
489
|
+
args.each do |k, v|
|
490
|
+
set k, v
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
##
|
495
|
+
# The Rake::RemoteTask executing in this Thread.
|
496
|
+
|
497
|
+
def self.task
|
498
|
+
Thread.current[:task]
|
499
|
+
end
|
500
|
+
|
501
|
+
##
|
502
|
+
# The configured Rake::RemoteTasks.
|
503
|
+
|
504
|
+
def self.tasks
|
505
|
+
@@tasks
|
506
|
+
end
|
507
|
+
|
508
|
+
##
|
509
|
+
# Execute +command+ under sudo using run.
|
510
|
+
|
511
|
+
def sudo command
|
512
|
+
run [sudo_cmd, sudo_flags, command].flatten.compact.join(" ")
|
513
|
+
end
|
514
|
+
|
515
|
+
##
|
516
|
+
# Sets the target host. Allows you to set an optional directory
|
517
|
+
# using the format:
|
518
|
+
#
|
519
|
+
# host.domain:/dir
|
520
|
+
|
521
|
+
def target_host= host
|
522
|
+
if host =~ /^(.+):(.+?)$/
|
523
|
+
@target_host = $1
|
524
|
+
@target_dir = $2
|
525
|
+
else
|
526
|
+
@target_host = host
|
527
|
+
@target_dir = nil
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
##
|
532
|
+
# The hosts this task will execute on. The hosts are determined from
|
533
|
+
# the role this task belongs to.
|
534
|
+
#
|
535
|
+
# The target hosts may be overridden by providing a comma-separated
|
536
|
+
# list of commands to the HOSTS environment variable:
|
537
|
+
#
|
538
|
+
# rake my_task HOSTS=app1.example.com,app2.example.com
|
539
|
+
|
540
|
+
def target_hosts
|
541
|
+
if hosts = ENV["HOSTS"] then
|
542
|
+
hosts.strip.gsub(/\s+/, '').split(",")
|
543
|
+
else
|
544
|
+
roles = Array options[:roles]
|
545
|
+
|
546
|
+
if roles.empty? then
|
547
|
+
Rake::RemoteTask.all_hosts
|
548
|
+
else
|
549
|
+
Rake::RemoteTask.hosts_for roles
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
##
|
555
|
+
# Similar to target_hosts, but returns true if user defined any hosts, even
|
556
|
+
# an empty list.
|
557
|
+
|
558
|
+
def defined_target_hosts?
|
559
|
+
return true if ENV["HOSTS"]
|
560
|
+
roles = Array options[:roles]
|
561
|
+
return true if roles.empty?
|
562
|
+
# borrowed from hosts_for:
|
563
|
+
roles.flatten.each { |r|
|
564
|
+
return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r]
|
565
|
+
}
|
566
|
+
return false
|
567
|
+
end
|
568
|
+
|
569
|
+
##
|
570
|
+
# Action is used to run a task's remote_actions in parallel on each
|
571
|
+
# of its hosts. Actions are created automatically in
|
572
|
+
# Rake::RemoteTask#enhance.
|
573
|
+
|
574
|
+
class Action
|
575
|
+
|
576
|
+
##
|
577
|
+
# The task this action is attached to.
|
578
|
+
|
579
|
+
attr_reader :task
|
580
|
+
|
581
|
+
##
|
582
|
+
# The block this action will execute.
|
583
|
+
|
584
|
+
attr_reader :block
|
585
|
+
|
586
|
+
##
|
587
|
+
# An Array of threads, one for each host this action executes on.
|
588
|
+
|
589
|
+
attr_reader :workers
|
590
|
+
|
591
|
+
##
|
592
|
+
# Creates a new Action that will run +block+ for +task+.
|
593
|
+
|
594
|
+
def initialize task, block
|
595
|
+
@task = task
|
596
|
+
@block = block
|
597
|
+
@workers = ThreadGroup.new
|
598
|
+
end
|
599
|
+
|
600
|
+
def == other # :nodoc:
|
601
|
+
return false unless Action === other
|
602
|
+
block == other.block && task == other.task
|
603
|
+
end
|
604
|
+
|
605
|
+
##
|
606
|
+
# Execute this action on +hosts+ in parallel. Returns when block
|
607
|
+
# has completed for each host.
|
608
|
+
|
609
|
+
def execute hosts, task, args
|
610
|
+
hosts.each do |host|
|
611
|
+
t = task.clone
|
612
|
+
t.target_host = host
|
613
|
+
thread = Thread.new(t) do |task|
|
614
|
+
Thread.current[:task] = task
|
615
|
+
case block.arity
|
616
|
+
when 1
|
617
|
+
block.call task
|
618
|
+
else
|
619
|
+
block.call task, args
|
620
|
+
end
|
621
|
+
Thread.current[:task] = nil
|
622
|
+
end
|
623
|
+
@workers.add thread
|
624
|
+
end
|
625
|
+
@workers.list.each { |thr| thr.join }
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
Rake::RemoteTask.set_defaults
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/remote_task'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
class StringIO
|
7
|
+
def readpartial(size) read end # suck!
|
8
|
+
end
|
9
|
+
|
10
|
+
module Process
|
11
|
+
def self.expected status
|
12
|
+
@@expected ||= []
|
13
|
+
@@expected << status
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
alias :waitpid2_old :waitpid2
|
18
|
+
|
19
|
+
def waitpid2(pid)
|
20
|
+
[ @@expected.shift ]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Rake::RemoteTask
|
26
|
+
attr_accessor :commands, :action, :input, :output, :error
|
27
|
+
|
28
|
+
Status = Struct.new :exitstatus
|
29
|
+
|
30
|
+
class Status
|
31
|
+
def success?() exitstatus == 0 end
|
32
|
+
end
|
33
|
+
|
34
|
+
def system *command
|
35
|
+
@commands << command
|
36
|
+
self.action ? self.action[command.join(' ')] : true
|
37
|
+
end
|
38
|
+
|
39
|
+
def popen4 *command
|
40
|
+
@commands << command
|
41
|
+
|
42
|
+
@input = StringIO.new
|
43
|
+
out = StringIO.new @output.shift.to_s
|
44
|
+
err = StringIO.new @error.shift.to_s
|
45
|
+
|
46
|
+
raise if block_given?
|
47
|
+
|
48
|
+
status = self.action ? self.action[command.join(' ')] : 0
|
49
|
+
Process.expected Status.new(status)
|
50
|
+
|
51
|
+
return 42, @input, out, err
|
52
|
+
end
|
53
|
+
|
54
|
+
def select reads, writes, errs, timeout
|
55
|
+
[reads, writes, errs]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Rake::TestCase < MiniTest::Unit::TestCase
|
60
|
+
def setup
|
61
|
+
@rake = Rake::RemoteTask
|
62
|
+
@rake.reset
|
63
|
+
Rake.application.clear
|
64
|
+
@task_count = Rake.application.tasks.size
|
65
|
+
@rake.set :domain, "example.com"
|
66
|
+
end
|
67
|
+
|
68
|
+
def util_set_hosts
|
69
|
+
@rake.host "app.example.com", :app
|
70
|
+
@rake.host "db.example.com", :db
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'rake/test_case'
|
2
|
+
|
3
|
+
class TestRakeRemoteTask < Rake::TestCase
|
4
|
+
# TODO: move to minitest
|
5
|
+
def assert_silent
|
6
|
+
out, err = capture_io do
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
|
10
|
+
assert_empty err
|
11
|
+
assert_empty out
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_enhance
|
15
|
+
util_set_hosts
|
16
|
+
body = Proc.new { 5 }
|
17
|
+
task = @rake.remote_task(:some_task => :foo, &body)
|
18
|
+
action = Rake::RemoteTask::Action.new(task, body)
|
19
|
+
assert_equal [action], task.remote_actions
|
20
|
+
assert_equal task, action.task
|
21
|
+
assert_equal ["foo"], task.prerequisites
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_enhance_with_no_task_body
|
25
|
+
util_set_hosts
|
26
|
+
util_setup_task
|
27
|
+
assert_equal [], @task.remote_actions
|
28
|
+
assert_equal [], @task.prerequisites
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_execute
|
32
|
+
util_set_hosts
|
33
|
+
set :some_variable, 1
|
34
|
+
set :can_set_nil, nil
|
35
|
+
set :lies_are, false
|
36
|
+
x = 5
|
37
|
+
task = @rake.remote_task(:some_task) { x += some_variable }
|
38
|
+
task.execute nil
|
39
|
+
assert_equal 1, task.some_variable
|
40
|
+
assert_equal 7, x
|
41
|
+
assert task.can_set_nil.nil?
|
42
|
+
assert_equal false, task.lies_are
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_set_false
|
46
|
+
set :can_set_nil, nil
|
47
|
+
set :lies_are, false
|
48
|
+
|
49
|
+
assert_equal nil, task.can_set_nil
|
50
|
+
|
51
|
+
assert_equal false, task.lies_are
|
52
|
+
assert_equal false, Rake::RemoteTask.fetch(:lies_are)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def test_fetch_false
|
57
|
+
assert_equal false, Rake::RemoteTask.fetch(:unknown, false)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_execute_exposes_target_host
|
61
|
+
host "app.example.com", :app
|
62
|
+
task = remote_task(:target_task) { set(:test_target_host, target_host) }
|
63
|
+
task.execute nil
|
64
|
+
assert_equal "app.example.com", Rake::RemoteTask.fetch(:test_target_host)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_execute_with_no_hosts
|
68
|
+
@rake.host "app.example.com", :app
|
69
|
+
t = @rake.remote_task(:flunk, :roles => :db) { flunk "should not have run" }
|
70
|
+
e = assert_raises(Rake::ConfigurationError) { t.execute nil }
|
71
|
+
assert_equal "No target hosts specified on task flunk for roles [:db]",
|
72
|
+
e.message
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_execute_with_no_roles
|
76
|
+
t = @rake.remote_task(:flunk, :roles => :junk) { flunk "should not have run" }
|
77
|
+
e = assert_raises(Rake::ConfigurationError) { t.execute nil }
|
78
|
+
assert_equal "No target hosts specified on task flunk for roles [:junk]",
|
79
|
+
e.message
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_execute_with_roles
|
83
|
+
util_set_hosts
|
84
|
+
set :some_variable, 1
|
85
|
+
x = 5
|
86
|
+
task = @rake.remote_task(:some_task, :roles => :db) { x += some_variable }
|
87
|
+
task.execute nil
|
88
|
+
assert_equal 1, task.some_variable
|
89
|
+
assert_equal 6, x
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_rsync
|
93
|
+
util_setup_task
|
94
|
+
@task.target_host = "app.example.com"
|
95
|
+
|
96
|
+
assert_silent do
|
97
|
+
@task.rsync 'localfile', 'host:remotefile'
|
98
|
+
end
|
99
|
+
|
100
|
+
commands = @task.commands
|
101
|
+
|
102
|
+
assert_equal 1, commands.size, 'not enough commands'
|
103
|
+
assert_equal(%w[rsync -azP --delete localfile host:remotefile],
|
104
|
+
commands.first)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_rsync_fail
|
108
|
+
util_setup_task
|
109
|
+
@task.target_host = "app.example.com"
|
110
|
+
@task.action = lambda { false }
|
111
|
+
|
112
|
+
e = assert_raises Rake::CommandFailedError do
|
113
|
+
assert_silent do
|
114
|
+
@task.rsync 'local', 'host:remote'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
exp = "execution failed: rsync -azP --delete local host:remote"
|
118
|
+
assert_equal exp, e.message
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_rsync_deprecation
|
122
|
+
util_setup_task
|
123
|
+
@task.target_host = "app.example.com"
|
124
|
+
|
125
|
+
out, err = capture_io do
|
126
|
+
@task.rsync 'localfile', 'remotefile'
|
127
|
+
end
|
128
|
+
|
129
|
+
commands = @task.commands
|
130
|
+
|
131
|
+
assert_equal 1, commands.size, 'not enough commands'
|
132
|
+
assert_equal(%w[rsync -azP --delete localfile app.example.com:remotefile],
|
133
|
+
commands.first)
|
134
|
+
|
135
|
+
assert_equal("rsync deprecation: pass target_host:remote_path explicitly\n",
|
136
|
+
err)
|
137
|
+
assert_empty out
|
138
|
+
# flunk "not yet"
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_get
|
142
|
+
util_setup_task
|
143
|
+
@task.target_host = "app.example.com"
|
144
|
+
|
145
|
+
assert_silent do
|
146
|
+
@task.get 'tmp', "remote1", "remote2"
|
147
|
+
end
|
148
|
+
|
149
|
+
commands = @task.commands
|
150
|
+
|
151
|
+
expected = %w[rsync -azP --delete app.example.com:remote1 app.example.com:remote2 tmp]
|
152
|
+
|
153
|
+
assert_equal 1, commands.size
|
154
|
+
assert_equal expected, commands.first
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_put
|
158
|
+
util_setup_task
|
159
|
+
@task.target_host = "app.example.com"
|
160
|
+
|
161
|
+
assert_silent do
|
162
|
+
@task.put 'dest' do
|
163
|
+
"whatever"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
commands = @task.commands
|
168
|
+
|
169
|
+
expected = %w[rsync -azP --delete HAPPY app.example.com:dest]
|
170
|
+
commands.first[3] = 'HAPPY'
|
171
|
+
|
172
|
+
assert_equal 1, commands.size
|
173
|
+
assert_equal expected, commands.first
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_run
|
177
|
+
util_setup_task
|
178
|
+
@task.output << "file1\nfile2\n"
|
179
|
+
@task.target_host = "app.example.com"
|
180
|
+
result = nil
|
181
|
+
|
182
|
+
out, err = capture_io do
|
183
|
+
result = @task.run("ls")
|
184
|
+
end
|
185
|
+
|
186
|
+
commands = @task.commands
|
187
|
+
|
188
|
+
assert_equal 1, commands.size, 'not enough commands'
|
189
|
+
assert_equal ["ssh", "app.example.com", "ls"],
|
190
|
+
commands.first, 'app'
|
191
|
+
assert_equal "file1\nfile2\n", result
|
192
|
+
|
193
|
+
assert_equal "file1\nfile2\n", out
|
194
|
+
assert_equal '', err
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_run_dir
|
198
|
+
util_setup_task
|
199
|
+
@task.target_host = "app.example.com:/www/dir1"
|
200
|
+
|
201
|
+
@task.run("ls")
|
202
|
+
|
203
|
+
commands = @task.commands
|
204
|
+
|
205
|
+
assert_equal 1, commands.size, 'not enough commands'
|
206
|
+
assert_equal [["ssh", "app.example.com", "cd /www/dir1 && ls"]], commands
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_run_failing_command
|
210
|
+
util_set_hosts
|
211
|
+
util_setup_task
|
212
|
+
@task.input = StringIO.new "file1\nfile2\n"
|
213
|
+
@task.target_host = 'app.example.com'
|
214
|
+
@task.action = lambda { 1 }
|
215
|
+
|
216
|
+
e = assert_raises(Rake::CommandFailedError) { @task.run("ls") }
|
217
|
+
assert_equal "execution failed with status 1: ssh app.example.com ls", e.message
|
218
|
+
|
219
|
+
assert_equal 1, @task.commands.size
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_run_sudo
|
223
|
+
util_setup_task
|
224
|
+
@task.output << "file1\nfile2\n"
|
225
|
+
@task.error << 'Password:'
|
226
|
+
@task.target_host = "app.example.com"
|
227
|
+
def @task.sudo_password() "my password" end # gets defined by set
|
228
|
+
result = nil
|
229
|
+
|
230
|
+
out, err = capture_io do
|
231
|
+
result = @task.run("sudo ls")
|
232
|
+
end
|
233
|
+
|
234
|
+
commands = @task.commands
|
235
|
+
|
236
|
+
assert_equal 1, commands.size, 'not enough commands'
|
237
|
+
assert_equal ['ssh', 'app.example.com', 'sudo ls'],
|
238
|
+
commands.first
|
239
|
+
|
240
|
+
assert_equal "my password\n", @task.input.string
|
241
|
+
|
242
|
+
# WARN: Technically incorrect, the password line should be
|
243
|
+
# first... this is an artifact of changes to the IO code in run
|
244
|
+
# and the fact that we have a very simplistic (non-blocking)
|
245
|
+
# testing model.
|
246
|
+
assert_equal "file1\nfile2\nPassword:\n", result
|
247
|
+
|
248
|
+
assert_equal "file1\nfile2\n", out
|
249
|
+
assert_equal "Password:\n", err
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_sudo
|
253
|
+
util_setup_task
|
254
|
+
@task.target_host = "app.example.com"
|
255
|
+
@task.sudo "ls"
|
256
|
+
|
257
|
+
commands = @task.commands
|
258
|
+
|
259
|
+
assert_equal 1, commands.size, 'wrong number of commands'
|
260
|
+
assert_equal ["ssh", "app.example.com", "sudo -p Password: ls"],
|
261
|
+
commands.first, 'app'
|
262
|
+
end
|
263
|
+
|
264
|
+
def util_setup_task(options = {})
|
265
|
+
@task = @rake.remote_task :test_task, options
|
266
|
+
@task.commands = []
|
267
|
+
@task.output = []
|
268
|
+
@task.error = []
|
269
|
+
@task.action = nil
|
270
|
+
@task
|
271
|
+
end
|
272
|
+
end
|
metadata
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rake-remote_task
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 2.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Davis
|
14
|
+
- Eric Hodel
|
15
|
+
- Wilson Bilkovich
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain:
|
19
|
+
- |
|
20
|
+
-----BEGIN CERTIFICATE-----
|
21
|
+
MIIDPjCCAiagAwIBAgIBADANBgkqhkiG9w0BAQUFADBFMRMwEQYDVQQDDApyeWFu
|
22
|
+
ZC1ydWJ5MRkwFwYKCZImiZPyLGQBGRYJemVuc3BpZGVyMRMwEQYKCZImiZPyLGQB
|
23
|
+
GRYDY29tMB4XDTA5MDMwNjE4NTMxNVoXDTEwMDMwNjE4NTMxNVowRTETMBEGA1UE
|
24
|
+
AwwKcnlhbmQtcnVieTEZMBcGCgmSJomT8ixkARkWCXplbnNwaWRlcjETMBEGCgmS
|
25
|
+
JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALda
|
26
|
+
b9DCgK+627gPJkB6XfjZ1itoOQvpqH1EXScSaba9/S2VF22VYQbXU1xQXL/WzCkx
|
27
|
+
taCPaLmfYIaFcHHCSY4hYDJijRQkLxPeB3xbOfzfLoBDbjvx5JxgJxUjmGa7xhcT
|
28
|
+
oOvjtt5P8+GSK9zLzxQP0gVLS/D0FmoE44XuDr3iQkVS2ujU5zZL84mMNqNB1znh
|
29
|
+
GiadM9GHRaDiaxuX0cIUBj19T01mVE2iymf9I6bEsiayK/n6QujtyCbTWsAS9Rqt
|
30
|
+
qhtV7HJxNKuPj/JFH0D2cswvzznE/a5FOYO68g+YCuFi5L8wZuuM8zzdwjrWHqSV
|
31
|
+
gBEfoTEGr7Zii72cx+sCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
|
32
|
+
HQYDVR0OBBYEFEfFe9md/r/tj/Wmwpy+MI8d9k/hMA0GCSqGSIb3DQEBBQUAA4IB
|
33
|
+
AQAY59gYvDxqSqgC92nAP9P8dnGgfZgLxP237xS6XxFGJSghdz/nI6pusfCWKM8m
|
34
|
+
vzjjH2wUMSSf3tNudQ3rCGLf2epkcU13/rguI88wO6MrE0wi4ZqLQX+eZQFskJb/
|
35
|
+
w6x9W1ur8eR01s397LSMexySDBrJOh34cm2AlfKr/jokKCTwcM0OvVZnAutaovC0
|
36
|
+
l1SVZ0ecg88bsWHA0Yhh7NFxK1utWoIhtB6AFC/+trM0FQEB/jZkIS8SaNzn96Rl
|
37
|
+
n0sZEf77FLf5peR8TP/PtmIg7Cyqz23sLM4mCOoTGIy5OcZ8TdyiyINUHtb5ej/T
|
38
|
+
FBHgymkyj/AOSqKRIpXPhjC6
|
39
|
+
-----END CERTIFICATE-----
|
40
|
+
|
41
|
+
date: 2010-07-15 00:00:00 -07:00
|
42
|
+
default_executable:
|
43
|
+
dependencies:
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rake
|
46
|
+
prerelease: false
|
47
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ~>
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
hash: 63
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
- 8
|
56
|
+
- 0
|
57
|
+
version: 0.8.0
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id001
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: open4
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 59
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
- 9
|
72
|
+
- 0
|
73
|
+
version: 0.9.0
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id002
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: rubyforge
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 7
|
85
|
+
segments:
|
86
|
+
- 2
|
87
|
+
- 0
|
88
|
+
- 4
|
89
|
+
version: 2.0.4
|
90
|
+
type: :development
|
91
|
+
version_requirements: *id003
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: minitest
|
94
|
+
prerelease: false
|
95
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 11
|
101
|
+
segments:
|
102
|
+
- 1
|
103
|
+
- 7
|
104
|
+
- 0
|
105
|
+
version: 1.7.0
|
106
|
+
type: :development
|
107
|
+
version_requirements: *id004
|
108
|
+
- !ruby/object:Gem::Dependency
|
109
|
+
name: minitest
|
110
|
+
prerelease: false
|
111
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ~>
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 11
|
117
|
+
segments:
|
118
|
+
- 1
|
119
|
+
- 7
|
120
|
+
- 0
|
121
|
+
version: 1.7.0
|
122
|
+
type: :development
|
123
|
+
version_requirements: *id005
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: hoe
|
126
|
+
prerelease: false
|
127
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
hash: 21
|
133
|
+
segments:
|
134
|
+
- 2
|
135
|
+
- 6
|
136
|
+
- 1
|
137
|
+
version: 2.6.1
|
138
|
+
type: :development
|
139
|
+
version_requirements: *id006
|
140
|
+
description: |-
|
141
|
+
Vlad the Deployer's sexy brainchild is rake-remote_task, extending
|
142
|
+
Rake with remote task goodness.
|
143
|
+
email:
|
144
|
+
- ryand-ruby@zenspider.com
|
145
|
+
- drbrain@segment7.net
|
146
|
+
- wilson@supremetyrant.com
|
147
|
+
executables: []
|
148
|
+
|
149
|
+
extensions: []
|
150
|
+
|
151
|
+
extra_rdoc_files:
|
152
|
+
- History.txt
|
153
|
+
- Manifest.txt
|
154
|
+
- README.txt
|
155
|
+
files:
|
156
|
+
- .autotest
|
157
|
+
- History.txt
|
158
|
+
- Manifest.txt
|
159
|
+
- README.txt
|
160
|
+
- Rakefile
|
161
|
+
- lib/rake/remote_task.rb
|
162
|
+
- lib/rake/test_case.rb
|
163
|
+
- test/test_rake_remote_task.rb
|
164
|
+
has_rdoc: true
|
165
|
+
homepage: http://rubyhitsquad.com/
|
166
|
+
licenses: []
|
167
|
+
|
168
|
+
post_install_message:
|
169
|
+
rdoc_options:
|
170
|
+
- --main
|
171
|
+
- README.txt
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
none: false
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
hash: 3
|
180
|
+
segments:
|
181
|
+
- 0
|
182
|
+
version: "0"
|
183
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
|
+
none: false
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
hash: 3
|
189
|
+
segments:
|
190
|
+
- 0
|
191
|
+
version: "0"
|
192
|
+
requirements: []
|
193
|
+
|
194
|
+
rubyforge_project: hitsquad
|
195
|
+
rubygems_version: 1.3.7
|
196
|
+
signing_key:
|
197
|
+
specification_version: 3
|
198
|
+
summary: Vlad the Deployer's sexy brainchild is rake-remote_task, extending Rake with remote task goodness.
|
199
|
+
test_files:
|
200
|
+
- test/test_rake_remote_task.rb
|
metadata.gz.sig
ADDED
Binary file
|