ktheory-vlad 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.
@@ -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