lastobelus-vlad 1.4.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/.autotest +24 -0
- data/History.txt +162 -0
- data/Manifest.txt +38 -0
- data/README.txt +78 -0
- data/Rakefile +44 -0
- data/considerations.txt +91 -0
- data/doco/deploying-merb-with-vlad.txt +155 -0
- data/doco/deploying-sinatra-with-vlad.txt +119 -0
- data/doco/faq.txt +131 -0
- data/doco/getting_started.txt +61 -0
- data/doco/migration.txt +43 -0
- data/doco/perforce.txt +5 -0
- data/doco/variables.txt +79 -0
- data/lib/rake_remote_task.rb +589 -0
- data/lib/vlad.rb +91 -0
- data/lib/vlad/apache.rb +37 -0
- data/lib/vlad/core.rb +181 -0
- data/lib/vlad/darcs.rb +24 -0
- data/lib/vlad/git.rb +49 -0
- data/lib/vlad/god.rb +23 -0
- data/lib/vlad/lighttpd.rb +85 -0
- data/lib/vlad/maintenance.rb +20 -0
- data/lib/vlad/merb.rb +51 -0
- data/lib/vlad/mercurial.rb +37 -0
- data/lib/vlad/mongrel.rb +62 -0
- data/lib/vlad/nginx.rb +48 -0
- data/lib/vlad/passenger.rb +8 -0
- data/lib/vlad/perforce.rb +117 -0
- data/lib/vlad/subversion.rb +35 -0
- data/lib/vlad/thin.rb +63 -0
- data/test/test_rake_remote_task.rb +257 -0
- data/test/test_vlad.rb +210 -0
- data/test/test_vlad_git.rb +41 -0
- data/test/test_vlad_mercurial.rb +31 -0
- data/test/test_vlad_perforce.rb +37 -0
- data/test/test_vlad_subversion.rb +27 -0
- data/test/vlad_test_case.rb +72 -0
- data/vladdemo.sh +97 -0
- metadata +141 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
# Deploying Sinatra with Vlad
|
2
|
+
|
3
|
+
This tutorial has been adapted from [Deploying Sinatra with Vlad](http://effectif.com/articles/deploying-sinatra-with-vlad) by [Graham Ashton](http://effectif.com "Effectif Development").
|
4
|
+
|
5
|
+
So you've just written a nice new [Sinatra application](http://www.sinatrarb.com/ "Sinatra"), and you want to get it running on your web server. How hard can it be? Well with [Vlad the Deployer](http://rubyhitsquad.com/Vlad_the_Deployer.html "Vlad the Deployer"), it's actually rather easy.
|
6
|
+
|
7
|
+
## Creating a sample application
|
8
|
+
|
9
|
+
Let's start by making ourselves a test app:
|
10
|
+
|
11
|
+
$ mkdir hello
|
12
|
+
$ cd hello
|
13
|
+
$ touch app.rb
|
14
|
+
|
15
|
+
Open `app.rb` in your editor and put this code in it:
|
16
|
+
|
17
|
+
require "rubygems"
|
18
|
+
require "sinatra"
|
19
|
+
|
20
|
+
get "/" do
|
21
|
+
"Hello!"
|
22
|
+
end
|
23
|
+
|
24
|
+
We can check that the app works locally by running it...
|
25
|
+
|
26
|
+
$ ruby app.rb
|
27
|
+
|
28
|
+
...and then opening [http://localhost:4567](http://localhost:4567) in a web browser.
|
29
|
+
|
30
|
+
We need to create a `public` directory too, as Vlad assumes that we have a `public` directory for our static assets. I'm also going to make an empty CSS file so that the directory doesn't get ignored by Git:
|
31
|
+
|
32
|
+
$ mkdir public
|
33
|
+
$ touch public/master.css
|
34
|
+
|
35
|
+
We'll deploy our application from version control. I'm using Git, but you can use any system that Vlad supports; just check your files into a repository that will be accessible from your web server.
|
36
|
+
|
37
|
+
## Configuring Vlad
|
38
|
+
|
39
|
+
Okay, we're ready for Vlad. It's a Ruby gem, so it's very easy to install:
|
40
|
+
|
41
|
+
$ sudo gem install vlad
|
42
|
+
Successfully installed vlad-1.2.0
|
43
|
+
1 gem installed
|
44
|
+
Installing ri documentation for vlad-1.2.0...
|
45
|
+
Installing RDoc documentation for vlad-1.2.0...
|
46
|
+
|
47
|
+
There's no need to install Vlad on your server, just your workstation.
|
48
|
+
|
49
|
+
You access Vlad's functionality through Rake tasks. This means that we need a `Rakefile` which loads the Vlad code. Create `Rakefile` in the same directory as `app.rb`, then add the following code to it:
|
50
|
+
|
51
|
+
begin
|
52
|
+
require "vlad"
|
53
|
+
Vlad.load(:app => nil, :scm => "git")
|
54
|
+
rescue LoadError
|
55
|
+
# do nothing
|
56
|
+
end
|
57
|
+
|
58
|
+
Note that we've told Vlad that we intend to use Git (subversion is the default). We've set `:app` to `nil` as Vlad assumes that we'll run our application with [Mongrel](http://mongrel.rubyforge.org/ "Mongrel - Trac"). I'm not going to use Mongrel here, so we don't want Vlad to load its Mongrel recipes.
|
59
|
+
|
60
|
+
If you run `rake -T` now you should see a bunch of vlad tasks that are available to you. You can't run them yet; you need to configure Vlad with a `config/deploy.rb` file:
|
61
|
+
|
62
|
+
$ mkdir config
|
63
|
+
$ touch config/deploy.rb
|
64
|
+
|
65
|
+
Open `deploy.rb` in your editor and set the following variables:
|
66
|
+
|
67
|
+
set :application, "hello"
|
68
|
+
set :repository, "ssh://your.git.server/path/to/project/hello.git"
|
69
|
+
set :domain, "your.web.server"
|
70
|
+
set :deploy_to, "/var/apps/#{application}"
|
71
|
+
|
72
|
+
Make sure that `:repository` correctly references your source control system, and that `:domain` is set to the hostname of your server.
|
73
|
+
|
74
|
+
I won't be able to create any directories under the `/var/apps` directory (I'm going to run vlad using my own username in this example), so I need to login to my server and make sure that I can create files in the `hello` directory:
|
75
|
+
|
76
|
+
$ ssh your.web.server
|
77
|
+
$ sudo mkdir -p /var/apps/hello
|
78
|
+
$ sudo chown yourusername /var/apps/hello
|
79
|
+
|
80
|
+
Now you can try running Vlad, to create all the directories necessary to serve your project. Back on your workstation, type:
|
81
|
+
|
82
|
+
$ rake vlad:setup
|
83
|
+
|
84
|
+
You should find that some directories have been created within `/var/apps/hello` on your server.
|
85
|
+
|
86
|
+
Let's trying deploying some code:
|
87
|
+
|
88
|
+
$ rake vlad:update
|
89
|
+
(in /Users/graham/data/effectif/projects/hello)
|
90
|
+
Initialized empty Git repository in /var/apps/hello/scm/repo/.git/
|
91
|
+
Switched to a new branch "deployed-HEAD"
|
92
|
+
|
93
|
+
You should now find that if you ssh into your server that you can run the application:
|
94
|
+
|
95
|
+
$ ssh your.web.server
|
96
|
+
$ cd /var/apps/hello/current
|
97
|
+
$ ruby app.rb
|
98
|
+
|
99
|
+
Try making a change to your source, committing it to your repository, then run `vlad:update` again. Your code will be updated. If you restart Sinatra in the new directory you'll see your changes in the browser.
|
100
|
+
|
101
|
+
If you're following along with these commands, be careful that you're running `app.rb` in the freshly deployed directory. `current` is a symlink to a specific release directory, so you'll need to leave the directory and return to it to see the new source code (i.e. symlinks don't get updated under your shell's feet). This should do it:
|
102
|
+
|
103
|
+
$ cd ~ && cd -
|
104
|
+
$ ruby app.rb
|
105
|
+
|
106
|
+
You may now be wondering how to get Thin running automatically, and how to re-start it when you run `vlad:update`. That should be the subject of my next blog post (you can [subscribe](/articles.xml) if you need it).
|
107
|
+
|
108
|
+
## Deploying from a Git branch
|
109
|
+
|
110
|
+
If you want to deploy from a specific Git branch (`master` is the default) you can set the `:revision` variable in `deploy.rb`:
|
111
|
+
|
112
|
+
set :revision, "origin/mybranch"
|
113
|
+
|
114
|
+
## Deploying as a different user
|
115
|
+
|
116
|
+
It's not a great idea to deploy and run applications as your own login name (it's better practice to run web applications as users that don't have many privileges). I've not really addressed users in this article in order to focus on the basics of Vlad, but if you're interested you can deploy as a different user with these settings in `deploy.rb`:
|
117
|
+
|
118
|
+
set :user, "deploy"
|
119
|
+
set :domain, "#{user}@domain.com"
|
data/doco/faq.txt
ADDED
@@ -0,0 +1,131 @@
|
|
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 is there no vlad:deploy?
|
9
|
+
=== A: Because everyone is a unique beautiful flower.
|
10
|
+
|
11
|
+
Everyone's deployment is different. Everyone. Unique scaling
|
12
|
+
requirements. Yadda yadda yadda. So rather than supply something that
|
13
|
+
nobody will use, we decided not to supply anything at all. Here is an
|
14
|
+
example deploy that I stole from the web (and improved) that you may like:
|
15
|
+
|
16
|
+
desc "Full deployment cycle"
|
17
|
+
task "vlad:deploy" => %w[
|
18
|
+
vlad:update
|
19
|
+
vlad:migrate
|
20
|
+
vlad:reset_session
|
21
|
+
vlad:start
|
22
|
+
vlad:cleanup
|
23
|
+
]
|
24
|
+
|
25
|
+
Just pop that in your config/deploy.rb, tweak it as necessary, and have at it.
|
26
|
+
|
27
|
+
=== Q: Why are there no before_action and after_action hooks?
|
28
|
+
=== A: Because we use rake!
|
29
|
+
|
30
|
+
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...
|
31
|
+
|
32
|
+
To extend a task (adding something after), just define it again:
|
33
|
+
|
34
|
+
task :action1 do
|
35
|
+
puts "one fish, two fish"
|
36
|
+
end
|
37
|
+
|
38
|
+
task :action1 do
|
39
|
+
puts "red fish, blue fish"
|
40
|
+
end
|
41
|
+
|
42
|
+
To prepend on a task, add a dependency:
|
43
|
+
|
44
|
+
task :action2 do
|
45
|
+
puts "red fish, blue fish"
|
46
|
+
end
|
47
|
+
|
48
|
+
task :myaction do
|
49
|
+
puts "one fish, two fish"
|
50
|
+
end
|
51
|
+
|
52
|
+
task :action2 => :myaction
|
53
|
+
|
54
|
+
=== Q: How can I replace a rake task instead of just adding to it?
|
55
|
+
=== A: Use Rake.clear_tasks str_or_regexp
|
56
|
+
|
57
|
+
namespace :vlad do
|
58
|
+
# Clear existing update task so that we can redefine instead of adding to it.
|
59
|
+
Rake.clear_tasks('vlad:update')
|
60
|
+
|
61
|
+
remote_task :update, :roles => :app do
|
62
|
+
#custom update stuff
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
=== Q: How do I invoke another rule?
|
67
|
+
=== A: The easiest way is via dependencies.
|
68
|
+
|
69
|
+
task :shazam! => [:action1, :action2]
|
70
|
+
|
71
|
+
The other way is to look it up and call invoke:
|
72
|
+
|
73
|
+
task :shazam! do
|
74
|
+
Rake::Task[:action1].invoke
|
75
|
+
Rake::Task[:action2].invoke
|
76
|
+
end
|
77
|
+
|
78
|
+
(Or, cheat and call out to rake again: sh "rake action1")
|
79
|
+
|
80
|
+
== Using SSH
|
81
|
+
|
82
|
+
=== Q: Is there any way to set the ssh user?
|
83
|
+
=== A: Yes, using ~/.ssh/config
|
84
|
+
|
85
|
+
Host example.com
|
86
|
+
User fluffy_bunny
|
87
|
+
|
88
|
+
OR: Alternatively, you can do this within your recipes like so:
|
89
|
+
|
90
|
+
set :user, "fluffy_bunny"
|
91
|
+
set :domain, "#{user}@example.com"
|
92
|
+
|
93
|
+
=== Q: Is there any way to speed up ssh connections?
|
94
|
+
=== A: Yes, add to your Host entry in ~/.ssh/config:
|
95
|
+
|
96
|
+
ControlMaster auto
|
97
|
+
ControlPath ~/.ssh/master-%r@%h:%p
|
98
|
+
|
99
|
+
=== Q: I'm tired of typing in my password!
|
100
|
+
=== A: Me too!
|
101
|
+
|
102
|
+
Put a password on your key, distribute your public key to the server and then use ssh-agent.
|
103
|
+
|
104
|
+
Check out this tiny tutorial at LBL: A brief ssh-agent tutorial <http://upc.lbl.gov/docs/user/sshagent.html>
|
105
|
+
|
106
|
+
If you're on a mac (on tiger, not leopard), use SSHKeychain, we love it. <http://www.sshkeychain.org/>. If you are on leopard, you get all of this for free.
|
107
|
+
|
108
|
+
=== Q: How do I use Vlad with a gateway?
|
109
|
+
=== A: Add the following to your deploy.rb variables:
|
110
|
+
|
111
|
+
set :ssh_flags, "-A #{mygateway}"
|
112
|
+
set :rsync_flags, "--rsh ssh -A #{mygateway} ssh"
|
113
|
+
|
114
|
+
=== Q: OMG subversion is stupid! It keeps asking for "Authentication Realm"
|
115
|
+
=== A: Yes, yes it is.
|
116
|
+
|
117
|
+
If you're seeing local checkouts work fine but they don't over ssh
|
118
|
+
(even to localhost!) then ssh into that machine (yes, even localhost)
|
119
|
+
and do a checkout there to a temporary directory. From then on,
|
120
|
+
checkout over ssh should work fine.
|
121
|
+
|
122
|
+
% svn co https://blah/blah /tmp/happy
|
123
|
+
... works fine ...
|
124
|
+
% ssh localhost svn co https://blah/blah /tmp/sad
|
125
|
+
... asks for authentication and then hangs ...
|
126
|
+
% ssh localhost
|
127
|
+
% svn co https://blah/blah /tmp/sad-no-happy
|
128
|
+
... asks for authentication ...
|
129
|
+
... works fine ...
|
130
|
+
% ssh localhost svn co https://blah/blah /tmp/happy2
|
131
|
+
... works fine ...
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
== Quick Start for a 1-Server Solution:
|
3
|
+
|
4
|
+
=== Setup
|
5
|
+
|
6
|
+
* Create a deploy file, usually in "config/deploy.rb":
|
7
|
+
|
8
|
+
set :application, "project"
|
9
|
+
set :domain, "example.com"
|
10
|
+
set :deploy_to, "/path/to/install"
|
11
|
+
set :repository, 'http://svn.example.com/project/branches/stable/'
|
12
|
+
|
13
|
+
This defaults to using 'svn export' from +repository+, and a single
|
14
|
+
server for +app+, +db+, and +www+. If you need to tweak these things,
|
15
|
+
refer to the variable documentation.
|
16
|
+
|
17
|
+
* If you want a multi-config environment, change your config like so:
|
18
|
+
|
19
|
+
set :application, "project"
|
20
|
+
set :repository, 'http://svn.example.com/project/branches/stable/'
|
21
|
+
|
22
|
+
task :beta do
|
23
|
+
set :domain, "beta.example.com"
|
24
|
+
set :deploy_to, "/path/to/install-beta"
|
25
|
+
end
|
26
|
+
|
27
|
+
task :dev do
|
28
|
+
set :domain, "dev.example.com"
|
29
|
+
set :deploy_to, "/path/to/install-dev"
|
30
|
+
end
|
31
|
+
|
32
|
+
task :prod do
|
33
|
+
set :domain, "example.com"
|
34
|
+
set :deploy_to, "/path/to/install"
|
35
|
+
end
|
36
|
+
|
37
|
+
* Add the following to your Rakefile:
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'vlad'
|
41
|
+
Vlad.load
|
42
|
+
rescue LoadError
|
43
|
+
# do nothing
|
44
|
+
end
|
45
|
+
|
46
|
+
Vlad.load has a lot of flexibility. See the rdoc for full information.
|
47
|
+
|
48
|
+
You don't need the begin/rescue/end block if you ensure that Vlad is
|
49
|
+
installed on all your servers. To be lazy, you can install vlad via:
|
50
|
+
|
51
|
+
% rake vlad:invoke COMMAND='sudo gem install vlad -y'
|
52
|
+
|
53
|
+
=== Initial Launch
|
54
|
+
|
55
|
+
* Run <tt>rake vlad:setup vlad:update vlad:migrate vlad:start</tt>
|
56
|
+
|
57
|
+
=== Subsequent Updates:
|
58
|
+
|
59
|
+
* <tt>rake vlad:update vlad:migrate vlad:start</tt>
|
60
|
+
|
61
|
+
Each step may be run separately.
|
data/doco/migration.txt
ADDED
@@ -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"
|
data/doco/perforce.txt
ADDED
data/doco/variables.txt
ADDED
@@ -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,589 @@
|
|
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, █ #{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
|
+
out_stream[stream].write data
|
179
|
+
|
180
|
+
if stream == err and data =~ sudo_prompt then
|
181
|
+
inn.puts sudo_password
|
182
|
+
data << "\n"
|
183
|
+
$stderr.write "\n"
|
184
|
+
end
|
185
|
+
|
186
|
+
result << data
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
unless status.success? then
|
191
|
+
raise(Vlad::CommandFailedError,
|
192
|
+
"execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
|
193
|
+
end
|
194
|
+
|
195
|
+
result.join
|
196
|
+
ensure
|
197
|
+
inn.close rescue nil
|
198
|
+
out.close rescue nil
|
199
|
+
err.close rescue nil
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Returns an Array with every host configured.
|
204
|
+
|
205
|
+
def self.all_hosts
|
206
|
+
hosts_for(roles.keys)
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# The default environment values. Used for resetting (mostly for
|
211
|
+
# tests).
|
212
|
+
|
213
|
+
def self.default_env
|
214
|
+
@@default_env
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.per_thread
|
218
|
+
@@per_thread
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# The vlad environment.
|
223
|
+
|
224
|
+
def self.env
|
225
|
+
@@env
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Fetches environment variable +name+ from the environment using
|
230
|
+
# default +default+.
|
231
|
+
|
232
|
+
def self.fetch name, default = nil
|
233
|
+
name = name.to_s if Symbol === name
|
234
|
+
if @@env.has_key? name then
|
235
|
+
protect_env(name) do
|
236
|
+
v = @@env[name]
|
237
|
+
v = @@env[name] = v.call if Proc === v unless per_thread[name]
|
238
|
+
v = v.call if Proc === v
|
239
|
+
v
|
240
|
+
end
|
241
|
+
elsif default || default == false
|
242
|
+
v = @@env[name] = default
|
243
|
+
else
|
244
|
+
raise Vlad::FetchError
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Add host +host_name+ that belongs to +roles+. Extra arguments may
|
250
|
+
# be specified for the host as a hash as the last argument.
|
251
|
+
#
|
252
|
+
# host is the inversion of role:
|
253
|
+
#
|
254
|
+
# host 'db1.example.com', :db, :master_db
|
255
|
+
#
|
256
|
+
# Is equivalent to:
|
257
|
+
#
|
258
|
+
# role :db, 'db1.example.com'
|
259
|
+
# role :master_db, 'db1.example.com'
|
260
|
+
|
261
|
+
def self.host host_name, *roles
|
262
|
+
opts = Hash === roles.last ? roles.pop : {}
|
263
|
+
|
264
|
+
roles.each do |role_name|
|
265
|
+
role role_name, host_name, opts.dup
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# Returns an Array of all hosts in +roles+.
|
271
|
+
|
272
|
+
def self.hosts_for *roles
|
273
|
+
roles.flatten.map { |r|
|
274
|
+
self.roles[r].keys
|
275
|
+
}.flatten.uniq.sort
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.mandatory name, desc # :nodoc:
|
279
|
+
self.set(name) do
|
280
|
+
raise(Vlad::ConfigurationError,
|
281
|
+
"Please specify the #{desc} via the #{name.inspect} variable")
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
##
|
286
|
+
# Ensures exclusive access to +name+.
|
287
|
+
|
288
|
+
def self.protect_env name # :nodoc:
|
289
|
+
@@env_locks[name].synchronize do
|
290
|
+
yield
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
##
|
295
|
+
# Adds a remote task named +name+ with options +options+ that will
|
296
|
+
# execute +block+.
|
297
|
+
|
298
|
+
def self.remote_task name, *args, &block
|
299
|
+
options = (Hash === args.last) ? args.pop : {}
|
300
|
+
t = Rake::RemoteTask.define_task(name, *args, &block)
|
301
|
+
options[:roles] = Array options[:roles]
|
302
|
+
options[:roles] |= @@current_roles
|
303
|
+
t.options = options
|
304
|
+
t
|
305
|
+
end
|
306
|
+
|
307
|
+
##
|
308
|
+
# Ensures +name+ does not conflict with an existing method.
|
309
|
+
|
310
|
+
def self.reserved_name? name # :nodoc:
|
311
|
+
!@@env.has_key?(name.to_s) && self.respond_to?(name)
|
312
|
+
end
|
313
|
+
|
314
|
+
##
|
315
|
+
# Resets vlad, restoring all roles, tasks and environment variables
|
316
|
+
# to the defaults.
|
317
|
+
|
318
|
+
def self.reset
|
319
|
+
@@def_role_hash = {} # official default role value
|
320
|
+
@@env = {}
|
321
|
+
@@tasks = {}
|
322
|
+
@@roles = Hash.new { |h,k| h[k] = @@def_role_hash }
|
323
|
+
@@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
|
324
|
+
|
325
|
+
@@default_env.each do |k,v|
|
326
|
+
case v
|
327
|
+
when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
|
328
|
+
@@env[k] = v
|
329
|
+
else
|
330
|
+
@@env[k] = v.dup
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
336
|
+
# Adds role +role_name+ with +host+ and +args+ for that host.
|
337
|
+
# TODO: merge:
|
338
|
+
# Declare a role and assign a remote host to it. Equivalent to the
|
339
|
+
# <tt>host</tt> method; provided for capistrano compatibility.
|
340
|
+
|
341
|
+
def self.role role_name, host = nil, args = {}
|
342
|
+
if block_given? then
|
343
|
+
raise ArgumentError, 'host not allowed with block' unless host.nil?
|
344
|
+
|
345
|
+
begin
|
346
|
+
current_roles << role_name
|
347
|
+
yield
|
348
|
+
ensure
|
349
|
+
current_roles.delete role_name
|
350
|
+
end
|
351
|
+
else
|
352
|
+
raise ArgumentError, 'host required' if host.nil?
|
353
|
+
|
354
|
+
[*host].each do |hst|
|
355
|
+
raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty?
|
356
|
+
end
|
357
|
+
@@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name]
|
358
|
+
@@roles[role_name][host] = args
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
##
|
363
|
+
# The configured roles.
|
364
|
+
|
365
|
+
def self.roles
|
366
|
+
host domain, :app, :web, :db if @@roles.empty?
|
367
|
+
|
368
|
+
@@roles
|
369
|
+
end
|
370
|
+
|
371
|
+
##
|
372
|
+
# Set environment variable +name+ to +value+ or +default_block+.
|
373
|
+
#
|
374
|
+
# If +default_block+ is defined, the block will be executed the
|
375
|
+
# first time the variable is fetched, and the value will be used for
|
376
|
+
# every subsequent fetch.
|
377
|
+
|
378
|
+
def self.set name, value = nil, &default_block
|
379
|
+
raise ArgumentError, "cannot provide both a value and a block" if
|
380
|
+
value and default_block unless
|
381
|
+
value == :per_thread
|
382
|
+
raise ArgumentError, "cannot set reserved name: '#{name}'" if
|
383
|
+
Rake::RemoteTask.reserved_name?(name) unless $TESTING
|
384
|
+
|
385
|
+
name = name.to_s
|
386
|
+
|
387
|
+
Rake::RemoteTask.per_thread[name] = true if
|
388
|
+
default_block && value == :per_thread
|
389
|
+
|
390
|
+
Rake::RemoteTask.default_env[name] = Rake::RemoteTask.env[name] =
|
391
|
+
default_block || value
|
392
|
+
|
393
|
+
Object.send :define_method, name do
|
394
|
+
Rake::RemoteTask.fetch name
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# Sets all the default values. Should only be called once. Use reset
|
400
|
+
# if you need to restore values.
|
401
|
+
|
402
|
+
def self.set_defaults
|
403
|
+
@@default_env ||= {}
|
404
|
+
@@per_thread ||= {}
|
405
|
+
self.reset
|
406
|
+
|
407
|
+
mandatory :repository, "repository path"
|
408
|
+
mandatory :deploy_to, "deploy path"
|
409
|
+
mandatory :domain, "server domain"
|
410
|
+
|
411
|
+
simple_set(:deploy_timestamped, true,
|
412
|
+
:deploy_via, :export,
|
413
|
+
:keep_releases, 5,
|
414
|
+
:migrate_args, "",
|
415
|
+
:migrate_target, :latest,
|
416
|
+
:rails_env, "production",
|
417
|
+
:rake_cmd, "rake",
|
418
|
+
:revision, "head",
|
419
|
+
:rsync_cmd, "rsync",
|
420
|
+
:rsync_flags, ['-azP', '--delete'],
|
421
|
+
:ssh_cmd, "ssh",
|
422
|
+
:ssh_flags, [],
|
423
|
+
:sudo_cmd, "sudo",
|
424
|
+
:sudo_flags, ['-p Password:'],
|
425
|
+
:sudo_prompt, /^Password:/,
|
426
|
+
:umask, '02')
|
427
|
+
|
428
|
+
set(:current_release) { File.join(releases_path, releases[-1]) }
|
429
|
+
set(:latest_release) { deploy_timestamped ?release_path: current_release }
|
430
|
+
set(:previous_release) { File.join(releases_path, releases[-2]) }
|
431
|
+
set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
|
432
|
+
set(:release_path) { File.join(releases_path, release_name) }
|
433
|
+
set(:releases) { task.run("ls -x #{releases_path}").split.sort }
|
434
|
+
|
435
|
+
set_path :current_path, "current"
|
436
|
+
set_path :releases_path, "releases"
|
437
|
+
set_path :scm_path, "scm"
|
438
|
+
set_path :shared_path, "shared"
|
439
|
+
|
440
|
+
set(:sudo_password) do
|
441
|
+
state = `stty -g`
|
442
|
+
|
443
|
+
raise Vlad::Error, "stty(1) not found" unless $?.success?
|
444
|
+
|
445
|
+
begin
|
446
|
+
system "stty -echo"
|
447
|
+
$stdout.print "sudo password: "
|
448
|
+
$stdout.flush
|
449
|
+
sudo_password = $stdin.gets
|
450
|
+
$stdout.puts
|
451
|
+
ensure
|
452
|
+
system "stty #{state}"
|
453
|
+
end
|
454
|
+
sudo_password
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def self.set_path(name, subdir) # :nodoc:
|
459
|
+
set(name) { File.join(deploy_to, subdir) }
|
460
|
+
end
|
461
|
+
|
462
|
+
def self.simple_set(*args) # :nodoc:
|
463
|
+
args = Hash[*args]
|
464
|
+
args.each do |k, v|
|
465
|
+
set k, v
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
##
|
470
|
+
# The Rake::RemoteTask executing in this Thread.
|
471
|
+
|
472
|
+
def self.task
|
473
|
+
Thread.current[:task]
|
474
|
+
end
|
475
|
+
|
476
|
+
##
|
477
|
+
# The configured Rake::RemoteTasks.
|
478
|
+
|
479
|
+
def self.tasks
|
480
|
+
@@tasks
|
481
|
+
end
|
482
|
+
|
483
|
+
##
|
484
|
+
# Execute +command+ under sudo using run.
|
485
|
+
|
486
|
+
def sudo command
|
487
|
+
run [sudo_cmd, sudo_flags, command].flatten.compact.join(" ")
|
488
|
+
end
|
489
|
+
|
490
|
+
##
|
491
|
+
# The hosts this task will execute on. The hosts are determined from
|
492
|
+
# the role this task belongs to.
|
493
|
+
#
|
494
|
+
# The target hosts may be overridden by providing a comma-separated
|
495
|
+
# list of commands to the HOSTS environment variable:
|
496
|
+
#
|
497
|
+
# rake my_task HOSTS=app1.example.com,app2.example.com
|
498
|
+
|
499
|
+
def target_hosts
|
500
|
+
if hosts = ENV["HOSTS"] then
|
501
|
+
hosts.strip.gsub(/\s+/, '').split(",")
|
502
|
+
else
|
503
|
+
roles = Array options[:roles]
|
504
|
+
|
505
|
+
if roles.empty? then
|
506
|
+
Rake::RemoteTask.all_hosts
|
507
|
+
else
|
508
|
+
Rake::RemoteTask.hosts_for roles
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
##
|
514
|
+
# Similar to target_hosts, but returns true if user defined any hosts, even
|
515
|
+
# an empty list.
|
516
|
+
|
517
|
+
def defined_target_hosts?
|
518
|
+
return true if ENV["HOSTS"]
|
519
|
+
roles = Array options[:roles]
|
520
|
+
return true if roles.empty?
|
521
|
+
# borrowed from hosts_for:
|
522
|
+
roles.flatten.each { |r|
|
523
|
+
return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r]
|
524
|
+
}
|
525
|
+
return false
|
526
|
+
end
|
527
|
+
|
528
|
+
##
|
529
|
+
# Action is used to run a task's remote_actions in parallel on each
|
530
|
+
# of its hosts. Actions are created automatically in
|
531
|
+
# Rake::RemoteTask#enhance.
|
532
|
+
|
533
|
+
class Action
|
534
|
+
|
535
|
+
##
|
536
|
+
# The task this action is attached to.
|
537
|
+
|
538
|
+
attr_reader :task
|
539
|
+
|
540
|
+
##
|
541
|
+
# The block this action will execute.
|
542
|
+
|
543
|
+
attr_reader :block
|
544
|
+
|
545
|
+
##
|
546
|
+
# An Array of threads, one for each host this action executes on.
|
547
|
+
|
548
|
+
attr_reader :workers
|
549
|
+
|
550
|
+
##
|
551
|
+
# Creates a new Action that will run +block+ for +task+.
|
552
|
+
|
553
|
+
def initialize task, block
|
554
|
+
@task = task
|
555
|
+
@block = block
|
556
|
+
@workers = ThreadGroup.new
|
557
|
+
end
|
558
|
+
|
559
|
+
def == other # :nodoc:
|
560
|
+
return false unless Action === other
|
561
|
+
block == other.block && task == other.task
|
562
|
+
end
|
563
|
+
|
564
|
+
##
|
565
|
+
# Execute this action on +hosts+ in parallel. Returns when block
|
566
|
+
# has completed for each host.
|
567
|
+
|
568
|
+
def execute hosts, task, args
|
569
|
+
hosts.each do |host|
|
570
|
+
t = task.clone
|
571
|
+
t.target_host = host
|
572
|
+
thread = Thread.new(t) do |task|
|
573
|
+
Thread.current[:task] = task
|
574
|
+
case block.arity
|
575
|
+
when 1
|
576
|
+
block.call task
|
577
|
+
else
|
578
|
+
block.call task, args
|
579
|
+
end
|
580
|
+
Thread.current[:task] = nil
|
581
|
+
end
|
582
|
+
@workers.add thread
|
583
|
+
end
|
584
|
+
@workers.list.each { |thr| thr.join }
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
Rake::RemoteTask.set_defaults
|