vlad 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,5 +1,30 @@
1
+ == 1.1.0 / 2007-09-12
2
+
3
+ * 3 major enhancements:
4
+ * Vlad.load now takes a hash of recipe overrides, eg: Vlad.load :web => :nginx.
5
+ See rdoc for defaults.
6
+ * Removed vlad_tasks.rb and split into vlad/apache.rb, vlad/mongrel.rb,
7
+ and vlad/core.rb.
8
+ * The flog ratio between capistrano+deps / vlad+deps is pi (or, damn close)!
9
+ * 12 minor enhancements:
10
+ * Added $TRACE to make it more available and cleaner to read.
11
+ * Added :svn_cmd variable.
12
+ * Added Rake.clear_tasks *str_or_regexp
13
+ * Added debug and mana_from_heaven tasks to Rakefile.
14
+ * Added more documentation.
15
+ * Added :rsync_cmd and :rsync_flags.
16
+ * Added :ssh_cmd and :ssh_flags.
17
+ * Added variable expansion to vlad:debug task.
18
+ * Removed :scm variable. Now a Vlad.load component/flavor/need-a-word-here.
19
+ * Removed :application var. Use it if you want it. We don't require it.
20
+ * Renamed :p4cmd to :p4_cmd.
21
+ * Renamed :rake var to :rake_cmd.
22
+ * 2 (important) bug fixes:
23
+ * HUGE: Fixed sudo hang bug #13072. Fix suggested by Chris Van Pelt.
24
+ * HUGE: Vlad.load calls user config last, allowing variable overrides.
25
+ ACK! Sorry!
26
+
1
27
  == 1.0.0 / 2007-08-04
2
28
 
3
29
  * 1 major enhancement
4
30
  * Birthday!
5
-
data/Manifest.txt CHANGED
@@ -3,15 +3,18 @@ Manifest.txt
3
3
  README.txt
4
4
  Rakefile
5
5
  considerations.txt
6
+ doco/faq.txt
6
7
  doco/getting_started.txt
7
8
  doco/migration.txt
8
9
  doco/perforce.txt
9
10
  doco/variables.txt
10
11
  lib/rake_remote_task.rb
11
12
  lib/vlad.rb
13
+ lib/vlad/apache.rb
14
+ lib/vlad/core.rb
15
+ lib/vlad/mongrel.rb
12
16
  lib/vlad/perforce.rb
13
17
  lib/vlad/subversion.rb
14
- lib/vlad_tasks.rb
15
18
  test/test_rake_remote_task.rb
16
19
  test/test_vlad.rb
17
20
  test/test_vlad_perforce.rb
data/README.txt CHANGED
@@ -15,27 +15,24 @@ Impale your application on the heartless spike of the Deployer.
15
15
  == FEATURES/PROBLEMS:
16
16
 
17
17
  * Full deployment automation stack.
18
- * Supports single server deployment with just 4 variables defined.
18
+ * Turnkey deployment for mongrel+apache+svn.
19
+ * Supports single server deployment with just 3 variables defined.
20
+ * Built on rake. Easy. Engine is small.
19
21
  * Very few dependencies. All simple.
20
22
  * Uses ssh with your ssh settings already in place.
21
23
  * Uses rsync for efficient transfers.
22
24
  * Run remote commands on one or more servers.
23
- * Syncs files to one or more servers.
24
25
  * Mix and match local and remote tasks.
25
- * Built on rake. easy.
26
26
  * Compatible with all of your tab completion shell script rake-tastic goodness.
27
- * Ships with tests that actually pass.
28
- * Engine is under 500 lines of code.
29
- * Super uper simple.
30
- * Does NOT support Windows right now. Coming soon in 1.1.
31
- * This is 1.0.0... expect rough edges.
27
+ * Ships with tests that actually pass in 0.028 seconds!
28
+ * Does NOT support Windows right now (we think). Coming soon in 1.2.
32
29
 
33
30
  == SYNOPSIS:
34
31
 
35
- rake vlad:setup # first time only
36
- rake vlad:update
37
- rake vlad:migrate # optional
38
- rake vlad:start
32
+ % rake vlad:setup # first time only
33
+ % rake vlad:update
34
+ % rake vlad:migrate # optional
35
+ % rake vlad:start
39
36
 
40
37
  == REQUIREMENTS:
41
38
 
data/Rakefile CHANGED
@@ -11,17 +11,41 @@ Hoe.new('vlad', Vlad::VERSION) do |p|
11
11
  p.email = "ryand-ruby@zenspider.com"
12
12
  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/).map { |s| s.strip }[2..-1]
13
13
  p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
14
+ p.summary = p.paragraphs_of('README.txt', 2).join
14
15
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
15
16
  p.extra_deps << 'rake'
16
17
  p.extra_deps << 'open4'
17
18
  end
18
19
 
20
+ desc "quick little hack to see what the state of the nation looks like"
21
+ task :debug do
22
+ Vlad.load :config => "lib/vlad/subversion.rb"
23
+ set :repository, "repository path"
24
+ set :deploy_to, "deploy path"
25
+ set :domain, "server domain"
26
+
27
+ Rake::Task['vlad:debug'].invoke
28
+ end
29
+
19
30
  task :flog do
20
- sh 'find lib -name \*.rb | grep -v vlad_tasks | xargs flog | head -1'
31
+ sh 'flog -s lib'
21
32
  end
22
33
 
23
34
  task :flog_full do
24
- sh 'find lib -name \*.rb | xargs flog -a'
35
+ sh 'flog -a lib'
36
+ end
37
+
38
+ task :mana_from_heaven do
39
+ # vlad = vlad + rake + open4
40
+ # rake sans-contrib = 2035.98356718206
41
+ vlad = `flog -s lib`.to_f + 2350.30744806517 + 502.363818023761
42
+ cap = 11480.3919695285
43
+ ratio = cap / vlad
44
+ target = cap / Math::PI
45
+
46
+ puts "%14.8f = %s" % [vlad, "vlad"]
47
+ puts "%14.8f = %s" % [ratio, "ratio"]
48
+ puts "%14.8f = %s" % [target - vlad, "needed delta"]
25
49
  end
26
50
 
27
51
  # vim: syntax=Ruby
data/doco/faq.txt ADDED
@@ -0,0 +1,75 @@
1
+ == Rake & Recipes
2
+
3
+ === Q: Why is there no vlad:restart?
4
+ === A: It is cleaner!
5
+
6
+ We don't want to have to think about what state we're in and where. So vlad:start does a restart if necessary. Restart is just "start again" after all... That is what start does.
7
+
8
+ === Q: Why are there no before_action and after_action hooks?
9
+ === A: Because we use rake!
10
+
11
+ Rake don't need no stinkin' hooks! They're too clever. Last I checked before_after_before_start worked in cap... how? why? I dunno...
12
+
13
+ To extend a task (adding something after), just define it again:
14
+
15
+ task :action1 do
16
+ puts "one fish, two fish"
17
+ end
18
+
19
+ task :action1 do
20
+ puts "red fish, blue fish"
21
+ end
22
+
23
+ To prepend on a task, add a dependency:
24
+
25
+ task :action2 do
26
+ puts "red fish, blue fish"
27
+ end
28
+
29
+ task :myaction do
30
+ puts "one fish, two fish"
31
+ end
32
+
33
+ task :action2 => :myaction
34
+
35
+ === Q: How do I invoke another rule?
36
+ === A: The easiest way is via dependencies.
37
+
38
+ task :shazam! => [:action1, :action2]
39
+
40
+ The other way is to look it up and call invoke:
41
+
42
+ task :shazam! do
43
+ Rake::Task[:action1].invoke
44
+ Rake::Task[:action2].invoke
45
+ end
46
+
47
+ (Or, cheat and call out to rake again: sh "rake action1")
48
+
49
+ == Using SSH
50
+
51
+ === Q: Is there any way to set the ssh user?
52
+ === A: Yes, using ~/.ssh/config
53
+
54
+ Host example.com
55
+ User fluffy_bunny
56
+
57
+ OR: Alternatively, you can do this within your recipes like so:
58
+
59
+ set :user, "fluffy_bunny"
60
+ set :domain, "#{user}@example.com"
61
+
62
+ === Q: Is there any way to speed up ssh connections?
63
+ === A: Yes, add to your Host entry in ~/.ssh/config:
64
+
65
+ ControlMaster auto
66
+ ControlPath ~/.ssh/master-%r@%h:%p
67
+
68
+ === Q: I'm tired of typing in my password!
69
+ === A: Me too!
70
+
71
+ Put a password on your key, distribute your public key to the server and then use ssh-agent.
72
+
73
+ Check out this tiny tutorial at LBL: A brief ssh-agent tutorial <http://upc.lbl.gov/docs/user/sshagent.html>
74
+
75
+ If you're on a mac, use SSHKeychain, we love it. <http://www.sshkeychain.org/>
@@ -16,12 +16,26 @@ refer to the variable documentation.
16
16
 
17
17
  * Add the following to your Rakefile:
18
18
 
19
- require 'vlad'
20
- Vlad.load 'config/deploy.rb'
19
+ begin
20
+ require 'vlad'
21
+ Vlad.load
22
+ rescue LoadError
23
+ # do nothing
24
+ end
21
25
 
22
- * <tt>rake vlad:setup</tt>
26
+ Vlad.load has a lot of flexibility. See the rdoc for full information.
23
27
 
24
- === Launch
28
+ You don't need the begin/rescue/end block if you ensure that Vlad is
29
+ installed on all your servers. To be lazy, you can install vlad via:
30
+
31
+ % rake vlad:invoke COMMAND='sudo gem install vlad -y'
32
+
33
+ === Initial Launch
34
+
35
+ * Run <tt>rake vlad:setup vlad:update vlad:migrate vlad:start</tt>
36
+
37
+ === Subsequent Updates:
25
38
 
26
39
  * <tt>rake vlad:update vlad:migrate vlad:start</tt>
27
40
 
41
+ Each step may be run separately.
data/doco/migration.txt CHANGED
@@ -1,5 +1,6 @@
1
1
  == Converting from Capistrano
2
2
 
3
+ * 'set scm' is removed. Vlad.load :scm => :something if you don't use subversion.
3
4
  * 'task' blocks are renamed to 'remote_task'.
4
5
  * Most variables are the same. See variables.txt for details.
5
6
  * No +with_command+ / +sudo+ / +via+ wonkiness
@@ -19,3 +20,24 @@
19
20
  - host "www.example.com", :app, :web, :db
20
21
  specifies a host with three roles.
21
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"
data/doco/variables.txt CHANGED
@@ -1,7 +1,6 @@
1
1
 
2
- == Variables
2
+ == Core Variables
3
3
 
4
- application:: REQUIRED: Name of your application. e.g. hitsquad
5
4
  repository:: REQUIRED: Repository path: e.g. http://repo.example.com/svn
6
5
  deploy_to:: REQUIRED: Deploy path on target machines. e.g. /var/www/app
7
6
  domain:: REQUIRED: Used for the common case of a single target
@@ -32,13 +31,46 @@ releases:: An array of all existing releases, oldest first.
32
31
  Defaults to latest release directory name.
33
32
  releases_path:: Full path to the 'releases' directory on the remote host.
34
33
  Defaults to "#{deploy_to}/releases".
35
- scm:: Which SCM module to use. Valid options are :subversion
36
- and :perforce as of 1.0. Defaults to "subversion"
34
+ rsync_cmd:: Path to rsync command. Defaults to "rsync".
35
+ rsync_flags:: Flags for rsync. Defaults to ['-azP', '--delete'].
37
36
  scm_path:: Path on the remote host that will be used as 'working
38
37
  space' for SCM tasks. Defaults to "#{deploy_to}/scm".
39
- sudo_password:: Asks for password when referenced.
40
- source:: Read-Only: An SCM worker instance defined by scm.
41
38
  shared_path:: Full path to remote 'shared' directory, symlinked into
42
39
  your app by default. Defaults to "#{deploy_to}/shared".
40
+ ssh_cmd:: Path to ssh. Defaults to "ssh".
41
+ ssh_flags:: Flags for ssh. Defaults to "".
42
+ sudo_cmd:: Path to sudo command. Defaults to "sudo".
43
+ sudo_flags:: Flogs for sudo. Defaults to nil.
44
+ sudo_password:: Asks for password when referenced.
45
+
46
+ == Apache Web Variables:
47
+
43
48
  web_command:: Command to execute when controlling the web server.
44
49
  Defaults to "apachectl".
50
+
51
+ == Mongrel App Variables:
52
+
53
+ mongrel_address:: Defaults to "127.0.0.1"
54
+ mongrel_clean:: Defaults to false
55
+ mongrel_command:: Defaults to 'mongrel_rails'
56
+ mongrel_conf:: Defaults to "#{shared_path}/mongrel_cluster.conf"
57
+ mongrel_config_script:: Defaults to nil
58
+ mongrel_environment:: Defaults to "production"
59
+ mongrel_group:: Defaults to nil
60
+ mongrel_log_file:: Defaults to nil
61
+ mongrel_pid_file:: Defaults to nil
62
+ mongrel_port:: Defaults to 8000
63
+ mongrel_prefix:: Defaults to nil
64
+ mongrel_servers:: Defaults to 2
65
+ mongrel_user:: Defaults to nil
66
+
67
+ == Perforce SCM Variables:
68
+
69
+ p4_cmd:: The perforce command to use. Defaults to "p4"
70
+ source:: A perforce SCM worker instance.
71
+
72
+ == Subversion SCM Variables:
73
+
74
+ source:: A subversion SCM worker instance.
75
+ svn_cmd:: The subversion command to use. Defaults to "svn"
76
+
@@ -1,10 +1,91 @@
1
1
  require 'rubygems'
2
2
  require 'open4'
3
+ require 'rake'
3
4
  require 'vlad'
4
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
+
5
34
  ##
6
- # Rake::RemoteTask is a subclass of Rake::Task that adds remote_actions that
7
- # execute in parallel on multiple hosts via ssh.
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
+ # Declare a Vlad task that will execute on all hosts by default. To
43
+ # limit that task to specific roles, use:
44
+ #
45
+ # remote_task :example, :roles => [:app, :web] do
46
+ def remote_task name, options = {}, &b
47
+ Rake::RemoteTask.remote_task name, options, &b
48
+ end
49
+
50
+ ##
51
+ # Declare a role and assign a remote host to it. Equivalent to the
52
+ # <tt>host</tt> method; provided for capistrano compatibility.
53
+ def role role_name, host, args = {}
54
+ Rake::RemoteTask.role role_name, host, args
55
+ end
56
+
57
+ ##
58
+ # Execute the given command on the <tt>target_host</tt> for the
59
+ # current task.
60
+ def run *args, &b
61
+ Thread.current[:task].run(*args, &b)
62
+ end
63
+
64
+ # rsync the given files to <tt>target_host</tt>.
65
+ def rsync local, remote
66
+ Thread.current[:task].rsync local, remote
67
+ end
68
+
69
+ # Declare a variable called +name+ and assign it a value. A
70
+ # globally-visible method with the name of the variable is defined.
71
+ # If a block is given, it will be called when the variable is first
72
+ # accessed. Subsequent references to the variable will always return
73
+ # the same value. Raises <tt>ArgumentError</tt> if the +name+ would
74
+ # conflict with an existing method.
75
+ def set name, val = nil, &b
76
+ Rake::RemoteTask.set name, val, &b
77
+ end
78
+
79
+ # Returns the name of the host that the current task is executing on.
80
+ # <tt>target_host</tt> can uniquely identify a particular task/host
81
+ # combination.
82
+ def target_host
83
+ Thread.current[:task].target_host
84
+ end
85
+
86
+ ##
87
+ # Rake::RemoteTask is a subclass of Rake::Task that adds
88
+ # remote_actions that execute in parallel on multiple hosts via ssh.
8
89
 
9
90
  class Rake::RemoteTask < Rake::Task
10
91
 
@@ -21,8 +102,8 @@ class Rake::RemoteTask < Rake::Task
21
102
  attr_accessor :target_host
22
103
 
23
104
  ##
24
- # An Array of Actions this host will perform during execution. Use enhance
25
- # to add new actions to a task.
105
+ # An Array of Actions this host will perform during execution. Use
106
+ # enhance to add new actions to a task.
26
107
 
27
108
  attr_reader :remote_actions
28
109
 
@@ -35,12 +116,12 @@ class Rake::RemoteTask < Rake::Task
35
116
  end
36
117
 
37
118
  ##
38
- # Add a local action to this task. This calls Rake::Task#enhance.
119
+ # Add a local action to this task. This calls Rake::Task#enhance.
39
120
 
40
121
  alias_method :original_enhance, :enhance
41
122
 
42
123
  ##
43
- # Add remote action +block+ to this task with dependencies +deps+. See
124
+ # Add remote action +block+ to this task with dependencies +deps+. See
44
125
  # Rake::Task#enhance.
45
126
 
46
127
  def enhance(deps=nil, &block)
@@ -50,12 +131,14 @@ class Rake::RemoteTask < Rake::Task
50
131
  end
51
132
 
52
133
  ##
53
- # Execute this action. Local actions will be performed first, then remote
134
+ # Execute this action. Local actions will be performed first, then remote
54
135
  # actions will be performed in parallel on each host configured for this
55
136
  # RemoteTask.
56
137
 
57
138
  def execute
58
- raise Vlad::ConfigurationError, "No target hosts specified for task: #{self.name}" if target_hosts.empty?
139
+ raise(Vlad::ConfigurationError,
140
+ "No target hosts specified for task: #{self.name}") if
141
+ target_hosts.empty?
59
142
  super
60
143
  @remote_actions.each { |act| act.execute(target_hosts) }
61
144
  end
@@ -64,7 +147,7 @@ class Rake::RemoteTask < Rake::Task
64
147
  # Use rsync to send +local+ to +remote+ on target_host.
65
148
 
66
149
  def rsync local, remote
67
- cmd = ['rsync', '-azP', '--delete', local, "#{@target_host}:#{remote}"]
150
+ cmd = [rsync_cmd, rsync_flags, local, "#{@target_host}:#{remote}"].flatten.compact
68
151
 
69
152
  success = system(*cmd)
70
153
 
@@ -74,81 +157,87 @@ class Rake::RemoteTask < Rake::Task
74
157
  end
75
158
 
76
159
  ##
77
- # Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
160
+ # Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
78
161
  # sudo password will be prompted for then saved for subsequent sudo commands.
79
162
 
80
163
  def run command
81
- cmd = ["ssh", target_host, command]
164
+ cmd = [ssh_cmd, ssh_flags, target_host, command].compact
82
165
  result = []
83
166
 
84
- puts cmd.join(' ') if Rake.application.options.trace
167
+ warn cmd.join(' ') if $TRACE
168
+
169
+ pid, inn, out, err = popen4(*cmd)
85
170
 
86
- status = popen4(*cmd) do |pid, inn, out, err|
87
- inn.sync = true
171
+ inn.sync = true
172
+ streams = [out, err]
173
+ out_stream = {
174
+ out => $stdout,
175
+ err => $stderr,
176
+ }
88
177
 
89
- until out.eof? and err.eof? do
90
- unless err.eof? then
91
- data = err.readpartial(1024)
92
- result << data
93
- $stderr.write data
178
+ # Handle process termination ourselves
179
+ status = nil
180
+ Thread.start do
181
+ status = Process.waitpid2(pid).last
182
+ end
94
183
 
95
- if data =~ /^Password:/ then
96
- inn.puts sudo_password
97
- result << "\n"
98
- $stderr.write "\n"
99
- end
184
+ until streams.empty? do
185
+ # don't busy loop
186
+ selected, = select streams, nil, nil, 0.1
187
+ next if selected.nil? or selected.empty?
188
+
189
+ selected.each do |stream|
190
+ if stream.eof? then
191
+ streams.delete stream if status # we've quit, so no more writing
192
+ next
100
193
  end
101
194
 
102
- unless out.eof? then
103
- data = out.readpartial(1024)
104
- result << data
105
- $stdout.write data
195
+ data = stream.readpartial(1024)
196
+ out_stream[stream].write data
197
+
198
+ if stream == err and data =~ /^Password:/ then
199
+ inn.puts sudo_password
200
+ data << "\n"
201
+ $stderr.write "\n"
106
202
  end
203
+
204
+ result << data
107
205
  end
108
206
  end
109
207
 
110
208
  unless status.success? then
111
- raise Vlad::CommandFailedError, "execution failed with status #{status.exitstatus}: #{cmd.join ' '}"
209
+ raise(Vlad::CommandFailedError,
210
+ "execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
112
211
  end
113
212
 
114
213
  result.join
115
214
  end
116
215
 
117
216
  ##
118
- # Execute +command+ under sudo using run.
217
+ # Returns an Array with every host configured.
119
218
 
120
- def sudo command
121
- run "sudo #{command}"
219
+ def self.all_hosts
220
+ hosts_for(roles.keys)
122
221
  end
123
222
 
124
223
  ##
125
- # The hosts this task will execute on. The hosts are determined from the
126
- # role this task belongs to.
127
- #
128
- # The target hosts may be overridden by providing a comma-separated list of
129
- # commands to the HOSTS environment variable:
130
- #
131
- # rake my_task HOSTS=app1.example.com,app2.example.com
224
+ # The default environment values. Used for resetting (mostly for
225
+ # tests).
132
226
 
133
- def target_hosts
134
- if hosts = ENV["HOSTS"] then
135
- hosts.strip.gsub(/\s+/, '').split(",")
136
- else
137
- roles = options[:roles]
138
- roles ? Rake::RemoteTask.hosts_for(roles) : Rake::RemoteTask.all_hosts
139
- end
227
+ def self.default_env
228
+ @@default_env
140
229
  end
141
230
 
142
231
  ##
143
- # Returns an Array with every host configured.
232
+ # The vlad environment.
144
233
 
145
- def self.all_hosts
146
- hosts_for(roles.keys)
234
+ def self.env
235
+ @@env
147
236
  end
148
237
 
149
238
  ##
150
- # Fetches environment variable +name+ from the environment using default
151
- # +default+.
239
+ # Fetches environment variable +name+ from the environment using
240
+ # default +default+.
152
241
 
153
242
  def self.fetch name, default = nil
154
243
  name = name.to_s if Symbol === name
@@ -166,8 +255,8 @@ class Rake::RemoteTask < Rake::Task
166
255
  end
167
256
 
168
257
  ##
169
- # Add host +host_name+ that belongs to +roles+. Extra arguments may be
170
- # specified for the host as a hash as the last argument.
258
+ # Add host +host_name+ that belongs to +roles+. Extra arguments may
259
+ # be specified for the host as a hash as the last argument.
171
260
  #
172
261
  # host is the inversion of role:
173
262
  #
@@ -195,15 +284,32 @@ class Rake::RemoteTask < Rake::Task
195
284
  }.flatten.uniq.sort
196
285
  end
197
286
 
287
+ def self.mandatory name, desc # :nodoc:
288
+ self.set(name) do
289
+ raise(Vlad::ConfigurationError,
290
+ "Please specify the #{desc} via the #{name.inspect} variable")
291
+ end
292
+ end
293
+
198
294
  ##
199
295
  # Ensures exclusive access to +name+.
200
296
 
201
297
  def self.protect_env name # :nodoc:
202
- @@env_locks[name.to_s].synchronize do
298
+ @@env_locks[name].synchronize do
203
299
  yield
204
300
  end
205
301
  end
206
302
 
303
+ ##
304
+ # Adds a remote task named +name+ with options +options+ that will
305
+ # execute +block+.
306
+
307
+ def self.remote_task name, options = {}, &block
308
+ t = Rake::RemoteTask.define_task(name, &block)
309
+ t.options = options
310
+ t
311
+ end
312
+
207
313
  ##
208
314
  # Ensures +name+ does not conflict with an existing method.
209
315
 
@@ -212,10 +318,31 @@ class Rake::RemoteTask < Rake::Task
212
318
  end
213
319
 
214
320
  ##
215
- # The Rake::RemoteTask executing in this Thread.
321
+ # Resets vlad, restoring all roles, tasks and environment variables
322
+ # to the defaults.
216
323
 
217
- def self.task
218
- Thread.current[:task]
324
+ def self.reset
325
+ @@roles = Hash.new { |h,k| h[k] = {} }
326
+ @@env = {}
327
+ @@tasks = {}
328
+ @@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
329
+
330
+ @@default_env.each do |k,v|
331
+ case v
332
+ when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
333
+ @@env[k] = v
334
+ else
335
+ @@env[k] = v.dup
336
+ end
337
+ end
338
+ end
339
+
340
+ ##
341
+ # Adds role +role_name+ with +host+ and +args+ for that host.
342
+
343
+ def self.role role_name, host, args = {}
344
+ raise ArgumentError, "invalid host" if host.nil? or host.empty?
345
+ @@roles[role_name][host] = args
219
346
  end
220
347
 
221
348
  ##
@@ -228,58 +355,63 @@ class Rake::RemoteTask < Rake::Task
228
355
  end
229
356
 
230
357
  ##
231
- # The configured Rake::RemoteTasks.
358
+ # Set environment variable +name+ to +value+ or +default_block+.
359
+ #
360
+ # If +default_block+ is defined, the block will be executed the
361
+ # first time the variable is fetched, and the value will be used for
362
+ # every subsequent fetch.
232
363
 
233
- def self.tasks
234
- @@tasks
235
- end
364
+ def self.set name, value = nil, &default_block
365
+ raise ArgumentError, "cannot provide both a value and a block" if
366
+ value and default_block
367
+ raise ArgumentError, "cannot set reserved name: '#{name}'" if
368
+ Rake::RemoteTask.reserved_name?(name) unless $TESTING
236
369
 
237
- ##
238
- # The vlad environment.
370
+ Rake::RemoteTask.default_env[name.to_s] = Rake::RemoteTask.env[name.to_s] =
371
+ value || default_block
239
372
 
240
- def self.env
241
- @@env
373
+ Object.send :define_method, name do
374
+ Rake::RemoteTask.fetch name
375
+ end
242
376
  end
243
377
 
244
378
  ##
245
- # Resets vlad, restoring all roles, tasks and environment variables to the
246
- # defaults.
247
-
248
- def self.reset
249
- @@roles = Hash.new { |h,k| h[k] = {} }
250
- @@env = {}
251
- @@tasks = {}
252
- @@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
253
-
254
- # mandatory
255
- set(:application) { raise(Vlad::ConfigurationError,
256
- "Please specify the name of the application") }
257
- set(:repository) { raise(Vlad::ConfigurationError,
258
- "Please specify the repository path") }
259
- set(:deploy_to) { raise(Vlad::ConfigurationError,
260
- "Please specify the deploy path") }
261
- set(:domain) { raise(Vlad::ConfigurationError,
262
- "Please specify the server domain") }
263
-
264
- # optional
265
- set(:current_path) { File.join(deploy_to, "current") }
266
- set(:current_release) { File.join(releases_path, releases[-1]) }
267
- set :keep_releases, 5
268
- set :deploy_timestamped, true
269
- set :deploy_via, :export
270
- set(:latest_release) { deploy_timestamped ? release_path : current_release }
271
- set :migrate_args, ""
272
- set :migrate_target, :latest
273
- set(:previous_release){ File.join(releases_path, releases[-2]) }
274
- set :rails_env, "production"
275
- set :rake, "rake"
276
- set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
277
- set(:release_path) { File.join(releases_path, release_name) }
278
- set(:releases) { task.run("ls -x #{releases_path}").split.sort }
279
- set(:releases_path) { File.join(deploy_to, "releases") }
280
- set :scm, :subversion
281
- set(:scm_path) { File.join(deploy_to, "scm") }
282
- set(:shared_path) { File.join(deploy_to, "shared") }
379
+ # Sets all the default values. Should only be called once. Use reset
380
+ # if you need to restore values.
381
+
382
+ def self.set_defaults
383
+ @@default_env ||= {}
384
+ self.reset
385
+
386
+ mandatory :repository, "repository path"
387
+ mandatory :deploy_to, "deploy path"
388
+ mandatory :domain, "server domain"
389
+
390
+ simple_set(:deploy_timestamped, true,
391
+ :deploy_via, :export,
392
+ :keep_releases, 5,
393
+ :migrate_args, "",
394
+ :migrate_target, :latest,
395
+ :rails_env, "production",
396
+ :rake_cmd, "rake",
397
+ :rsync_cmd, "rsync",
398
+ :rsync_flags, ['-azP', '--delete'],
399
+ :ssh_cmd, "ssh",
400
+ :ssh_flags, nil,
401
+ :sudo_cmd, "sudo",
402
+ :sudo_flags, nil)
403
+
404
+ set(:current_release) { File.join(releases_path, releases[-1]) }
405
+ set(:latest_release) { deploy_timestamped ?release_path: current_release }
406
+ set(:previous_release) { File.join(releases_path, releases[-2]) }
407
+ set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
408
+ set(:release_path) { File.join(releases_path, release_name) }
409
+ set(:releases) { task.run("ls -x #{releases_path}").split.sort }
410
+
411
+ set_path :current_path, "current"
412
+ set_path :releases_path, "releases"
413
+ set_path :scm_path, "scm"
414
+ set_path :shared_path, "shared"
283
415
 
284
416
  set(:sudo_password) do
285
417
  state = `stty -g`
@@ -297,55 +429,62 @@ class Rake::RemoteTask < Rake::Task
297
429
  end
298
430
  sudo_password
299
431
  end
432
+ end
300
433
 
301
- set(:source) do
302
- require "vlad/#{scm}"
303
- Vlad.const_get(scm.to_s.capitalize).new
434
+ def self.set_path(name, subdir) # :nodoc:
435
+ set(name) { File.join(deploy_to, subdir) }
436
+ end
437
+
438
+ def self.simple_set(*args) # :nodoc:
439
+ args = Hash[*args]
440
+ args.each do |k, v|
441
+ set k, v
304
442
  end
305
443
  end
306
444
 
307
445
  ##
308
- # Adds role +role_name+ with +host+ and +args+ for that host.
446
+ # The Rake::RemoteTask executing in this Thread.
309
447
 
310
- def self.role role_name, host, args = {}
311
- raise ArgumentError, "invalid host" if host.nil? or host.empty?
312
- @@roles[role_name][host] = args
448
+ def self.task
449
+ Thread.current[:task]
313
450
  end
314
451
 
315
452
  ##
316
- # Adds a remote task named +name+ with options +options+ that will execute
317
- # +block+.
453
+ # The configured Rake::RemoteTasks.
318
454
 
319
- def self.remote_task name, options = {}, &block
320
- t = Rake::RemoteTask.define_task(name, &block)
321
- t.options = options
322
- roles = options[:roles]
323
- t
455
+ def self.tasks
456
+ @@tasks
324
457
  end
325
458
 
326
459
  ##
327
- # Set environment variable +name+ to +value+ or +default_block+.
328
- #
329
- # If +default_block+ is defined, the block will be executed the first time
330
- # the variable is fetched, and the value will be used for every subsequent
331
- # fetch.
460
+ # Execute +command+ under sudo using run.
332
461
 
333
- def self.set name, value = nil, &default_block
334
- raise ArgumentError, "cannot provide both a value and a block" if
335
- value and default_block
336
- raise ArgumentError, "cannot set reserved name: '#{name}'" if
337
- Rake::RemoteTask.reserved_name?(name)
462
+ def sudo command
463
+ run [sudo_cmd, sudo_flags, command].compact.join(" ")
464
+ end
338
465
 
339
- Rake::RemoteTask.env[name.to_s] = value || default_block
466
+ ##
467
+ # The hosts this task will execute on. The hosts are determined from
468
+ # the role this task belongs to.
469
+ #
470
+ # The target hosts may be overridden by providing a comma-separated
471
+ # list of commands to the HOSTS environment variable:
472
+ #
473
+ # rake my_task HOSTS=app1.example.com,app2.example.com
340
474
 
341
- Object.send :define_method, name do
342
- Rake::RemoteTask.fetch name
475
+ def target_hosts
476
+ if hosts = ENV["HOSTS"] then
477
+ hosts.strip.gsub(/\s+/, '').split(",")
478
+ else
479
+ roles = options[:roles]
480
+ roles ? Rake::RemoteTask.hosts_for(roles) : Rake::RemoteTask.all_hosts
343
481
  end
344
482
  end
345
483
 
346
484
  ##
347
- # Action is used to run a task's remote_actions in parallel on each of its
348
- # hosts. Actions are created automatically in Rake::RemoteTask#enhance.
485
+ # Action is used to run a task's remote_actions in parallel on each
486
+ # of its hosts. Actions are created automatically in
487
+ # Rake::RemoteTask#enhance.
349
488
 
350
489
  class Action
351
490
 
@@ -379,8 +518,8 @@ class Rake::RemoteTask < Rake::Task
379
518
  end
380
519
 
381
520
  ##
382
- # Execute this action on +hosts+ in parallel. Returns when block has
383
- # completed for each host.
521
+ # Execute this action on +hosts+ in parallel. Returns when block
522
+ # has completed for each host.
384
523
 
385
524
  def execute hosts
386
525
  hosts.each do |host|
@@ -397,3 +536,4 @@ class Rake::RemoteTask < Rake::Task
397
536
  end
398
537
  end
399
538
 
539
+ Rake::RemoteTask.set_defaults