isst-vlad 2.0.1

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.
@@ -0,0 +1,43 @@
1
+ == Converting from Capistrano
2
+
3
+ * 'set scm' is removed. Vlad.load :scm => :something if you don't use subversion.
4
+ * 'task' blocks are renamed to 'remote_task'.
5
+ * Most variables are the same. See variables.txt for details.
6
+ * No +with_command+ / +sudo+ / +via+ wonkiness
7
+ * Uses real ssh so env vars and the like are not a problem
8
+ - no +with_env+ as a result.
9
+ * Vlad doesn't use ':no_release' or ':primary'.
10
+ - If you have a task that needs to run on only one host from a role,
11
+ you should declare a new role for that host:
12
+
13
+ role :master_db, "master.example.com"
14
+
15
+ ..and then override the role for the task you want to limit:
16
+
17
+ Rake::Task["mytask"].options[:roles] = :master_db
18
+
19
+ * The 'host' method can be used to consolidate multiple 'role' calls.
20
+ - host "www.example.com", :app, :web, :db
21
+ specifies a host with three roles.
22
+ * migrate_env is now migrate_args.
23
+ * Vlad doesn't have before/after magic add-on tasks.
24
+
25
+ == BEFORE:
26
+
27
+ set :application, "rubyholic"
28
+ set :domain, "zenspider.textdriven.com"
29
+ set :repository, "svn://svn.example.com/rubyholic/branches/stable"
30
+ set :deploy_to, "/users/home/zenspider/domains/new.rubyholic.com"
31
+
32
+ set :user, "zenspider"
33
+ set :use_sudo, false
34
+
35
+ role :web, domain
36
+ role :app, domain
37
+ role :db, domain, :primary => true
38
+
39
+ == AFTER:
40
+
41
+ set :domain, "zenspider.textdriven.com"
42
+ set :repository, "svn://svn.example.com/rubyholic/branches/stable"
43
+ set :deploy_to, "/users/home/zenspider/domains/new.rubyholic.com"
@@ -0,0 +1,5 @@
1
+ # Using Perforce
2
+
3
+ TODO: write real doco.
4
+
5
+ Set up a .p4config file and put it in the client's scm directories.
@@ -0,0 +1,84 @@
1
+
2
+ == Core Variables
3
+
4
+ repository:: REQUIRED: Repository path: e.g. http://repo.example.com/svn
5
+ deploy_to:: REQUIRED: Deploy path on target machines. e.g. /var/www/app
6
+ domain:: REQUIRED: Used for the common case of a single target
7
+ server. e.g. example.com
8
+ current_path:: The full path on the remote host that will be symlinked
9
+ as 'current'. Defaults to "#{deploy_to}/current".
10
+ current_release:: The full path to the current release's actual location.
11
+ Defaults to "#{releases_path}/#{releases.last}".
12
+ deploy_timestamped:: Create timestamped release directories instead of using
13
+ revision numbers. Defaults to true.
14
+ deploy_via:: Which SCM command should be used when deploying the app.
15
+ Defaults to "export".
16
+ latest_release:: The most recent release, which may not yet have been
17
+ symlinked. Defaults to release_path.
18
+ migrate_args:: Set this to change the RAILS_ENV that 'rake db:migrate'
19
+ will run under. Defaults to "".
20
+ migrate_target:: Set this if you need to specify a particular migration
21
+ 'VERSION' number. Defaults to "latest".
22
+ prepend_cmd:: Will be used to prepend every command that 'run' calls
23
+ Example: prepend_cmd = "sudo bash -rc" command = "ls -l"
24
+ this would actually run sudo bash -rc ls -l on remote host
25
+ rails_env:: Specifies the RAILS_ENV environment variable that will
26
+ be used. Defaults to "production".
27
+ rack_env:: Specifies the RACK_ENV environment variable that will
28
+ be used. Defaults to "production".
29
+ rake_cmd:: Set this if you need to specify an alternate path to
30
+ 'rake'. Defaults to "rake".
31
+ release_name:: Name of the release directory, if deploy_timestamped is
32
+ true. Defaults to timestamp: "YYYYMMDDHHMMSS".
33
+ release_path:: Path to this release, which may not have been created
34
+ yet. Defaults to "#{releases_path}/#{release_name}".
35
+ releases:: An array of all existing releases, oldest first.
36
+ Defaults to latest release directory name.
37
+ releases_path:: Full path to the 'releases' directory on the remote host.
38
+ Defaults to "#{deploy_to}/releases".
39
+ revision:: Revision to use for release. Defaults to 'head'.
40
+ rsync_cmd:: Path to rsync command. Defaults to "rsync".
41
+ rsync_flags:: Flags for rsync. Defaults to ['-azP', '--delete'].
42
+ scm_path:: Path on the remote host that will be used as 'working
43
+ space' for SCM tasks. Defaults to "#{deploy_to}/scm".
44
+ shared_path:: Full path to remote 'shared' directory, symlinked into
45
+ your app by default. Defaults to "#{deploy_to}/shared".
46
+ ssh_cmd:: Path to ssh. Defaults to "ssh".
47
+ ssh_flags:: Flags for ssh. Defaults to [].
48
+ sudo_cmd:: Path to sudo command. Defaults to "sudo".
49
+ sudo_flags:: Flogs for sudo. Defaults to ["-p Password:"].
50
+ sudo_prompt:: Regexp for sudo password prompt. Defaults to /^Password:/.
51
+ sudo_password:: Asks for password when referenced.
52
+ umask:: Sets your umask value. Defaults to "02".
53
+
54
+ == Apache Web Variables:
55
+
56
+ web_command:: Command to execute when controlling the web server.
57
+ Defaults to "apachectl".
58
+
59
+ == Mongrel App Variables:
60
+
61
+ mongrel_address:: Defaults to "127.0.0.1"
62
+ mongrel_clean:: Defaults to false
63
+ mongrel_command:: Defaults to 'mongrel_rails'
64
+ mongrel_conf:: Defaults to "#{shared_path}/mongrel_cluster.conf"
65
+ mongrel_config_script:: Defaults to nil
66
+ mongrel_environment:: Defaults to "production"
67
+ mongrel_group:: Defaults to nil
68
+ mongrel_log_file:: Defaults to nil
69
+ mongrel_pid_file:: Defaults to nil
70
+ mongrel_port:: Defaults to 8000
71
+ mongrel_prefix:: Defaults to nil
72
+ mongrel_servers:: Defaults to 2
73
+ mongrel_user:: Defaults to nil
74
+
75
+ == Perforce SCM Variables:
76
+
77
+ p4_cmd:: The perforce command to use. Defaults to "p4"
78
+ source:: A perforce SCM worker instance.
79
+
80
+ == Subversion SCM Variables:
81
+
82
+ source:: A subversion SCM worker instance.
83
+ svn_cmd:: The subversion command to use. Defaults to "svn"
84
+
@@ -0,0 +1,89 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{isst-vlad}
8
+ s.version = "2.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ryan Davis", "Eric Hodel", "Wilson Bilkovich", "Aaron Suggs", "Ryan Richins"]
12
+ s.date = %q{2010-06-06}
13
+ s.description = %q{
14
+ Vlad the Deployer is pragmatic application deployment automation,
15
+ without mercy. Much like Capistrano, but with 1/10th the
16
+ complexity. Vlad integrates seamlessly with Rake, and uses familiar
17
+ and standard tools like ssh and rsync. This is a fork of vlad maintained by
18
+ NETO ISS Tools (Dudes at Comcast). See PATCHES.txt for more info""
19
+ }
20
+ s.email = ["ryand-ruby@zenspider.com", "drbrain@segment7.net", "wilson@supremetyrant.com", "aaron@ktheory.com", "Ryan_Richins@cable.comcast.com"]
21
+ s.extra_rdoc_files = [
22
+ "README.txt"
23
+ ]
24
+ s.files = [
25
+ ".autotest",
26
+ "History.txt",
27
+ "PATCHES.txt",
28
+ "README.txt",
29
+ "Rakefile",
30
+ "VERSION",
31
+ "considerations.txt",
32
+ "doco/deploying-merb-with-vlad.txt",
33
+ "doco/deploying-sinatra-with-vlad.txt",
34
+ "doco/faq.txt",
35
+ "doco/getting_started.txt",
36
+ "doco/migration.txt",
37
+ "doco/perforce.txt",
38
+ "doco/variables.txt",
39
+ "isst-vlad.gemspec",
40
+ "lib/remote_task.rb",
41
+ "lib/test_case.rb",
42
+ "lib/vlad.rb",
43
+ "lib/vlad/apache.rb",
44
+ "lib/vlad/core.rb",
45
+ "lib/vlad/git.rb",
46
+ "lib/vlad/maintenance.rb",
47
+ "lib/vlad/merb.rb",
48
+ "lib/vlad/nginx.rb",
49
+ "lib/vlad/passenger.rb",
50
+ "lib/vlad/rails.rb",
51
+ "lib/vlad/sequel.rb",
52
+ "lib/vlad/sinatra.rb",
53
+ "lib/vlad/subversion.rb",
54
+ "lib/vlad/thin.rb",
55
+ "test/test_remote_task.rb",
56
+ "test/test_vlad.rb",
57
+ "test/test_vlad_git.rb",
58
+ "test/test_vlad_subversion.rb",
59
+ "vladdemo.sh"
60
+ ]
61
+ s.homepage = %q{http://github.com/netoisstools/isst-vlad}
62
+ s.rdoc_options = ["--charset=UTF-8"]
63
+ s.require_paths = ["lib"]
64
+ s.rubygems_version = %q{1.3.7}
65
+ s.summary = %q{Vlad the Deployer is pragmatic application deployment automation, without mercy [isst fork]}
66
+ s.test_files = [
67
+ "test/test_remote_task.rb",
68
+ "test/test_vlad.rb",
69
+ "test/test_vlad_git.rb",
70
+ "test/test_vlad_subversion.rb"
71
+ ]
72
+
73
+ if s.respond_to? :specification_version then
74
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
75
+ s.specification_version = 3
76
+
77
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
78
+ s.add_runtime_dependency(%q<rake>, ["~> 0.8.0"])
79
+ s.add_runtime_dependency(%q<open4>, ["~> 0.9.0"])
80
+ else
81
+ s.add_dependency(%q<rake>, ["~> 0.8.0"])
82
+ s.add_dependency(%q<open4>, ["~> 0.9.0"])
83
+ end
84
+ else
85
+ s.add_dependency(%q<rake>, ["~> 0.8.0"])
86
+ s.add_dependency(%q<open4>, ["~> 0.9.0"])
87
+ end
88
+ end
89
+
@@ -0,0 +1,641 @@
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
+ @@current_roles = []
44
+
45
+ include Open4
46
+
47
+ ##
48
+ # Options for execution of this task.
49
+
50
+ attr_accessor :options
51
+
52
+ ##
53
+ # The host this task is running on during execution.
54
+
55
+ attr_reader :target_host
56
+
57
+ ##
58
+ # The directory on the host this task is running in during execution.
59
+
60
+ attr_reader :target_dir
61
+
62
+ ##
63
+ # An Array of Actions this host will perform during execution. Use
64
+ # enhance to add new actions to a task.
65
+
66
+ attr_reader :remote_actions
67
+
68
+ def self.current_roles
69
+ @@current_roles
70
+ end
71
+
72
+ ##
73
+ # Create a new task named +task_name+ attached to Rake::Application +app+.
74
+
75
+ def initialize(task_name, app)
76
+ super
77
+
78
+ @remote_actions = []
79
+ @happy = false # used for deprecation warnings on get/put/rsync
80
+ end
81
+
82
+ ##
83
+ # Add a local action to this task. This calls Rake::Task#enhance.
84
+
85
+ alias_method :original_enhance, :enhance
86
+
87
+ ##
88
+ # Add remote action +block+ to this task with dependencies +deps+. See
89
+ # Rake::Task#enhance.
90
+
91
+ def enhance(deps=nil, &block)
92
+ original_enhance(deps) # can't use super because block passed regardless.
93
+ @remote_actions << Action.new(self, block) if block_given?
94
+ self
95
+ end
96
+
97
+ ##
98
+ # Execute this action. Local actions will be performed first, then remote
99
+ # actions will be performed in parallel on each host configured for this
100
+ # RemoteTask.
101
+
102
+ def execute(args = nil)
103
+ raise(Rake::ConfigurationError,
104
+ "No target hosts specified on task #{self.name} for roles #{options[:roles].inspect}") unless
105
+ defined_target_hosts?
106
+
107
+ super args
108
+
109
+ @remote_actions.each { |act| act.execute(target_hosts, self, args) }
110
+ end
111
+
112
+ ##
113
+ # Pull +files+ from the remote +host+ using rsync to +local_dir+.
114
+ # TODO: what if role has multiple hosts & the files overlap? subdirs?
115
+
116
+ def get local_dir, *files
117
+ @happy = true
118
+ host = target_host
119
+ rsync files.map { |f| "#{host}:#{f}" }, local_dir
120
+ @happy = false
121
+ end
122
+
123
+ ##
124
+ # Copy a (usually generated) file to +remote_path+. Contents of block
125
+ # are copied to +remote_path+ and you may specify an optional
126
+ # base_name for the tempfile (aids in debugging).
127
+
128
+ def put remote_path, base_name = File.basename(remote_path)
129
+ require 'tempfile'
130
+ Tempfile.open base_name do |fp|
131
+ fp.puts yield
132
+ fp.flush
133
+ @happy = true
134
+ rsync fp.path, "#{target_host}:#{remote_path}"
135
+ @happy = false
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Execute rsync with +args+. Tacks on pre-specified +rsync_cmd+ and
141
+ # +rsync_flags+.
142
+ #
143
+ # Favor #get and #put for most tasks. Old-style direct use where the
144
+ # target_host was implicit is now deprecated.
145
+
146
+ def rsync *args
147
+ unless @happy || args[-1] =~ /:/ then
148
+ warn "rsync deprecation: pass target_host:remote_path explicitly"
149
+ args[-1] = "#{target_host}:#{args[-1]}"
150
+ end
151
+
152
+ cmd = [rsync_cmd, rsync_flags, args].flatten.compact
153
+ cmdstr = cmd.join ' '
154
+
155
+ warn cmdstr if $TRACE
156
+
157
+ success = system(*cmd)
158
+
159
+ raise Rake::CommandFailedError, "execution failed: #{cmdstr}" unless success
160
+ end
161
+
162
+ ##
163
+ # Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
164
+ # sudo password will be prompted for then saved for subsequent sudo commands.
165
+
166
+ def run command
167
+ command = "cd #{target_dir} && #{command}" if target_dir
168
+ command = "#{prepend_cmd} '#{command}'" if prepend_cmd
169
+ cmd = [ssh_cmd, ssh_flags, target_host, command].flatten
170
+ result = []
171
+
172
+ trace = [ssh_cmd, ssh_flags, target_host, "'#{command}'"].flatten.join(' ')
173
+ warn trace if $TRACE
174
+
175
+ pid, inn, out, err = popen4(*cmd)
176
+
177
+ inn.sync = true
178
+ streams = [out, err]
179
+ out_stream = {
180
+ out => $stdout,
181
+ err => $stderr,
182
+ }
183
+
184
+ # Handle process termination ourselves
185
+ status = nil
186
+ Thread.start do
187
+ status = Process.waitpid2(pid).last
188
+ end
189
+
190
+ until streams.empty? do
191
+ # don't busy loop
192
+ selected, = select streams, nil, nil, 0.1
193
+
194
+ next if selected.nil? or selected.empty?
195
+
196
+ selected.each do |stream|
197
+ if stream.eof? then
198
+ streams.delete stream if status # we've quit, so no more writing
199
+ next
200
+ end
201
+
202
+ data = stream.readpartial(1024)
203
+
204
+ # Add the prefix to each line
205
+ if (prefix_output)
206
+ # prefix target hostname unless prefix_output is a custom string
207
+ prefix = String === prefix_output ? prefix_output : "#{target_host}: "
208
+
209
+ # don't prefix sudo prompts
210
+ data.gsub!(/^/, prefix) unless stream == err and data =~ sudo_prompt
211
+ end
212
+
213
+ out_stream[stream].write data
214
+
215
+ if stream == err and data =~ sudo_prompt then
216
+ inn.puts sudo_password
217
+ data << "\n"
218
+ $stderr.write "\n"
219
+ end
220
+
221
+ result << data
222
+ end
223
+ end
224
+
225
+ unless status.success? then
226
+ raise(Rake::CommandFailedError,
227
+ "execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
228
+ end
229
+
230
+ result.join
231
+ ensure
232
+ inn.close rescue nil
233
+ out.close rescue nil
234
+ err.close rescue nil
235
+ end
236
+
237
+ ##
238
+ # Returns an Array with every host configured.
239
+
240
+ def self.all_hosts
241
+ hosts_for(roles.keys)
242
+ end
243
+
244
+ ##
245
+ # The default environment values. Used for resetting (mostly for
246
+ # tests).
247
+
248
+ def self.default_env
249
+ @@default_env
250
+ end
251
+
252
+ def self.per_thread
253
+ @@per_thread
254
+ end
255
+
256
+ ##
257
+ # The vlad environment.
258
+
259
+ def self.env
260
+ @@env
261
+ end
262
+
263
+ ##
264
+ # Fetches environment variable +name+ from the environment using
265
+ # default +default+.
266
+
267
+ def self.fetch name, default = nil
268
+ name = name.to_s if Symbol === name
269
+ if @@env.has_key? name then
270
+ protect_env(name) do
271
+ v = @@env[name]
272
+ v = @@env[name] = v.call if Proc === v unless per_thread[name]
273
+ v = v.call if Proc === v
274
+ v
275
+ end
276
+ elsif default || default == false
277
+ v = @@env[name] = default
278
+ else
279
+ raise Rake::FetchError
280
+ end
281
+ end
282
+
283
+ ##
284
+ # Add host +host_name+ that belongs to +roles+. Extra arguments may
285
+ # be specified for the host as a hash as the last argument.
286
+ #
287
+ # host is the inversion of role:
288
+ #
289
+ # host 'db1.example.com', :db, :master_db
290
+ #
291
+ # Is equivalent to:
292
+ #
293
+ # role :db, 'db1.example.com'
294
+ # role :master_db, 'db1.example.com'
295
+
296
+ def self.host host_name, *roles
297
+ opts = Hash === roles.last ? roles.pop : {}
298
+
299
+ roles.each do |role_name|
300
+ role role_name, host_name, opts.dup
301
+ end
302
+ end
303
+
304
+ ##
305
+ # Returns an Array of all hosts in +roles+.
306
+
307
+ def self.hosts_for *roles
308
+ roles.flatten.map { |r|
309
+ self.roles[r].keys
310
+ }.flatten.uniq.sort
311
+ end
312
+
313
+ def self.mandatory name, desc # :nodoc:
314
+ self.set(name) do
315
+ raise(Rake::ConfigurationError,
316
+ "Please specify the #{desc} via the #{name.inspect} variable")
317
+ end
318
+ end
319
+
320
+ ##
321
+ # Ensures exclusive access to +name+.
322
+
323
+ def self.protect_env name # :nodoc:
324
+ @@env_locks[name].synchronize do
325
+ yield
326
+ end
327
+ end
328
+
329
+ ##
330
+ # Adds a remote task named +name+ with options +options+ that will
331
+ # execute +block+.
332
+
333
+ def self.remote_task name, *args, &block
334
+ options = (Hash === args.last) ? args.pop : {}
335
+ t = Rake::RemoteTask.define_task(name, *args, &block)
336
+ options[:roles] = Array options[:roles]
337
+ options[:roles] |= @@current_roles
338
+ t.options = options
339
+ t
340
+ end
341
+
342
+ ##
343
+ # Ensures +name+ does not conflict with an existing method.
344
+
345
+ def self.reserved_name? name # :nodoc:
346
+ !@@env.has_key?(name.to_s) && self.respond_to?(name)
347
+ end
348
+
349
+ ##
350
+ # Resets vlad, restoring all roles, tasks and environment variables
351
+ # to the defaults.
352
+
353
+ def self.reset
354
+ @@def_role_hash = {} # official default role value
355
+ @@env = {}
356
+ @@tasks = {}
357
+ @@roles = Hash.new { |h,k| h[k] = @@def_role_hash }
358
+ @@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
359
+
360
+ @@default_env.each do |k,v|
361
+ case v
362
+ when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
363
+ @@env[k] = v
364
+ else
365
+ @@env[k] = v.dup
366
+ end
367
+ end
368
+ end
369
+
370
+ ##
371
+ # Adds role +role_name+ with +host+ and +args+ for that host.
372
+ # TODO: merge:
373
+ # Declare a role and assign a remote host to it. Equivalent to the
374
+ # <tt>host</tt> method; provided for capistrano compatibility.
375
+
376
+ def self.role role_name, host = nil, args = {}
377
+ if block_given? then
378
+ raise ArgumentError, 'host not allowed with block' unless host.nil?
379
+
380
+ begin
381
+ current_roles << role_name
382
+ yield
383
+ ensure
384
+ current_roles.delete role_name
385
+ end
386
+ else
387
+ raise ArgumentError, 'host required' if host.nil?
388
+
389
+ [*host].each do |hst|
390
+ raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty?
391
+ end
392
+ @@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name]
393
+ @@roles[role_name][host] = args
394
+ end
395
+ end
396
+
397
+ ##
398
+ # The configured roles.
399
+
400
+ def self.roles
401
+ host domain, :app, :web, :db if @@roles.empty?
402
+
403
+ @@roles
404
+ end
405
+
406
+ ##
407
+ # Set environment variable +name+ to +value+ or +default_block+.
408
+ #
409
+ # If +default_block+ is defined, the block will be executed the
410
+ # first time the variable is fetched, and the value will be used for
411
+ # every subsequent fetch.
412
+
413
+ def self.set name, value = nil, &default_block
414
+ raise ArgumentError, "cannot provide both a value and a block" if
415
+ value and default_block unless
416
+ value == :per_thread
417
+ raise ArgumentError, "cannot set reserved name: '#{name}'" if
418
+ Rake::RemoteTask.reserved_name?(name) unless $TESTING
419
+
420
+ name = name.to_s
421
+
422
+ Rake::RemoteTask.per_thread[name] = true if
423
+ default_block && value == :per_thread
424
+
425
+ Rake::RemoteTask.default_env[name] = Rake::RemoteTask.env[name] =
426
+ default_block || value
427
+
428
+ Object.send :define_method, name do
429
+ Rake::RemoteTask.fetch name
430
+ end
431
+ end
432
+
433
+ ##
434
+ # Sets all the default values. Should only be called once. Use reset
435
+ # if you need to restore values.
436
+
437
+ def self.set_defaults
438
+ @@default_env ||= {}
439
+ @@per_thread ||= {}
440
+ self.reset
441
+
442
+ mandatory :repository, "repository path"
443
+ mandatory :deploy_to, "deploy path"
444
+ mandatory :domain, "server domain"
445
+
446
+ simple_set(:deploy_timestamped, true,
447
+ :deploy_via, :export,
448
+ :keep_releases, 5,
449
+ :rake_cmd, "rake",
450
+ :revision, "head",
451
+ :rsync_cmd, "rsync",
452
+ :rsync_flags, ['-azP', '--delete'],
453
+ :ssh_cmd, "ssh",
454
+ :ssh_flags, [],
455
+ :sudo_cmd, "sudo",
456
+ :sudo_flags, ['-p Password:'],
457
+ :sudo_prompt, /^Password:/,
458
+ :umask, '02',
459
+ :mkdirs, [],
460
+ :shared_paths, {},
461
+ :prefix_output, false,
462
+ :prepend_cmd, false)
463
+
464
+ set(:current_release) { File.join(releases_path, releases[-1]) }
465
+ set(:latest_release) { deploy_timestamped ? release_path : current_release}
466
+ set(:previous_release) { File.join(releases_path, releases[-2]) }
467
+ set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
468
+ set(:release_path) { File.join(releases_path, release_name) }
469
+ set(:releases) { task.run("ls -x #{releases_path}").split.sort }
470
+
471
+ set_path :current_path, "current"
472
+ set_path :releases_path, "releases"
473
+ set_path :scm_path, "scm"
474
+ set_path :shared_path, "shared"
475
+
476
+ set(:sudo_password) do
477
+ state = `stty -g`
478
+
479
+ raise Rake::Error, "stty(1) not found" unless $?.success?
480
+
481
+ begin
482
+ system "stty -echo"
483
+ $stdout.print "sudo password: "
484
+ $stdout.flush
485
+ sudo_password = $stdin.gets
486
+ $stdout.puts
487
+ ensure
488
+ system "stty #{state}"
489
+ end
490
+ sudo_password
491
+ end
492
+ end
493
+
494
+ def self.set_path(name, subdir) # :nodoc:
495
+ set(name) { File.join(deploy_to, subdir) }
496
+ end
497
+
498
+ def self.simple_set(*args) # :nodoc:
499
+ args = Hash[*args]
500
+ args.each do |k, v|
501
+ set k, v
502
+ end
503
+ end
504
+
505
+ ##
506
+ # The Rake::RemoteTask executing in this Thread.
507
+
508
+ def self.task
509
+ Thread.current[:task]
510
+ end
511
+
512
+ ##
513
+ # The configured Rake::RemoteTasks.
514
+
515
+ def self.tasks
516
+ @@tasks
517
+ end
518
+
519
+ ##
520
+ # Execute +command+ under sudo using run.
521
+
522
+ def sudo command
523
+ run [sudo_cmd, sudo_flags, command].flatten.compact.join(" ")
524
+ end
525
+
526
+ ##
527
+ # Sets the target host. Allows you to set an optional directory
528
+ # using the format:
529
+ #
530
+ # host.domain:/dir
531
+
532
+ def target_host= host
533
+ if host =~ /^(.+):(.+?)$/
534
+ @target_host = $1
535
+ @target_dir = $2
536
+ else
537
+ @target_host = host
538
+ @target_dir = nil
539
+ end
540
+ end
541
+
542
+ ##
543
+ # The hosts this task will execute on. The hosts are determined from
544
+ # the role this task belongs to.
545
+ #
546
+ # The target hosts may be overridden by providing a comma-separated
547
+ # list of commands to the HOSTS environment variable:
548
+ #
549
+ # rake my_task HOSTS=app1.example.com,app2.example.com
550
+
551
+ def target_hosts
552
+ if hosts = ENV["HOSTS"] then
553
+ hosts.strip.gsub(/\s+/, '').split(",")
554
+ else
555
+ roles = Array options[:roles]
556
+
557
+ if roles.empty? then
558
+ Rake::RemoteTask.all_hosts
559
+ else
560
+ Rake::RemoteTask.hosts_for roles
561
+ end
562
+ end
563
+ end
564
+
565
+ ##
566
+ # Similar to target_hosts, but returns true if user defined any hosts, even
567
+ # an empty list.
568
+
569
+ def defined_target_hosts?
570
+ return true if ENV["HOSTS"]
571
+ roles = Array options[:roles]
572
+ return true if roles.empty?
573
+ # borrowed from hosts_for:
574
+ roles.flatten.each { |r|
575
+ return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r]
576
+ }
577
+ return false
578
+ end
579
+
580
+ ##
581
+ # Action is used to run a task's remote_actions in parallel on each
582
+ # of its hosts. Actions are created automatically in
583
+ # Rake::RemoteTask#enhance.
584
+
585
+ class Action
586
+
587
+ ##
588
+ # The task this action is attached to.
589
+
590
+ attr_reader :task
591
+
592
+ ##
593
+ # The block this action will execute.
594
+
595
+ attr_reader :block
596
+
597
+ ##
598
+ # An Array of threads, one for each host this action executes on.
599
+
600
+ attr_reader :workers
601
+
602
+ ##
603
+ # Creates a new Action that will run +block+ for +task+.
604
+
605
+ def initialize task, block
606
+ @task = task
607
+ @block = block
608
+ @workers = ThreadGroup.new
609
+ end
610
+
611
+ def == other # :nodoc:
612
+ return false unless Action === other
613
+ block == other.block && task == other.task
614
+ end
615
+
616
+ ##
617
+ # Execute this action on +hosts+ in parallel. Returns when block
618
+ # has completed for each host.
619
+
620
+ def execute hosts, task, args
621
+ hosts.each do |host|
622
+ t = task.clone
623
+ t.target_host = host
624
+ thread = Thread.new(t) do |task|
625
+ Thread.current[:task] = task
626
+ case block.arity
627
+ when 1
628
+ block.call task
629
+ else
630
+ block.call task, args
631
+ end
632
+ Thread.current[:task] = nil
633
+ end
634
+ @workers.add thread
635
+ end
636
+ @workers.list.each { |thr| thr.join }
637
+ end
638
+ end
639
+ end
640
+
641
+ Rake::RemoteTask.set_defaults