vlad 1.0.0 → 1.1.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.
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