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 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
@@ -0,0 +1,7 @@
1
+ === 2.0.0 / 2010-07-15
2
+
3
+ * 1 major enhancement
4
+
5
+ * Split out from Vlad.
6
+
7
+
data/Manifest.txt ADDED
@@ -0,0 +1,8 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/rake/remote_task.rb
7
+ lib/rake/test_case.rb
8
+ test/test_rake_remote_task.rb
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, &block; #{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