rake-remote_task 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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