ktheory-vlad 2.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/.autotest +24 -0
- data/History.txt +173 -0
- data/Manifest.txt +25 -0
- data/PATCHES.txt +27 -0
- data/README.txt +78 -0
- data/Rakefile +58 -0
- data/VERSION +1 -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/ktheory-vlad.gemspec +98 -0
- data/lib/rake_remote_task.rb +600 -0
- data/lib/vlad.rb +91 -0
- data/lib/vlad/apache.rb +37 -0
- data/lib/vlad/core.rb +191 -0
- data/lib/vlad/darcs.rb +24 -0
- data/lib/vlad/git.rb +86 -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/lib/vlad_test_case.rb +73 -0
- data/test/test_rake_remote_task.rb +279 -0
- data/test/test_vlad.rb +210 -0
- data/test/test_vlad_git.rb +65 -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/vladdemo.sh +97 -0
- metadata +134 -0
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,98 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ktheory-vlad}
|
8
|
+
s.version = "2.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ryan Davis", "Eric Hodel", "Wilson Bilkovich", "Aaron Suggs"]
|
12
|
+
s.date = %q{2009-12-05}
|
13
|
+
s.description = %q{
|
14
|
+
Vlad the Deployer is pragmatic application deployment automation,
|
15
|
+
without mercy. Much like Capistrano, but with 1/10th the
|
16
|
+
complexity. Vlad integrates seamlessly with Rake, and uses familiar
|
17
|
+
and standard tools like ssh and rsync. This is a fork of vlad maintained by
|
18
|
+
Aaron Suggs. See PATCHES.txt for more info""
|
19
|
+
}
|
20
|
+
s.email = ["ryand-ruby@zenspider.com", "drbrain@segment7.net", "wilson@supremetyrant.com", "aaron@ktheory.com"]
|
21
|
+
s.extra_rdoc_files = [
|
22
|
+
"README.txt"
|
23
|
+
]
|
24
|
+
s.files = [
|
25
|
+
".autotest",
|
26
|
+
"History.txt",
|
27
|
+
"Manifest.txt",
|
28
|
+
"PATCHES.txt",
|
29
|
+
"README.txt",
|
30
|
+
"Rakefile",
|
31
|
+
"VERSION",
|
32
|
+
"considerations.txt",
|
33
|
+
"doco/deploying-merb-with-vlad.txt",
|
34
|
+
"doco/deploying-sinatra-with-vlad.txt",
|
35
|
+
"doco/faq.txt",
|
36
|
+
"doco/getting_started.txt",
|
37
|
+
"doco/migration.txt",
|
38
|
+
"doco/perforce.txt",
|
39
|
+
"doco/variables.txt",
|
40
|
+
"ktheory-vlad-2.0.0.gem",
|
41
|
+
"ktheory-vlad.gemspec",
|
42
|
+
"lib/rake_remote_task.rb",
|
43
|
+
"lib/vlad.rb",
|
44
|
+
"lib/vlad/apache.rb",
|
45
|
+
"lib/vlad/core.rb",
|
46
|
+
"lib/vlad/darcs.rb",
|
47
|
+
"lib/vlad/git.rb",
|
48
|
+
"lib/vlad/god.rb",
|
49
|
+
"lib/vlad/lighttpd.rb",
|
50
|
+
"lib/vlad/maintenance.rb",
|
51
|
+
"lib/vlad/merb.rb",
|
52
|
+
"lib/vlad/mercurial.rb",
|
53
|
+
"lib/vlad/mongrel.rb",
|
54
|
+
"lib/vlad/nginx.rb",
|
55
|
+
"lib/vlad/passenger.rb",
|
56
|
+
"lib/vlad/perforce.rb",
|
57
|
+
"lib/vlad/subversion.rb",
|
58
|
+
"lib/vlad/thin.rb",
|
59
|
+
"lib/vlad_test_case.rb",
|
60
|
+
"test/test_rake_remote_task.rb",
|
61
|
+
"test/test_vlad.rb",
|
62
|
+
"test/test_vlad_git.rb",
|
63
|
+
"test/test_vlad_mercurial.rb",
|
64
|
+
"test/test_vlad_perforce.rb",
|
65
|
+
"test/test_vlad_subversion.rb",
|
66
|
+
"vladdemo.sh"
|
67
|
+
]
|
68
|
+
s.homepage = %q{http://github.com/ktheory/vlad}
|
69
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
70
|
+
s.require_paths = ["lib"]
|
71
|
+
s.rubygems_version = %q{1.3.5}
|
72
|
+
s.summary = %q{Vlad the Deployer is pragmatic application deployment automation, without mercy [ktheory's fork]}
|
73
|
+
s.test_files = [
|
74
|
+
"test/test_rake_remote_task.rb",
|
75
|
+
"test/test_vlad.rb",
|
76
|
+
"test/test_vlad_git.rb",
|
77
|
+
"test/test_vlad_mercurial.rb",
|
78
|
+
"test/test_vlad_perforce.rb",
|
79
|
+
"test/test_vlad_subversion.rb"
|
80
|
+
]
|
81
|
+
|
82
|
+
if s.respond_to? :specification_version then
|
83
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
84
|
+
s.specification_version = 3
|
85
|
+
|
86
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
87
|
+
s.add_runtime_dependency(%q<rake>, ["~> 0.8.0"])
|
88
|
+
s.add_runtime_dependency(%q<open4>, ["~> 0.9.0"])
|
89
|
+
else
|
90
|
+
s.add_dependency(%q<rake>, ["~> 0.8.0"])
|
91
|
+
s.add_dependency(%q<open4>, ["~> 0.9.0"])
|
92
|
+
end
|
93
|
+
else
|
94
|
+
s.add_dependency(%q<rake>, ["~> 0.8.0"])
|
95
|
+
s.add_dependency(%q<open4>, ["~> 0.9.0"])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,600 @@
|
|
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
|
+
|
179
|
+
# Add the prefix to each line
|
180
|
+
if (prefix_output)
|
181
|
+
# prefix target hostname unless prefix_output is a custom string
|
182
|
+
prefix = String === prefix_output ? prefix_output : "#{target_host}: "
|
183
|
+
|
184
|
+
# don't prefix sudo prompts
|
185
|
+
data.gsub!(/^/, prefix) unless stream == err and data =~ sudo_prompt
|
186
|
+
end
|
187
|
+
|
188
|
+
out_stream[stream].write data
|
189
|
+
|
190
|
+
if stream == err and data =~ sudo_prompt then
|
191
|
+
inn.puts sudo_password
|
192
|
+
data << "\n"
|
193
|
+
$stderr.write "\n"
|
194
|
+
end
|
195
|
+
|
196
|
+
result << data
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
unless status.success? then
|
201
|
+
raise(Vlad::CommandFailedError,
|
202
|
+
"execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
|
203
|
+
end
|
204
|
+
|
205
|
+
result.join
|
206
|
+
ensure
|
207
|
+
inn.close rescue nil
|
208
|
+
out.close rescue nil
|
209
|
+
err.close rescue nil
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Returns an Array with every host configured.
|
214
|
+
|
215
|
+
def self.all_hosts
|
216
|
+
hosts_for(roles.keys)
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# The default environment values. Used for resetting (mostly for
|
221
|
+
# tests).
|
222
|
+
|
223
|
+
def self.default_env
|
224
|
+
@@default_env
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.per_thread
|
228
|
+
@@per_thread
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# The vlad environment.
|
233
|
+
|
234
|
+
def self.env
|
235
|
+
@@env
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# Fetches environment variable +name+ from the environment using
|
240
|
+
# default +default+.
|
241
|
+
|
242
|
+
def self.fetch name, default = nil
|
243
|
+
name = name.to_s if Symbol === name
|
244
|
+
if @@env.has_key? name then
|
245
|
+
protect_env(name) do
|
246
|
+
v = @@env[name]
|
247
|
+
v = @@env[name] = v.call if Proc === v unless per_thread[name]
|
248
|
+
v = v.call if Proc === v
|
249
|
+
v
|
250
|
+
end
|
251
|
+
elsif default || default == false
|
252
|
+
v = @@env[name] = default
|
253
|
+
else
|
254
|
+
raise Vlad::FetchError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# Add host +host_name+ that belongs to +roles+. Extra arguments may
|
260
|
+
# be specified for the host as a hash as the last argument.
|
261
|
+
#
|
262
|
+
# host is the inversion of role:
|
263
|
+
#
|
264
|
+
# host 'db1.example.com', :db, :master_db
|
265
|
+
#
|
266
|
+
# Is equivalent to:
|
267
|
+
#
|
268
|
+
# role :db, 'db1.example.com'
|
269
|
+
# role :master_db, 'db1.example.com'
|
270
|
+
|
271
|
+
def self.host host_name, *roles
|
272
|
+
opts = Hash === roles.last ? roles.pop : {}
|
273
|
+
|
274
|
+
roles.each do |role_name|
|
275
|
+
role role_name, host_name, opts.dup
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
##
|
280
|
+
# Returns an Array of all hosts in +roles+.
|
281
|
+
|
282
|
+
def self.hosts_for *roles
|
283
|
+
roles.flatten.map { |r|
|
284
|
+
self.roles[r].keys
|
285
|
+
}.flatten.uniq.sort
|
286
|
+
end
|
287
|
+
|
288
|
+
def self.mandatory name, desc # :nodoc:
|
289
|
+
self.set(name) do
|
290
|
+
raise(Vlad::ConfigurationError,
|
291
|
+
"Please specify the #{desc} via the #{name.inspect} variable")
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Ensures exclusive access to +name+.
|
297
|
+
|
298
|
+
def self.protect_env name # :nodoc:
|
299
|
+
@@env_locks[name].synchronize do
|
300
|
+
yield
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Adds a remote task named +name+ with options +options+ that will
|
306
|
+
# execute +block+.
|
307
|
+
|
308
|
+
def self.remote_task name, *args, &block
|
309
|
+
options = (Hash === args.last) ? args.pop : {}
|
310
|
+
t = Rake::RemoteTask.define_task(name, *args, &block)
|
311
|
+
options[:roles] = Array options[:roles]
|
312
|
+
options[:roles] |= @@current_roles
|
313
|
+
t.options = options
|
314
|
+
t
|
315
|
+
end
|
316
|
+
|
317
|
+
##
|
318
|
+
# Ensures +name+ does not conflict with an existing method.
|
319
|
+
|
320
|
+
def self.reserved_name? name # :nodoc:
|
321
|
+
!@@env.has_key?(name.to_s) && self.respond_to?(name)
|
322
|
+
end
|
323
|
+
|
324
|
+
##
|
325
|
+
# Resets vlad, restoring all roles, tasks and environment variables
|
326
|
+
# to the defaults.
|
327
|
+
|
328
|
+
def self.reset
|
329
|
+
@@def_role_hash = {} # official default role value
|
330
|
+
@@env = {}
|
331
|
+
@@tasks = {}
|
332
|
+
@@roles = Hash.new { |h,k| h[k] = @@def_role_hash }
|
333
|
+
@@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
|
334
|
+
|
335
|
+
@@default_env.each do |k,v|
|
336
|
+
case v
|
337
|
+
when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
|
338
|
+
@@env[k] = v
|
339
|
+
else
|
340
|
+
@@env[k] = v.dup
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# Adds role +role_name+ with +host+ and +args+ for that host.
|
347
|
+
# TODO: merge:
|
348
|
+
# Declare a role and assign a remote host to it. Equivalent to the
|
349
|
+
# <tt>host</tt> method; provided for capistrano compatibility.
|
350
|
+
|
351
|
+
def self.role role_name, host = nil, args = {}
|
352
|
+
if block_given? then
|
353
|
+
raise ArgumentError, 'host not allowed with block' unless host.nil?
|
354
|
+
|
355
|
+
begin
|
356
|
+
current_roles << role_name
|
357
|
+
yield
|
358
|
+
ensure
|
359
|
+
current_roles.delete role_name
|
360
|
+
end
|
361
|
+
else
|
362
|
+
raise ArgumentError, 'host required' if host.nil?
|
363
|
+
|
364
|
+
[*host].each do |hst|
|
365
|
+
raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty?
|
366
|
+
end
|
367
|
+
@@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name]
|
368
|
+
@@roles[role_name][host] = args
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# The configured roles.
|
374
|
+
|
375
|
+
def self.roles
|
376
|
+
host domain, :app, :web, :db if @@roles.empty?
|
377
|
+
|
378
|
+
@@roles
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# Set environment variable +name+ to +value+ or +default_block+.
|
383
|
+
#
|
384
|
+
# If +default_block+ is defined, the block will be executed the
|
385
|
+
# first time the variable is fetched, and the value will be used for
|
386
|
+
# every subsequent fetch.
|
387
|
+
|
388
|
+
def self.set name, value = nil, &default_block
|
389
|
+
raise ArgumentError, "cannot provide both a value and a block" if
|
390
|
+
value and default_block unless
|
391
|
+
value == :per_thread
|
392
|
+
raise ArgumentError, "cannot set reserved name: '#{name}'" if
|
393
|
+
Rake::RemoteTask.reserved_name?(name) unless $TESTING
|
394
|
+
|
395
|
+
name = name.to_s
|
396
|
+
|
397
|
+
Rake::RemoteTask.per_thread[name] = true if
|
398
|
+
default_block && value == :per_thread
|
399
|
+
|
400
|
+
Rake::RemoteTask.default_env[name] = Rake::RemoteTask.env[name] =
|
401
|
+
default_block || value
|
402
|
+
|
403
|
+
Object.send :define_method, name do
|
404
|
+
Rake::RemoteTask.fetch name
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
##
|
409
|
+
# Sets all the default values. Should only be called once. Use reset
|
410
|
+
# if you need to restore values.
|
411
|
+
|
412
|
+
def self.set_defaults
|
413
|
+
@@default_env ||= {}
|
414
|
+
@@per_thread ||= {}
|
415
|
+
self.reset
|
416
|
+
|
417
|
+
mandatory :repository, "repository path"
|
418
|
+
mandatory :deploy_to, "deploy path"
|
419
|
+
mandatory :domain, "server domain"
|
420
|
+
|
421
|
+
simple_set(:deploy_timestamped, true,
|
422
|
+
:deploy_via, :export,
|
423
|
+
:keep_releases, 5,
|
424
|
+
:migrate_args, "",
|
425
|
+
:migrate_target, :latest,
|
426
|
+
:rails_env, "production",
|
427
|
+
:rake_cmd, "rake",
|
428
|
+
:revision, "head",
|
429
|
+
:rsync_cmd, "rsync",
|
430
|
+
:rsync_flags, ['-azP', '--delete'],
|
431
|
+
:ssh_cmd, "ssh",
|
432
|
+
:ssh_flags, [],
|
433
|
+
:sudo_cmd, "sudo",
|
434
|
+
:sudo_flags, ['-p Password:'],
|
435
|
+
:sudo_prompt, /^Password:/,
|
436
|
+
:umask, '02',
|
437
|
+
:prefix_output, false)
|
438
|
+
|
439
|
+
set(:current_release) { File.join(releases_path, releases[-1]) }
|
440
|
+
set(:latest_release) { deploy_timestamped ?release_path: current_release }
|
441
|
+
set(:previous_release) { File.join(releases_path, releases[-2]) }
|
442
|
+
set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
|
443
|
+
set(:release_path) { File.join(releases_path, release_name) }
|
444
|
+
set(:releases) { task.run("ls -x #{releases_path}").split.sort }
|
445
|
+
|
446
|
+
set_path :current_path, "current"
|
447
|
+
set_path :releases_path, "releases"
|
448
|
+
set_path :scm_path, "scm"
|
449
|
+
set_path :shared_path, "shared"
|
450
|
+
|
451
|
+
set(:sudo_password) do
|
452
|
+
state = `stty -g`
|
453
|
+
|
454
|
+
raise Vlad::Error, "stty(1) not found" unless $?.success?
|
455
|
+
|
456
|
+
begin
|
457
|
+
system "stty -echo"
|
458
|
+
$stdout.print "sudo password: "
|
459
|
+
$stdout.flush
|
460
|
+
sudo_password = $stdin.gets
|
461
|
+
$stdout.puts
|
462
|
+
ensure
|
463
|
+
system "stty #{state}"
|
464
|
+
end
|
465
|
+
sudo_password
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def self.set_path(name, subdir) # :nodoc:
|
470
|
+
set(name) { File.join(deploy_to, subdir) }
|
471
|
+
end
|
472
|
+
|
473
|
+
def self.simple_set(*args) # :nodoc:
|
474
|
+
args = Hash[*args]
|
475
|
+
args.each do |k, v|
|
476
|
+
set k, v
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
##
|
481
|
+
# The Rake::RemoteTask executing in this Thread.
|
482
|
+
|
483
|
+
def self.task
|
484
|
+
Thread.current[:task]
|
485
|
+
end
|
486
|
+
|
487
|
+
##
|
488
|
+
# The configured Rake::RemoteTasks.
|
489
|
+
|
490
|
+
def self.tasks
|
491
|
+
@@tasks
|
492
|
+
end
|
493
|
+
|
494
|
+
##
|
495
|
+
# Execute +command+ under sudo using run.
|
496
|
+
|
497
|
+
def sudo command
|
498
|
+
run [sudo_cmd, sudo_flags, command].flatten.compact.join(" ")
|
499
|
+
end
|
500
|
+
|
501
|
+
##
|
502
|
+
# The hosts this task will execute on. The hosts are determined from
|
503
|
+
# the role this task belongs to.
|
504
|
+
#
|
505
|
+
# The target hosts may be overridden by providing a comma-separated
|
506
|
+
# list of commands to the HOSTS environment variable:
|
507
|
+
#
|
508
|
+
# rake my_task HOSTS=app1.example.com,app2.example.com
|
509
|
+
|
510
|
+
def target_hosts
|
511
|
+
if hosts = ENV["HOSTS"] then
|
512
|
+
hosts.strip.gsub(/\s+/, '').split(",")
|
513
|
+
else
|
514
|
+
roles = Array options[:roles]
|
515
|
+
|
516
|
+
if roles.empty? then
|
517
|
+
Rake::RemoteTask.all_hosts
|
518
|
+
else
|
519
|
+
Rake::RemoteTask.hosts_for roles
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
##
|
525
|
+
# Similar to target_hosts, but returns true if user defined any hosts, even
|
526
|
+
# an empty list.
|
527
|
+
|
528
|
+
def defined_target_hosts?
|
529
|
+
return true if ENV["HOSTS"]
|
530
|
+
roles = Array options[:roles]
|
531
|
+
return true if roles.empty?
|
532
|
+
# borrowed from hosts_for:
|
533
|
+
roles.flatten.each { |r|
|
534
|
+
return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r]
|
535
|
+
}
|
536
|
+
return false
|
537
|
+
end
|
538
|
+
|
539
|
+
##
|
540
|
+
# Action is used to run a task's remote_actions in parallel on each
|
541
|
+
# of its hosts. Actions are created automatically in
|
542
|
+
# Rake::RemoteTask#enhance.
|
543
|
+
|
544
|
+
class Action
|
545
|
+
|
546
|
+
##
|
547
|
+
# The task this action is attached to.
|
548
|
+
|
549
|
+
attr_reader :task
|
550
|
+
|
551
|
+
##
|
552
|
+
# The block this action will execute.
|
553
|
+
|
554
|
+
attr_reader :block
|
555
|
+
|
556
|
+
##
|
557
|
+
# An Array of threads, one for each host this action executes on.
|
558
|
+
|
559
|
+
attr_reader :workers
|
560
|
+
|
561
|
+
##
|
562
|
+
# Creates a new Action that will run +block+ for +task+.
|
563
|
+
|
564
|
+
def initialize task, block
|
565
|
+
@task = task
|
566
|
+
@block = block
|
567
|
+
@workers = ThreadGroup.new
|
568
|
+
end
|
569
|
+
|
570
|
+
def == other # :nodoc:
|
571
|
+
return false unless Action === other
|
572
|
+
block == other.block && task == other.task
|
573
|
+
end
|
574
|
+
|
575
|
+
##
|
576
|
+
# Execute this action on +hosts+ in parallel. Returns when block
|
577
|
+
# has completed for each host.
|
578
|
+
|
579
|
+
def execute hosts, task, args
|
580
|
+
hosts.each do |host|
|
581
|
+
t = task.clone
|
582
|
+
t.target_host = host
|
583
|
+
thread = Thread.new(t) do |task|
|
584
|
+
Thread.current[:task] = task
|
585
|
+
case block.arity
|
586
|
+
when 1
|
587
|
+
block.call task
|
588
|
+
else
|
589
|
+
block.call task, args
|
590
|
+
end
|
591
|
+
Thread.current[:task] = nil
|
592
|
+
end
|
593
|
+
@workers.add thread
|
594
|
+
end
|
595
|
+
@workers.list.each { |thr| thr.join }
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
Rake::RemoteTask.set_defaults
|