felix-vlad 1.2.0.3

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,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,77 @@
1
+
2
+ == Core Variables
3
+
4
+ code_repo:: 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:: 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 nil.
45
+ sudo_password:: Asks for password when referenced.
46
+
47
+ == Apache Web Variables:
48
+
49
+ web_command:: Command to execute when controlling the web server.
50
+ Defaults to "apachectl".
51
+
52
+ == Mongrel App Variables:
53
+
54
+ mongrel_address:: Defaults to "127.0.0.1"
55
+ mongrel_clean:: Defaults to false
56
+ mongrel_command:: Defaults to 'mongrel_rails'
57
+ mongrel_conf:: Defaults to "#{shared_path}/mongrel_cluster.conf"
58
+ mongrel_config_script:: Defaults to nil
59
+ mongrel_environment:: Defaults to "production"
60
+ mongrel_group:: Defaults to nil
61
+ mongrel_log_file:: Defaults to nil
62
+ mongrel_pid_file:: Defaults to nil
63
+ mongrel_port:: Defaults to 8000
64
+ mongrel_prefix:: Defaults to nil
65
+ mongrel_servers:: Defaults to 2
66
+ mongrel_user:: Defaults to nil
67
+
68
+ == Perforce SCM Variables:
69
+
70
+ p4_cmd:: The perforce command to use. Defaults to "p4"
71
+ source:: A perforce SCM worker instance.
72
+
73
+ == Subversion SCM Variables:
74
+
75
+ source:: A subversion SCM worker instance.
76
+ svn_cmd:: The subversion command to use. Defaults to "svn"
77
+
@@ -0,0 +1,566 @@
1
+ require 'rubygems'
2
+ require 'open4'
3
+ require 'rake'
4
+ require 'vlad'
5
+
6
+ $TESTING ||= false
7
+ $TRACE = Rake.application.options.trace
8
+
9
+ module Rake
10
+ module TaskManager
11
+ ##
12
+ # This gives us access to the tasks already defined in rake.
13
+ def all_tasks
14
+ @tasks
15
+ end
16
+ end
17
+
18
+ ##
19
+ # Hooks into rake and allows us to clear out a task by name or
20
+ # regexp. Use this if you want to completely override a task instead
21
+ # of extend it.
22
+ def self.clear_tasks(*tasks)
23
+ tasks.flatten.each do |name|
24
+ case name
25
+ when Regexp then
26
+ Rake.application.all_tasks.delete_if { |k,_| k =~ name }
27
+ else
28
+ Rake.application.all_tasks.delete(name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Declare a remote host and its roles. Equivalent to <tt>role</tt>,
36
+ # but shorter for multiple roles.
37
+ def host host_name, *roles
38
+ Rake::RemoteTask.host host_name, *roles
39
+ end
40
+
41
+ ##
42
+ # Copy a (usually generated) file to +remote_path+. Contents of block
43
+ # are copied to +remote_path+ and you may specify an optional
44
+ # base_name for the tempfile (aids in debugging).
45
+
46
+ def put remote_path, base_name = 'vlad.unknown'
47
+ Tempfile.open base_name do |fp|
48
+ fp.puts yield
49
+ fp.flush
50
+ rsync fp.path, remote_path
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Declare a Vlad task that will execute on all hosts by default. To
56
+ # limit that task to specific roles, use:
57
+ #
58
+ # remote_task :example, :roles => [:app, :web] do
59
+ def remote_task name, options = {}, &b
60
+ Rake::RemoteTask.remote_task name, options, &b
61
+ end
62
+
63
+ ##
64
+ # Declare a role and assign a remote host to it. Equivalent to the
65
+ # <tt>host</tt> method; provided for capistrano compatibility.
66
+ def role role_name, host, args = {}
67
+ Rake::RemoteTask.role role_name, host, args
68
+ end
69
+
70
+ ##
71
+ # Execute the given command on the <tt>target_host</tt> for the
72
+ # current task.
73
+ def run *args, &b
74
+ Thread.current[:task].run(*args, &b)
75
+ end
76
+
77
+ # rsync the given files to <tt>target_host</tt>.
78
+ def rsync local, remote
79
+ Thread.current[:task].rsync local, remote
80
+ end
81
+
82
+ # Declare a variable called +name+ and assign it a value. A
83
+ # globally-visible method with the name of the variable is defined.
84
+ # If a block is given, it will be called when the variable is first
85
+ # accessed. Subsequent references to the variable will always return
86
+ # the same value. Raises <tt>ArgumentError</tt> if the +name+ would
87
+ # conflict with an existing method.
88
+ def set name, val = nil, &b
89
+ Rake::RemoteTask.set name, val, &b
90
+ end
91
+
92
+ # Returns the name of the host that the current task is executing on.
93
+ # <tt>target_host</tt> can uniquely identify a particular task/host
94
+ # combination.
95
+ def target_host
96
+ Thread.current[:task].target_host
97
+ end
98
+
99
+ if Gem::Version.new(RAKEVERSION) < Gem::Version.new('0.8') then
100
+ class Rake::Task
101
+ alias vlad_original_execute execute
102
+
103
+ def execute(args = nil)
104
+ vlad_original_execute
105
+ end
106
+ end
107
+ end
108
+
109
+ ##
110
+ # Rake::RemoteTask is a subclass of Rake::Task that adds
111
+ # remote_actions that execute in parallel on multiple hosts via ssh.
112
+
113
+ class Rake::RemoteTask < Rake::Task
114
+
115
+ include Open4
116
+
117
+ ##
118
+ # Options for execution of this task.
119
+
120
+ attr_accessor :options
121
+
122
+ ##
123
+ # The host this task is running on during execution.
124
+
125
+ attr_accessor :target_host
126
+
127
+ ##
128
+ # An Array of Actions this host will perform during execution. Use
129
+ # enhance to add new actions to a task.
130
+
131
+ attr_reader :remote_actions
132
+
133
+ ##
134
+ # Create a new task named +task_name+ attached to Rake::Application +app+.
135
+
136
+ def initialize(task_name, app)
137
+ super
138
+ @remote_actions = []
139
+ end
140
+
141
+ ##
142
+ # Add a local action to this task. This calls Rake::Task#enhance.
143
+
144
+ alias_method :original_enhance, :enhance
145
+
146
+ ##
147
+ # Add remote action +block+ to this task with dependencies +deps+. See
148
+ # Rake::Task#enhance.
149
+
150
+ def enhance(deps=nil, &block)
151
+ original_enhance(deps) # can't use super because block passed regardless.
152
+ @remote_actions << Action.new(self, block) if block_given?
153
+ self
154
+ end
155
+
156
+ ##
157
+ # Execute this action. Local actions will be performed first, then remote
158
+ # actions will be performed in parallel on each host configured for this
159
+ # RemoteTask.
160
+
161
+ def execute(args = nil)
162
+ raise(Vlad::ConfigurationError,
163
+ "No target hosts specified for task: #{self.name}") if
164
+ target_hosts.empty?
165
+
166
+ super args
167
+
168
+ @remote_actions.each { |act| act.execute(target_hosts, args) }
169
+ end
170
+
171
+ ##
172
+ # Use rsync to send +local+ to +remote+ on target_host.
173
+
174
+ def rsync local, remote
175
+ cmd = [rsync_cmd, rsync_flags, local, "#{@target_host}:#{remote}"].flatten.compact
176
+
177
+ success = system(*cmd)
178
+
179
+ unless success then
180
+ raise Vlad::CommandFailedError, "execution failed: #{cmd.join ' '}"
181
+ end
182
+ end
183
+
184
+ ##
185
+ # Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
186
+ # sudo password will be prompted for then saved for subsequent sudo commands.
187
+
188
+ def run command
189
+ cmd = [ssh_cmd, ssh_flags, target_host, command].compact
190
+ result = []
191
+
192
+ warn cmd.join(' ') if $TRACE
193
+
194
+ pid, inn, out, err = popen4(*cmd)
195
+
196
+ inn.sync = true
197
+ streams = [out, err]
198
+ out_stream = {
199
+ out => $stdout,
200
+ err => $stderr,
201
+ }
202
+
203
+ # Handle process termination ourselves
204
+ status = nil
205
+ Thread.start do
206
+ status = Process.waitpid2(pid).last
207
+ end
208
+
209
+ until streams.empty? do
210
+ # don't busy loop
211
+ selected, = select streams, nil, nil, 0.1
212
+
213
+ next if selected.nil? or selected.empty?
214
+
215
+ selected.each do |stream|
216
+ if stream.eof? then
217
+ streams.delete stream if status # we've quit, so no more writing
218
+ next
219
+ end
220
+
221
+ data = stream.readpartial(1024)
222
+ out_stream[stream].write data
223
+
224
+ if stream == err and data =~ /^Password:/ then
225
+ inn.puts sudo_password
226
+ data << "\n"
227
+ $stderr.write "\n"
228
+ end
229
+
230
+ result << data
231
+ end
232
+ end
233
+
234
+ unless status.success? then
235
+ raise(Vlad::CommandFailedError,
236
+ "execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
237
+ end
238
+
239
+ result.join
240
+ end
241
+
242
+ ##
243
+ # Returns an Array with every host configured.
244
+
245
+ def self.all_hosts
246
+ hosts_for(roles.keys)
247
+ end
248
+
249
+ ##
250
+ # The default environment values. Used for resetting (mostly for
251
+ # tests).
252
+
253
+ def self.default_env
254
+ @@default_env
255
+ end
256
+
257
+ ##
258
+ # The vlad environment.
259
+
260
+ def self.env
261
+ @@env
262
+ end
263
+
264
+ ##
265
+ # Fetches environment variable +name+ from the environment using
266
+ # default +default+.
267
+
268
+ def self.fetch name, default = nil
269
+ name = name.to_s if Symbol === name
270
+ if @@env.has_key? name then
271
+ protect_env(name) do
272
+ v = @@env[name]
273
+ v = @@env[name] = v.call if Proc === v
274
+ v
275
+ end
276
+ elsif default
277
+ v = @@env[name] = default
278
+ else
279
+ raise Vlad::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(Vlad::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, options = {}, &block
334
+ t = Rake::RemoteTask.define_task(name, &block)
335
+ t.options = options
336
+ t
337
+ end
338
+
339
+ ##
340
+ # Ensures +name+ does not conflict with an existing method.
341
+
342
+ def self.reserved_name? name # :nodoc:
343
+ !@@env.has_key?(name.to_s) && self.respond_to?(name)
344
+ end
345
+
346
+ ##
347
+ # Resets vlad, restoring all roles, tasks and environment variables
348
+ # to the defaults.
349
+
350
+ def self.reset
351
+ @@roles = Hash.new { |h,k| h[k] = {} }
352
+ @@env = {}
353
+ @@tasks = {}
354
+ @@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
355
+
356
+ @@default_env.each do |k,v|
357
+ case v
358
+ when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
359
+ @@env[k] = v
360
+ else
361
+ @@env[k] = v.dup
362
+ end
363
+ end
364
+ end
365
+
366
+ ##
367
+ # Adds role +role_name+ with +host+ and +args+ for that host.
368
+
369
+ def self.role role_name, host, args = {}
370
+ raise ArgumentError, "invalid host" if host.nil? or host.empty?
371
+ @@roles[role_name][host] = args
372
+ end
373
+
374
+ ##
375
+ # The configured roles.
376
+
377
+ def self.roles
378
+ host domain, :app, :web, :db if @@roles.empty?
379
+
380
+ @@roles
381
+ end
382
+
383
+ ##
384
+ # Set environment variable +name+ to +value+ or +default_block+.
385
+ #
386
+ # If +default_block+ is defined, the block will be executed the
387
+ # first time the variable is fetched, and the value will be used for
388
+ # every subsequent fetch.
389
+
390
+ def self.set name, value = nil, &default_block
391
+ raise ArgumentError, "cannot provide both a value and a block" if
392
+ value and default_block
393
+ raise ArgumentError, "cannot set reserved name: '#{name}'" if
394
+ Rake::RemoteTask.reserved_name?(name) unless $TESTING
395
+
396
+ Rake::RemoteTask.default_env[name.to_s] = Rake::RemoteTask.env[name.to_s] =
397
+ value || default_block
398
+
399
+ Object.send :define_method, name do
400
+ Rake::RemoteTask.fetch name
401
+ end
402
+ end
403
+
404
+ ##
405
+ # Sets all the default values. Should only be called once. Use reset
406
+ # if you need to restore values.
407
+
408
+ def self.set_defaults
409
+ @@default_env ||= {}
410
+ self.reset
411
+
412
+ mandatory :code_repo, "code repo path"
413
+ mandatory :deploy_to, "deploy path"
414
+ mandatory :domain, "server domain"
415
+
416
+ simple_set(:deploy_timestamped, true,
417
+ :deploy_via, :export,
418
+ :keep_releases, 5,
419
+ :migrate_args, "",
420
+ :migrate_target, :latest,
421
+ :rails_env, "production",
422
+ :rake_cmd, "rake",
423
+ :revision, "head",
424
+ :rsync_cmd, "rsync",
425
+ :rsync_flags, ['-azP', '--delete'],
426
+ :ssh_cmd, "ssh",
427
+ :ssh_flags, nil,
428
+ :sudo_cmd, "sudo",
429
+ :sudo_flags, nil)
430
+
431
+ set(:current_release) { File.join(releases_path, releases[-1]) }
432
+ set(:latest_release) { deploy_timestamped ?release_path: current_release }
433
+ set(:previous_release) { File.join(releases_path, releases[-2]) }
434
+ set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
435
+ set(:release_path) { File.join(releases_path, release_name) }
436
+ set(:releases) { task.run("ls -x #{releases_path}").split.sort }
437
+
438
+ set_path :current_path, "current"
439
+ set_path :releases_path, "releases"
440
+ set_path :scm_path, "scm"
441
+ set_path :shared_path, "shared"
442
+
443
+ set(:sudo_password) do
444
+ state = `stty -g`
445
+
446
+ raise Vlad::Error, "stty(1) not found" unless $?.success?
447
+
448
+ begin
449
+ system "stty -echo"
450
+ $stdout.print "sudo password: "
451
+ $stdout.flush
452
+ sudo_password = $stdin.gets
453
+ $stdout.puts
454
+ ensure
455
+ system "stty #{state}"
456
+ end
457
+ sudo_password
458
+ end
459
+ end
460
+
461
+ def self.set_path(name, subdir) # :nodoc:
462
+ set(name) { File.join(deploy_to, subdir) }
463
+ end
464
+
465
+ def self.simple_set(*args) # :nodoc:
466
+ args = Hash[*args]
467
+ args.each do |k, v|
468
+ set k, v
469
+ end
470
+ end
471
+
472
+ ##
473
+ # The Rake::RemoteTask executing in this Thread.
474
+
475
+ def self.task
476
+ Thread.current[:task]
477
+ end
478
+
479
+ ##
480
+ # The configured Rake::RemoteTasks.
481
+
482
+ def self.tasks
483
+ @@tasks
484
+ end
485
+
486
+ ##
487
+ # Execute +command+ under sudo using run.
488
+
489
+ def sudo command
490
+ run [sudo_cmd, sudo_flags, command].compact.join(" ")
491
+ end
492
+
493
+ ##
494
+ # The hosts this task will execute on. The hosts are determined from
495
+ # the role this task belongs to.
496
+ #
497
+ # The target hosts may be overridden by providing a comma-separated
498
+ # list of commands to the HOSTS environment variable:
499
+ #
500
+ # rake my_task HOSTS=app1.example.com,app2.example.com
501
+
502
+ def target_hosts
503
+ if hosts = ENV["HOSTS"] then
504
+ hosts.strip.gsub(/\s+/, '').split(",")
505
+ else
506
+ roles = options[:roles]
507
+ roles ? Rake::RemoteTask.hosts_for(roles) : Rake::RemoteTask.all_hosts
508
+ end
509
+ end
510
+
511
+ ##
512
+ # Action is used to run a task's remote_actions in parallel on each
513
+ # of its hosts. Actions are created automatically in
514
+ # Rake::RemoteTask#enhance.
515
+
516
+ class Action
517
+
518
+ ##
519
+ # The task this action is attached to.
520
+
521
+ attr_reader :task
522
+
523
+ ##
524
+ # The block this action will execute.
525
+
526
+ attr_reader :block
527
+
528
+ ##
529
+ # An Array of threads, one for each host this action executes on.
530
+
531
+ attr_reader :workers
532
+
533
+ ##
534
+ # Creates a new Action that will run +block+ for +task+.
535
+
536
+ def initialize task, block
537
+ @task = task
538
+ @block = block
539
+ @workers = []
540
+ end
541
+
542
+ def == other # :nodoc:
543
+ return false unless Action === other
544
+ block == other.block && task == other.task
545
+ end
546
+
547
+ ##
548
+ # Execute this action on +hosts+ in parallel. Returns when block
549
+ # has completed for each host.
550
+
551
+ def execute hosts, args = nil
552
+ hosts.each do |host|
553
+ t = task.clone
554
+ t.target_host = host
555
+ thread = Thread.new(t) do |task|
556
+ Thread.current[:task] = task
557
+ block.call args
558
+ end
559
+ @workers << thread
560
+ end
561
+ @workers.each { |w| w.join }
562
+ end
563
+ end
564
+ end
565
+
566
+ Rake::RemoteTask.set_defaults