ktheory-vlad 2.0.0

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