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 +26 -1
- data/Manifest.txt +4 -1
- data/README.txt +9 -12
- data/Rakefile +26 -2
- data/doco/faq.txt +75 -0
- data/doco/getting_started.txt +18 -4
- data/doco/migration.txt +22 -0
- data/doco/variables.txt +38 -6
- data/lib/rake_remote_task.rb +274 -134
- data/lib/vlad.rb +62 -51
- data/lib/vlad/apache.rb +37 -0
- data/lib/{vlad_tasks.rb → vlad/core.rb} +21 -135
- data/lib/vlad/mongrel.rb +65 -0
- data/lib/vlad/perforce.rb +5 -9
- data/lib/vlad/subversion.rb +8 -4
- data/test/test_rake_remote_task.rb +6 -1
- data/test/test_vlad.rb +8 -16
- data/test/test_vlad_perforce.rb +0 -1
- data/test/test_vlad_subversion.rb +2 -1
- data/test/vlad_test_case.rb +17 -7
- metadata +9 -5
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
|
-
*
|
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
|
-
*
|
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 '
|
31
|
+
sh 'flog -s lib'
|
21
32
|
end
|
22
33
|
|
23
34
|
task :flog_full do
|
24
|
-
sh '
|
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/>
|
data/doco/getting_started.txt
CHANGED
@@ -16,12 +16,26 @@ refer to the variable documentation.
|
|
16
16
|
|
17
17
|
* Add the following to your Rakefile:
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
begin
|
20
|
+
require 'vlad'
|
21
|
+
Vlad.load
|
22
|
+
rescue LoadError
|
23
|
+
# do nothing
|
24
|
+
end
|
21
25
|
|
22
|
-
|
26
|
+
Vlad.load has a lot of flexibility. See the rdoc for full information.
|
23
27
|
|
24
|
-
|
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
|
-
|
36
|
-
|
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
|
+
|
data/lib/rake_remote_task.rb
CHANGED
@@ -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
|
-
#
|
7
|
-
#
|
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.
|
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.
|
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+.
|
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.
|
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
|
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 = [
|
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.
|
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 = [
|
164
|
+
cmd = [ssh_cmd, ssh_flags, target_host, command].compact
|
82
165
|
result = []
|
83
166
|
|
84
|
-
|
167
|
+
warn cmd.join(' ') if $TRACE
|
168
|
+
|
169
|
+
pid, inn, out, err = popen4(*cmd)
|
85
170
|
|
86
|
-
|
87
|
-
|
171
|
+
inn.sync = true
|
172
|
+
streams = [out, err]
|
173
|
+
out_stream = {
|
174
|
+
out => $stdout,
|
175
|
+
err => $stderr,
|
176
|
+
}
|
88
177
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
178
|
+
# Handle process termination ourselves
|
179
|
+
status = nil
|
180
|
+
Thread.start do
|
181
|
+
status = Process.waitpid2(pid).last
|
182
|
+
end
|
94
183
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
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
|
-
#
|
217
|
+
# Returns an Array with every host configured.
|
119
218
|
|
120
|
-
def
|
121
|
-
|
219
|
+
def self.all_hosts
|
220
|
+
hosts_for(roles.keys)
|
122
221
|
end
|
123
222
|
|
124
223
|
##
|
125
|
-
# The
|
126
|
-
#
|
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
|
134
|
-
|
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
|
-
#
|
232
|
+
# The vlad environment.
|
144
233
|
|
145
|
-
def self.
|
146
|
-
|
234
|
+
def self.env
|
235
|
+
@@env
|
147
236
|
end
|
148
237
|
|
149
238
|
##
|
150
|
-
# Fetches environment variable +name+ from the environment using
|
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+.
|
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
|
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
|
-
#
|
321
|
+
# Resets vlad, restoring all roles, tasks and environment variables
|
322
|
+
# to the defaults.
|
216
323
|
|
217
|
-
def self.
|
218
|
-
|
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
|
-
#
|
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.
|
234
|
-
|
235
|
-
|
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
|
-
|
370
|
+
Rake::RemoteTask.default_env[name.to_s] = Rake::RemoteTask.env[name.to_s] =
|
371
|
+
value || default_block
|
239
372
|
|
240
|
-
|
241
|
-
|
373
|
+
Object.send :define_method, name do
|
374
|
+
Rake::RemoteTask.fetch name
|
375
|
+
end
|
242
376
|
end
|
243
377
|
|
244
378
|
##
|
245
|
-
#
|
246
|
-
#
|
247
|
-
|
248
|
-
def self.
|
249
|
-
@@
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
set(:
|
271
|
-
set :
|
272
|
-
set
|
273
|
-
set(:
|
274
|
-
set
|
275
|
-
set
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
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
|
-
#
|
446
|
+
# The Rake::RemoteTask executing in this Thread.
|
309
447
|
|
310
|
-
def self.
|
311
|
-
|
312
|
-
@@roles[role_name][host] = args
|
448
|
+
def self.task
|
449
|
+
Thread.current[:task]
|
313
450
|
end
|
314
451
|
|
315
452
|
##
|
316
|
-
#
|
317
|
-
# +block+.
|
453
|
+
# The configured Rake::RemoteTasks.
|
318
454
|
|
319
|
-
def self.
|
320
|
-
|
321
|
-
t.options = options
|
322
|
-
roles = options[:roles]
|
323
|
-
t
|
455
|
+
def self.tasks
|
456
|
+
@@tasks
|
324
457
|
end
|
325
458
|
|
326
459
|
##
|
327
|
-
#
|
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
|
334
|
-
|
335
|
-
|
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
|
-
|
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
|
-
|
342
|
-
|
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
|
348
|
-
# hosts.
|
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.
|
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
|