vlad 1.0.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 +5 -0
- data/Manifest.txt +19 -0
- data/README.txt +81 -0
- data/Rakefile +27 -0
- data/considerations.txt +91 -0
- data/doco/getting_started.txt +27 -0
- data/doco/migration.txt +21 -0
- data/doco/perforce.txt +5 -0
- data/doco/variables.txt +44 -0
- data/lib/rake_remote_task.rb +399 -0
- data/lib/vlad.rb +76 -0
- data/lib/vlad/perforce.rb +51 -0
- data/lib/vlad/subversion.rb +30 -0
- data/lib/vlad_tasks.rb +282 -0
- data/test/test_rake_remote_task.rb +181 -0
- data/test/test_vlad.rb +219 -0
- data/test/test_vlad_perforce.rb +38 -0
- data/test/test_vlad_subversion.rb +26 -0
- data/test/vlad_test_case.rb +61 -0
- metadata +103 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
considerations.txt
|
6
|
+
doco/getting_started.txt
|
7
|
+
doco/migration.txt
|
8
|
+
doco/perforce.txt
|
9
|
+
doco/variables.txt
|
10
|
+
lib/rake_remote_task.rb
|
11
|
+
lib/vlad.rb
|
12
|
+
lib/vlad/perforce.rb
|
13
|
+
lib/vlad/subversion.rb
|
14
|
+
lib/vlad_tasks.rb
|
15
|
+
test/test_rake_remote_task.rb
|
16
|
+
test/test_vlad.rb
|
17
|
+
test/test_vlad_perforce.rb
|
18
|
+
test/test_vlad_subversion.rb
|
19
|
+
test/vlad_test_case.rb
|
data/README.txt
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
Vlad the Deployer
|
2
|
+
by the Ruby Hit Squad
|
3
|
+
http://rubyhitsquad.com/
|
4
|
+
http://rubyforge.org/projects/hitsquad/
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
Vlad the Deployer is pragmatic application deployment automation,
|
9
|
+
without mercy. Much like Capistrano, but with 1/10th the
|
10
|
+
complexity. Vlad integrates seamlessly with Rake, and uses familiar
|
11
|
+
and standard tools like ssh and rsync.
|
12
|
+
|
13
|
+
Impale your application on the heartless spike of the Deployer.
|
14
|
+
|
15
|
+
== FEATURES/PROBLEMS:
|
16
|
+
|
17
|
+
* Full deployment automation stack.
|
18
|
+
* Supports single server deployment with just 4 variables defined.
|
19
|
+
* Very few dependencies. All simple.
|
20
|
+
* Uses ssh with your ssh settings already in place.
|
21
|
+
* Uses rsync for efficient transfers.
|
22
|
+
* Run remote commands on one or more servers.
|
23
|
+
* Syncs files to one or more servers.
|
24
|
+
* Mix and match local and remote tasks.
|
25
|
+
* Built on rake. easy.
|
26
|
+
* Compatible with all of your tab completion shell script rake-tastic goodness.
|
27
|
+
* Ships with tests that actually pass.
|
28
|
+
* Engine is under 500 lines of code.
|
29
|
+
* Super uper simple.
|
30
|
+
* Does NOT support Windows right now. Coming soon in 1.1.
|
31
|
+
* This is 1.0.0... expect rough edges.
|
32
|
+
|
33
|
+
== SYNOPSIS:
|
34
|
+
|
35
|
+
rake vlad:setup # first time only
|
36
|
+
rake vlad:update
|
37
|
+
rake vlad:migrate # optional
|
38
|
+
rake vlad:start
|
39
|
+
|
40
|
+
== REQUIREMENTS:
|
41
|
+
|
42
|
+
* Rake
|
43
|
+
* Hoe
|
44
|
+
* Rubyforge
|
45
|
+
* open4
|
46
|
+
|
47
|
+
== INSTALL:
|
48
|
+
|
49
|
+
* sudo gem install -y vlad
|
50
|
+
|
51
|
+
== SPECIAL THANKS:
|
52
|
+
|
53
|
+
* First, of course, to Capistrano. For coming up with the idea and
|
54
|
+
providing a lot of meat for the recipes.
|
55
|
+
* Scott Baron for coming up with one of the best project names evar.
|
56
|
+
* Bradley Taylor for giving us permission to use RailsMachine recipes sans-LGPL.
|
57
|
+
|
58
|
+
== LICENSE:
|
59
|
+
|
60
|
+
(The MIT License)
|
61
|
+
|
62
|
+
Copyright (c) 2007 Ryan Davis and the rest of the Ruby Hit Squad
|
63
|
+
|
64
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
65
|
+
a copy of this software and associated documentation files (the
|
66
|
+
'Software'), to deal in the Software without restriction, including
|
67
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
68
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
69
|
+
permit persons to whom the Software is furnished to do so, subject to
|
70
|
+
the following conditions:
|
71
|
+
|
72
|
+
The above copyright notice and this permission notice shall be
|
73
|
+
included in all copies or substantial portions of the Software.
|
74
|
+
|
75
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
76
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
77
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
78
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
79
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
80
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
81
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
$: << 'lib'
|
6
|
+
require 'vlad'
|
7
|
+
|
8
|
+
Hoe.new('vlad', Vlad::VERSION) do |p|
|
9
|
+
p.rubyforge_name = 'hitsquad'
|
10
|
+
p.author = ["Ryan Davis", "Eric Hodel", "Wilson Bilkovich"]
|
11
|
+
p.email = "ryand-ruby@zenspider.com"
|
12
|
+
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/).map { |s| s.strip }[2..-1]
|
13
|
+
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
14
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
|
+
p.extra_deps << 'rake'
|
16
|
+
p.extra_deps << 'open4'
|
17
|
+
end
|
18
|
+
|
19
|
+
task :flog do
|
20
|
+
sh 'find lib -name \*.rb | grep -v vlad_tasks | xargs flog | head -1'
|
21
|
+
end
|
22
|
+
|
23
|
+
task :flog_full do
|
24
|
+
sh 'find lib -name \*.rb | xargs flog -a'
|
25
|
+
end
|
26
|
+
|
27
|
+
# vim: syntax=Ruby
|
data/considerations.txt
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
* might we want per connection values?
|
2
|
+
|
3
|
+
* :except => {:no_release => true}
|
4
|
+
|
5
|
+
It is common to configure tasks to 'announce' deployments in IRC, Campfire,
|
6
|
+
etc. If you have 6 app servers, you don't want to see 6 announcements. In
|
7
|
+
Capistrano, this is handled via the :no_release => true flag. Various tasks
|
8
|
+
only execute on the 'release' servers.
|
9
|
+
|
10
|
+
An easier way to meet this would be to introduce a :release role in the
|
11
|
+
default setup
|
12
|
+
|
13
|
+
role :release, "app1.example.com"
|
14
|
+
|
15
|
+
remote_task :announce_in_irc, :roles => :release ...
|
16
|
+
|
17
|
+
Drawback: Yet another thing to change when you migrate a project from cap to
|
18
|
+
vlad
|
19
|
+
|
20
|
+
* 'dynamic deployments'
|
21
|
+
|
22
|
+
role :app, "app1.example.com"
|
23
|
+
role :app, "app2.example.com"
|
24
|
+
|
25
|
+
Let's say that app1 and app2 need slightly different monit configurations.
|
26
|
+
|
27
|
+
In Capistrano, you might approach this by making two additional roles, and
|
28
|
+
splitting your 'push a monit config' task into two. This sucks.
|
29
|
+
|
30
|
+
Vlad makes the 'execution context' of a task available. In Vlad, you would:
|
31
|
+
|
32
|
+
remote_task :update_monit, :roles => :app
|
33
|
+
rsync "templates/#{target_host}.monitrc", "/etc/monitrc"
|
34
|
+
end
|
35
|
+
|
36
|
+
* fine-grained tasks
|
37
|
+
|
38
|
+
remote_task :update
|
39
|
+
remote_task :symlink
|
40
|
+
remote_task :migrate
|
41
|
+
remote_task :deploy => [:update, :symlink, :migrate, :restart]
|
42
|
+
|
43
|
+
Let's assume that this is a multi-server config with shared deploy path.
|
44
|
+
The user wants to do only a single checkout. If we make "update" be one big
|
45
|
+
task body that includes the update, symlink, and migrate steps,
|
46
|
+
it is difficult for the user to override the roles for the particular steps
|
47
|
+
they need to change.
|
48
|
+
|
49
|
+
If we break these into separate tasks, they can say:
|
50
|
+
|
51
|
+
Rake::Task["migrate"].options[:roles] = :master_db
|
52
|
+
|
53
|
+
and the migrations will only run on the master db
|
54
|
+
|
55
|
+
* sudo / via how? and if we call it via I will stab ppl. "user" is sufficient.
|
56
|
+
|
57
|
+
* handling 'use_sudo'
|
58
|
+
|
59
|
+
1. Check for this inside the 'run' command, and preface the command
|
60
|
+
with 'sudo' if necessary
|
61
|
+
|
62
|
+
2. Default this to 'false' in the reset method, and check for it
|
63
|
+
in the default tasks that we provide:
|
64
|
+
if use_sudo then
|
65
|
+
sudo "blah"
|
66
|
+
else
|
67
|
+
run "blah"
|
68
|
+
end
|
69
|
+
|
70
|
+
Option 2 has fewer moving parts, but clutters up the tasks that care about
|
71
|
+
this.
|
72
|
+
|
73
|
+
* Dependencies
|
74
|
+
|
75
|
+
Task dependencies aren't settable when creating a Rake::RemoteTask.
|
76
|
+
|
77
|
+
* Apache configuration
|
78
|
+
|
79
|
+
Pull in railsmachine/rails/recipes/apache.rb's apache configuration. Needs
|
80
|
+
erb to work.
|
81
|
+
|
82
|
+
* I really like tasks with naming <cmd>_<role> (eg setup_app,
|
83
|
+
start_web). We could easily make the front end remote_task command
|
84
|
+
look for such a convention and apply the :role => x automatically.
|
85
|
+
|
86
|
+
* from bousquet: get a couple of server environment recipes that prepare your
|
87
|
+
machine that would be the golden ticket:
|
88
|
+
|
89
|
+
rake vlad:prepare TYPE=accelerator | ubuntu | osx | osxserver | site5 | ...
|
90
|
+
|
91
|
+
and have people maintaining those setups who depend on them
|
@@ -0,0 +1,27 @@
|
|
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
|
+
* Add the following to your Rakefile:
|
18
|
+
|
19
|
+
require 'vlad'
|
20
|
+
Vlad.load 'config/deploy.rb'
|
21
|
+
|
22
|
+
* <tt>rake vlad:setup</tt>
|
23
|
+
|
24
|
+
=== Launch
|
25
|
+
|
26
|
+
* <tt>rake vlad:update vlad:migrate vlad:start</tt>
|
27
|
+
|
data/doco/migration.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== Converting from Capistrano
|
2
|
+
|
3
|
+
* 'task' blocks are renamed to 'remote_task'.
|
4
|
+
* Most variables are the same. See variables.txt for details.
|
5
|
+
* No +with_command+ / +sudo+ / +via+ wonkiness
|
6
|
+
* Uses real ssh so env vars and the like are not a problem
|
7
|
+
- no +with_env+ as a result.
|
8
|
+
* Vlad doesn't use ':no_release' or ':primary'.
|
9
|
+
- If you have a task that needs to run on only one host from a role,
|
10
|
+
you should declare a new role for that host:
|
11
|
+
|
12
|
+
role :master_db, "master.example.com"
|
13
|
+
|
14
|
+
..and then override the role for the task you want to limit:
|
15
|
+
|
16
|
+
Rake::Task["mytask"].options[:roles] = :master_db
|
17
|
+
|
18
|
+
* The 'host' method can be used to consolidate multiple 'role' calls.
|
19
|
+
- host "www.example.com", :app, :web, :db
|
20
|
+
specifies a host with three roles.
|
21
|
+
* migrate_env is now migrate_args.
|
data/doco/perforce.txt
ADDED
data/doco/variables.txt
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
== Variables
|
3
|
+
|
4
|
+
application:: REQUIRED: Name of your application. e.g. hitsquad
|
5
|
+
repository:: REQUIRED: Repository path: e.g. http://repo.example.com/svn
|
6
|
+
deploy_to:: REQUIRED: Deploy path on target machines. e.g. /var/www/app
|
7
|
+
domain:: REQUIRED: Used for the common case of a single target
|
8
|
+
server. e.g. example.com
|
9
|
+
current_path:: The full path on the remote host that will be symlinked
|
10
|
+
as 'current'. Defaults to "#{deploy_to}/current".
|
11
|
+
current_release:: The full path to the current release's actual location.
|
12
|
+
Defaults to "#{releases_path}/#{releases.last}".
|
13
|
+
deploy_timestamped:: Create timestamped release directories instead of using
|
14
|
+
revision numbers. Defaults to true.
|
15
|
+
deploy_via:: Which SCM command should be used when deploying the app.
|
16
|
+
Defaults to "export".
|
17
|
+
latest_release:: The most recent release, which may not yet have been
|
18
|
+
symlinked. Defaults to release_path.
|
19
|
+
migrate_args:: Set this to change the RAILS_ENV that 'rake db:migrate'
|
20
|
+
will run under. Defaults to "".
|
21
|
+
migrate_target:: Set this if you need to specify a particular migration
|
22
|
+
'VERSION' number. Defaults to "latest".
|
23
|
+
rails_env:: Specifies the RAILS_ENV environment variable that will
|
24
|
+
be used. Defaults to "production".
|
25
|
+
rake:: Set this if you need to specify an alternate path to
|
26
|
+
'rake'. Defaults to "rake".
|
27
|
+
release_name:: Name of the release directory, if deploy_timestamped is
|
28
|
+
true. Defaults to timestamp: "YYYYMMDDHHMMSS".
|
29
|
+
release_path:: Path to this release, which may not have been created
|
30
|
+
yet. Defaults to "#{releases_path}/#{release_name}".
|
31
|
+
releases:: An array of all existing releases, oldest first.
|
32
|
+
Defaults to latest release directory name.
|
33
|
+
releases_path:: Full path to the 'releases' directory on the remote host.
|
34
|
+
Defaults to "#{deploy_to}/releases".
|
35
|
+
scm:: Which SCM module to use. Valid options are :subversion
|
36
|
+
and :perforce as of 1.0. Defaults to "subversion"
|
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
|
+
sudo_password:: Asks for password when referenced.
|
40
|
+
source:: Read-Only: An SCM worker instance defined by scm.
|
41
|
+
shared_path:: Full path to remote 'shared' directory, symlinked into
|
42
|
+
your app by default. Defaults to "#{deploy_to}/shared".
|
43
|
+
web_command:: Command to execute when controlling the web server.
|
44
|
+
Defaults to "apachectl".
|
@@ -0,0 +1,399 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'open4'
|
3
|
+
require 'vlad'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Rake::RemoteTask is a subclass of Rake::Task that adds remote_actions that
|
7
|
+
# execute in parallel on multiple hosts via ssh.
|
8
|
+
|
9
|
+
class Rake::RemoteTask < Rake::Task
|
10
|
+
|
11
|
+
include Open4
|
12
|
+
|
13
|
+
##
|
14
|
+
# Options for execution of this task.
|
15
|
+
|
16
|
+
attr_accessor :options
|
17
|
+
|
18
|
+
##
|
19
|
+
# The host this task is running on during execution.
|
20
|
+
|
21
|
+
attr_accessor :target_host
|
22
|
+
|
23
|
+
##
|
24
|
+
# An Array of Actions this host will perform during execution. Use enhance
|
25
|
+
# to add new actions to a task.
|
26
|
+
|
27
|
+
attr_reader :remote_actions
|
28
|
+
|
29
|
+
##
|
30
|
+
# Create a new task named +task_name+ attached to Rake::Application +app+.
|
31
|
+
|
32
|
+
def initialize(task_name, app)
|
33
|
+
super
|
34
|
+
@remote_actions = []
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Add a local action to this task. This calls Rake::Task#enhance.
|
39
|
+
|
40
|
+
alias_method :original_enhance, :enhance
|
41
|
+
|
42
|
+
##
|
43
|
+
# Add remote action +block+ to this task with dependencies +deps+. See
|
44
|
+
# Rake::Task#enhance.
|
45
|
+
|
46
|
+
def enhance(deps=nil, &block)
|
47
|
+
original_enhance(deps) # can't use super because block passed regardless.
|
48
|
+
@remote_actions << Action.new(self, block) if block_given?
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Execute this action. Local actions will be performed first, then remote
|
54
|
+
# actions will be performed in parallel on each host configured for this
|
55
|
+
# RemoteTask.
|
56
|
+
|
57
|
+
def execute
|
58
|
+
raise Vlad::ConfigurationError, "No target hosts specified for task: #{self.name}" if target_hosts.empty?
|
59
|
+
super
|
60
|
+
@remote_actions.each { |act| act.execute(target_hosts) }
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Use rsync to send +local+ to +remote+ on target_host.
|
65
|
+
|
66
|
+
def rsync local, remote
|
67
|
+
cmd = ['rsync', '-azP', '--delete', local, "#{@target_host}:#{remote}"]
|
68
|
+
|
69
|
+
success = system(*cmd)
|
70
|
+
|
71
|
+
unless success then
|
72
|
+
raise Vlad::CommandFailedError, "execution failed: #{cmd.join ' '}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
|
78
|
+
# sudo password will be prompted for then saved for subsequent sudo commands.
|
79
|
+
|
80
|
+
def run command
|
81
|
+
cmd = ["ssh", target_host, command]
|
82
|
+
result = []
|
83
|
+
|
84
|
+
puts cmd.join(' ') if Rake.application.options.trace
|
85
|
+
|
86
|
+
status = popen4(*cmd) do |pid, inn, out, err|
|
87
|
+
inn.sync = true
|
88
|
+
|
89
|
+
until out.eof? and err.eof? do
|
90
|
+
unless err.eof? then
|
91
|
+
data = err.readpartial(1024)
|
92
|
+
result << data
|
93
|
+
$stderr.write data
|
94
|
+
|
95
|
+
if data =~ /^Password:/ then
|
96
|
+
inn.puts sudo_password
|
97
|
+
result << "\n"
|
98
|
+
$stderr.write "\n"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
unless out.eof? then
|
103
|
+
data = out.readpartial(1024)
|
104
|
+
result << data
|
105
|
+
$stdout.write data
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
unless status.success? then
|
111
|
+
raise Vlad::CommandFailedError, "execution failed with status #{status.exitstatus}: #{cmd.join ' '}"
|
112
|
+
end
|
113
|
+
|
114
|
+
result.join
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Execute +command+ under sudo using run.
|
119
|
+
|
120
|
+
def sudo command
|
121
|
+
run "sudo #{command}"
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# The hosts this task will execute on. The hosts are determined from the
|
126
|
+
# role this task belongs to.
|
127
|
+
#
|
128
|
+
# The target hosts may be overridden by providing a comma-separated list of
|
129
|
+
# commands to the HOSTS environment variable:
|
130
|
+
#
|
131
|
+
# rake my_task HOSTS=app1.example.com,app2.example.com
|
132
|
+
|
133
|
+
def target_hosts
|
134
|
+
if hosts = ENV["HOSTS"] then
|
135
|
+
hosts.strip.gsub(/\s+/, '').split(",")
|
136
|
+
else
|
137
|
+
roles = options[:roles]
|
138
|
+
roles ? Rake::RemoteTask.hosts_for(roles) : Rake::RemoteTask.all_hosts
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Returns an Array with every host configured.
|
144
|
+
|
145
|
+
def self.all_hosts
|
146
|
+
hosts_for(roles.keys)
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Fetches environment variable +name+ from the environment using default
|
151
|
+
# +default+.
|
152
|
+
|
153
|
+
def self.fetch name, default = nil
|
154
|
+
name = name.to_s if Symbol === name
|
155
|
+
if @@env.has_key? name then
|
156
|
+
protect_env(name) do
|
157
|
+
v = @@env[name]
|
158
|
+
v = @@env[name] = v.call if Proc === v
|
159
|
+
v
|
160
|
+
end
|
161
|
+
elsif default
|
162
|
+
v = @@env[name] = default
|
163
|
+
else
|
164
|
+
raise Vlad::FetchError
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Add host +host_name+ that belongs to +roles+. Extra arguments may be
|
170
|
+
# specified for the host as a hash as the last argument.
|
171
|
+
#
|
172
|
+
# host is the inversion of role:
|
173
|
+
#
|
174
|
+
# host 'db1.example.com', :db, :master_db
|
175
|
+
#
|
176
|
+
# Is equivalent to:
|
177
|
+
#
|
178
|
+
# role :db, 'db1.example.com'
|
179
|
+
# role :master_db, 'db1.example.com'
|
180
|
+
|
181
|
+
def self.host host_name, *roles
|
182
|
+
opts = Hash === roles.last ? roles.pop : {}
|
183
|
+
|
184
|
+
roles.each do |role_name|
|
185
|
+
role role_name, host_name, opts.dup
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# Returns an Array of all hosts in +roles+.
|
191
|
+
|
192
|
+
def self.hosts_for *roles
|
193
|
+
roles.flatten.map { |r|
|
194
|
+
self.roles[r].keys
|
195
|
+
}.flatten.uniq.sort
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Ensures exclusive access to +name+.
|
200
|
+
|
201
|
+
def self.protect_env name # :nodoc:
|
202
|
+
@@env_locks[name.to_s].synchronize do
|
203
|
+
yield
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Ensures +name+ does not conflict with an existing method.
|
209
|
+
|
210
|
+
def self.reserved_name? name # :nodoc:
|
211
|
+
!@@env.has_key?(name.to_s) && self.respond_to?(name)
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# The Rake::RemoteTask executing in this Thread.
|
216
|
+
|
217
|
+
def self.task
|
218
|
+
Thread.current[:task]
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# The configured roles.
|
223
|
+
|
224
|
+
def self.roles
|
225
|
+
host domain, :app, :web, :db if @@roles.empty?
|
226
|
+
|
227
|
+
@@roles
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# The configured Rake::RemoteTasks.
|
232
|
+
|
233
|
+
def self.tasks
|
234
|
+
@@tasks
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# The vlad environment.
|
239
|
+
|
240
|
+
def self.env
|
241
|
+
@@env
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Resets vlad, restoring all roles, tasks and environment variables to the
|
246
|
+
# defaults.
|
247
|
+
|
248
|
+
def self.reset
|
249
|
+
@@roles = Hash.new { |h,k| h[k] = {} }
|
250
|
+
@@env = {}
|
251
|
+
@@tasks = {}
|
252
|
+
@@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
|
253
|
+
|
254
|
+
# mandatory
|
255
|
+
set(:application) { raise(Vlad::ConfigurationError,
|
256
|
+
"Please specify the name of the application") }
|
257
|
+
set(:repository) { raise(Vlad::ConfigurationError,
|
258
|
+
"Please specify the repository path") }
|
259
|
+
set(:deploy_to) { raise(Vlad::ConfigurationError,
|
260
|
+
"Please specify the deploy path") }
|
261
|
+
set(:domain) { raise(Vlad::ConfigurationError,
|
262
|
+
"Please specify the server domain") }
|
263
|
+
|
264
|
+
# optional
|
265
|
+
set(:current_path) { File.join(deploy_to, "current") }
|
266
|
+
set(:current_release) { File.join(releases_path, releases[-1]) }
|
267
|
+
set :keep_releases, 5
|
268
|
+
set :deploy_timestamped, true
|
269
|
+
set :deploy_via, :export
|
270
|
+
set(:latest_release) { deploy_timestamped ? release_path : current_release }
|
271
|
+
set :migrate_args, ""
|
272
|
+
set :migrate_target, :latest
|
273
|
+
set(:previous_release){ File.join(releases_path, releases[-2]) }
|
274
|
+
set :rails_env, "production"
|
275
|
+
set :rake, "rake"
|
276
|
+
set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
|
277
|
+
set(:release_path) { File.join(releases_path, release_name) }
|
278
|
+
set(:releases) { task.run("ls -x #{releases_path}").split.sort }
|
279
|
+
set(:releases_path) { File.join(deploy_to, "releases") }
|
280
|
+
set :scm, :subversion
|
281
|
+
set(:scm_path) { File.join(deploy_to, "scm") }
|
282
|
+
set(:shared_path) { File.join(deploy_to, "shared") }
|
283
|
+
|
284
|
+
set(:sudo_password) do
|
285
|
+
state = `stty -g`
|
286
|
+
|
287
|
+
raise Vlad::Error, "stty(1) not found" unless $?.success?
|
288
|
+
|
289
|
+
begin
|
290
|
+
system "stty -echo"
|
291
|
+
$stdout.print "sudo password: "
|
292
|
+
$stdout.flush
|
293
|
+
sudo_password = $stdin.gets
|
294
|
+
$stdout.puts
|
295
|
+
ensure
|
296
|
+
system "stty #{state}"
|
297
|
+
end
|
298
|
+
sudo_password
|
299
|
+
end
|
300
|
+
|
301
|
+
set(:source) do
|
302
|
+
require "vlad/#{scm}"
|
303
|
+
Vlad.const_get(scm.to_s.capitalize).new
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
##
|
308
|
+
# Adds role +role_name+ with +host+ and +args+ for that host.
|
309
|
+
|
310
|
+
def self.role role_name, host, args = {}
|
311
|
+
raise ArgumentError, "invalid host" if host.nil? or host.empty?
|
312
|
+
@@roles[role_name][host] = args
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
# Adds a remote task named +name+ with options +options+ that will execute
|
317
|
+
# +block+.
|
318
|
+
|
319
|
+
def self.remote_task name, options = {}, &block
|
320
|
+
t = Rake::RemoteTask.define_task(name, &block)
|
321
|
+
t.options = options
|
322
|
+
roles = options[:roles]
|
323
|
+
t
|
324
|
+
end
|
325
|
+
|
326
|
+
##
|
327
|
+
# Set environment variable +name+ to +value+ or +default_block+.
|
328
|
+
#
|
329
|
+
# If +default_block+ is defined, the block will be executed the first time
|
330
|
+
# the variable is fetched, and the value will be used for every subsequent
|
331
|
+
# fetch.
|
332
|
+
|
333
|
+
def self.set name, value = nil, &default_block
|
334
|
+
raise ArgumentError, "cannot provide both a value and a block" if
|
335
|
+
value and default_block
|
336
|
+
raise ArgumentError, "cannot set reserved name: '#{name}'" if
|
337
|
+
Rake::RemoteTask.reserved_name?(name)
|
338
|
+
|
339
|
+
Rake::RemoteTask.env[name.to_s] = value || default_block
|
340
|
+
|
341
|
+
Object.send :define_method, name do
|
342
|
+
Rake::RemoteTask.fetch name
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
##
|
347
|
+
# Action is used to run a task's remote_actions in parallel on each of its
|
348
|
+
# hosts. Actions are created automatically in Rake::RemoteTask#enhance.
|
349
|
+
|
350
|
+
class Action
|
351
|
+
|
352
|
+
##
|
353
|
+
# The task this action is attached to.
|
354
|
+
|
355
|
+
attr_reader :task
|
356
|
+
|
357
|
+
##
|
358
|
+
# The block this action will execute.
|
359
|
+
|
360
|
+
attr_reader :block
|
361
|
+
|
362
|
+
##
|
363
|
+
# An Array of threads, one for each host this action executes on.
|
364
|
+
|
365
|
+
attr_reader :workers
|
366
|
+
|
367
|
+
##
|
368
|
+
# Creates a new Action that will run +block+ for +task+.
|
369
|
+
|
370
|
+
def initialize task, block
|
371
|
+
@task = task
|
372
|
+
@block = block
|
373
|
+
@workers = []
|
374
|
+
end
|
375
|
+
|
376
|
+
def == other # :nodoc:
|
377
|
+
return false unless Action === other
|
378
|
+
block == other.block && task == other.task
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# Execute this action on +hosts+ in parallel. Returns when block has
|
383
|
+
# completed for each host.
|
384
|
+
|
385
|
+
def execute hosts
|
386
|
+
hosts.each do |host|
|
387
|
+
t = task.clone
|
388
|
+
t.target_host = host
|
389
|
+
thread = Thread.new(t) do |task|
|
390
|
+
Thread.current[:task] = task
|
391
|
+
block.call
|
392
|
+
end
|
393
|
+
@workers << thread
|
394
|
+
end
|
395
|
+
@workers.each { |w| w.join }
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|