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 +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
|