yad 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ doc/rdoc
5
+ pkg
6
+ *#*#
7
+ .#*#
8
+ .#*
9
+ wip
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Don Barlow
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ = Yad: Yet Another Deployer
2
+
3
+ == Description
4
+
5
+ Vlad the Deployer by Ryan Davis, Eric Hodel and Wilson Bilkovich is
6
+ great, but it is not exactly what I wanted. The underlying
7
+ Rake::RemoteTask is awesome, so I wanted to keep that. However, I
8
+ wanted to change how the recipes work. Therefore, I created yet
9
+ another deployer, Yad.
10
+
11
+ In general, Yad is a deployer for database-backed applications in the
12
+ style of Capistrano by Jamis Buck. However, in addition to deploying
13
+ Rails web applications, it can be used to deploy applications that do
14
+ not have a web tier and work from a cron task or something
15
+ of that nature.
16
+
17
+ Yad works by setting up a a core set of tasks that do nothing by
18
+ default. It is up to you to define delegates that will take the
19
+ actions specific for your application.
20
+
21
+ http://sites.google.com/a/perrysburghacker.com/yad/_/rsrc/1247934950955/Home/yad.png
22
+
23
+ At this time, the following delegates are supported:
24
+
25
+ ==== Source Code Managers
26
+ * Git
27
+ ==== Database Managers
28
+ * None
29
+ * Rails
30
+ ==== Frameworks
31
+ * None
32
+ * Rails
33
+ ==== Application Servers
34
+ * Passenger
35
+ ==== Maintenance Page
36
+ * Shared System
37
+ (i.e. maintenance.html in the shared/config directory copied to the shared/system
38
+ directory to be seen by your web server)
39
+ ==== Web Servers
40
+ * Apache
41
+
42
+
43
+ == Installation
44
+
45
+ sudo gem install yad
46
+ or
47
+ sudo gem install ottobar-yad
48
+
49
+
50
+ == Examples
51
+
52
+ === Single stage with git, rails and passenger
53
+
54
+ * Create a deploy.rake file:
55
+
56
+ # #{Rails.root}/lib/tasks/deploy.rake
57
+ begin
58
+ require 'yad'
59
+
60
+ set :application, "myapp"
61
+ set :repository, "path/to/my/repository/#{application}.git"
62
+ set :domain, "mydomain.com"
63
+ set :deploy_to "/home/deploy/projects/#{application}"
64
+
65
+ set :scm, :git
66
+ set :framework, :rails
67
+ # assumes that you have a config/database_production.yml file in your repository
68
+ set :framework_update_db_config_via, :copy
69
+ set :db, :rails
70
+ set :app, :passenger
71
+
72
+ desc "Deploy a new version of the application"
73
+ task "yad:deploy" => %w(yad:update yad:start_app yad:cleanup)
74
+
75
+ desc "Deploy a new version of the application and run database migrations"
76
+ task "yad:deploy_with_migrations" => %w(yad:update yad:migrate_db yad:start_app yad:cleanup)
77
+
78
+ rescue LoadError
79
+ puts "Yad not available. Install it with: sudo gem install yad"
80
+ end
81
+
82
+ * For initial deployment, run the following tasks:
83
+
84
+ rake yad:setup
85
+ rake yad:update
86
+ rake yad:create_db
87
+ rake yad:migrate_db
88
+
89
+ * Then, configure or otherwise set up your web server
90
+
91
+ * For subsequent releases, run:
92
+
93
+ rake yad:deploy
94
+ or
95
+ rake yad:deploy_with_migrations
96
+
97
+ --
98
+ see the FAQ[link:files/doc/faq_rdoc.html] for more details
99
+ ++
100
+
101
+ == Links
102
+
103
+ * http://github.com/ottobar/yad/tree/master
104
+ * http://yad.rubyforge.org
105
+ * http://rubyforge.org/projects/yad
106
+
107
+
108
+ == Copyright
109
+
110
+ Copyright (c) 2009 Don Barlow. See LICENSE for details.
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "yad"
8
+ gem.description = "Yad: Yet Another Deployer"
9
+ gem.summary = %Q{Yet another deployer, pretty much stolen from Vlad}
10
+ gem.email = "ottobar@perryburghacker.com"
11
+ gem.homepage = "http://yad.rubyforge.org"
12
+ gem.authors = ["Don Barlow"]
13
+ gem.rubyforge_project = "yad"
14
+ gem.add_dependency('rake', '~> 0.8.7')
15
+ gem.add_dependency('open4', '~> 0.9.6')
16
+ gem.add_development_dependency('voloko-sdoc', '~> 0.2.12.1')
17
+ gem.add_development_dependency('technicalpickles-jeweler', '~> 1.0.1')
18
+
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
24
+
25
+ require 'spec/rake/spectask'
26
+ Spec::Rake::SpecTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ task :default => :spec
38
+
39
+ require 'rake/rdoctask'
40
+ begin
41
+ require 'sdoc'
42
+ Rake::RDocTask.new('doc') do |rdoc|
43
+ if File.exist?('VERSION.yml')
44
+ config = YAML.load(File.read('VERSION.yml'))
45
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
46
+ else
47
+ version = ""
48
+ end
49
+
50
+ rdoc.rdoc_dir = 'doc/rdoc'
51
+ rdoc.title = "Yad: Yet Another Deployer v#{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ rdoc.rdoc_files.include('LICENSE')
55
+ rdoc.rdoc_files.include('doc/faq.rdoc')
56
+ rdoc.main = 'README.rdoc'
57
+ rdoc.template = 'direct'
58
+ end
59
+ rescue LoadError
60
+ puts "Sdoc not available. Install it with: sudo gem install voloko-sdoc -s http://gems.github.com"
61
+ end
62
+
63
+ begin
64
+ require 'rake/contrib/sshpublisher'
65
+ namespace :rubyforge do
66
+
67
+ desc "Release gem and RDoc documentation to RubyForge"
68
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
69
+
70
+ namespace :release do
71
+ desc "Publish RDoc to RubyForge."
72
+ task :docs => [:doc] do
73
+ config = YAML.load(
74
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
75
+ )
76
+
77
+ host = "#{config['username']}@rubyforge.org"
78
+ remote_dir = "/var/www/gforge-projects/yad/"
79
+ local_dir = 'doc/rdoc'
80
+
81
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
82
+ end
83
+ end
84
+ end
85
+ rescue LoadError
86
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
87
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 0
4
+ :patch: 4
@@ -0,0 +1,9 @@
1
+ = Title here
2
+
3
+ and a paragraph here
4
+ and then such
5
+
6
+ == something else
7
+
8
+ * one thing
9
+ * thing 2
File without changes
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'yad'
3
+
4
+ require 'spec/expectations'
5
+
6
+ World do |world|
7
+
8
+ world
9
+ end
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
@@ -0,0 +1,66 @@
1
+ module Rake
2
+ module TaskManager
3
+ # This gives us access to the tasks already defined in rake.
4
+ def all_tasks
5
+ @tasks
6
+ end
7
+ end
8
+
9
+ # Simple shortcut for Rake.application.all_tasks
10
+ def self.all_tasks
11
+ Rake.application.all_tasks
12
+ end
13
+
14
+ # Hooks into rake and allows us to clear out a task by name or
15
+ # regexp. Use this if you want to completely override a task instead
16
+ # of extend it.
17
+ def self.clear_tasks(*tasks)
18
+ tasks.flatten.each do |name|
19
+ case name
20
+ when Regexp then
21
+ all_tasks.delete_if { |k,_| k =~ name }
22
+ else
23
+ all_tasks.delete(name)
24
+ end
25
+ end
26
+ end
27
+
28
+ # Removes the last action added to a task. Use this when two
29
+ # libraries define the same task and you only want one of the
30
+ # actions.
31
+ #
32
+ # require 'hoe'
33
+ # require 'tasks/rails'
34
+ # Rake.undo("test") # rolls out rails' test task
35
+ def self.undo(*names)
36
+ names.each do |name|
37
+ all_tasks[name].actions.delete_at(-1)
38
+ end
39
+ end
40
+
41
+ class Task
42
+ # Invokes the task named +task_name+ if the +module_name+ has been
43
+ # defined. It is the mechanism for delegating commands out of the
44
+ # core to modules specific for your application
45
+ def self.invoke_if_defined(task_name, module_name, error_message = nil)
46
+ module_value = Rake::RemoteTask.fetch(module_name, false)
47
+ if module_value
48
+ # define delegated tasks
49
+ eval("Yad::#{module_name.to_s.classify}::#{module_value.to_s.classify}.define_tasks")
50
+ Rake::Task[task_name].invoke
51
+ elsif error_message
52
+ raise ArgumentError, error_message
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ if Gem::Version.new(RAKEVERSION) < Gem::Version.new('0.8') then
59
+ class Rake::Task
60
+ alias task_original_execute execute
61
+
62
+ def execute(args = nil)
63
+ task_original_execute
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,521 @@
1
+ $TESTING ||= false
2
+ $TRACE = Rake.application.options.trace
3
+ $-w = true if $TRACE
4
+
5
+ def export receiver, *methods
6
+ methods.each do |method|
7
+ eval "def #{method} *args, &block; #{receiver}.#{method}(*args, &block);end"
8
+ end
9
+ end
10
+
11
+ export "Thread.current[:task]", :get, :put, :rsync, :run, :sudo, :target_host
12
+ export "Rake::RemoteTask", :host, :remote_task, :role, :set
13
+
14
+ # Rake::RemoteTask is a subclass of Rake::Task that adds
15
+ # remote_actions that execute in parallel on multiple hosts via ssh.
16
+ class Rake::RemoteTask < Rake::Task
17
+ # Base error class for all errors.
18
+ class Error < RuntimeError; end
19
+
20
+ # Raised when you have incorrect configuration.
21
+ class ConfigurationError < Error; end
22
+
23
+ # Raised when a remote command fails.
24
+ class CommandFailedError < Error; end
25
+
26
+ # Raised when an environment variable hasn't been set.
27
+ class FetchError < Error; end
28
+
29
+ @@current_roles = []
30
+
31
+ include Open4
32
+
33
+ # Options for execution of this task.
34
+ attr_accessor :options
35
+
36
+ # The host this task is running on during execution.
37
+ attr_accessor :target_host
38
+
39
+ # An Array of Actions this host will perform during execution. Use
40
+ # enhance to add new actions to a task.
41
+ attr_reader :remote_actions
42
+
43
+ def self.current_roles
44
+ @@current_roles
45
+ end
46
+
47
+ # Create a new task named +task_name+ attached to Rake::Application +app+.
48
+ def initialize(task_name, app)
49
+ super
50
+ @remote_actions = []
51
+ end
52
+
53
+ # Add a local action to this task. This calls Rake::Task#enhance.
54
+ alias_method :original_enhance, :enhance
55
+
56
+ # Add remote action +block+ to this task with dependencies +deps+. See
57
+ # Rake::Task#enhance.
58
+ def enhance(deps=nil, &block)
59
+ original_enhance(deps) # can't use super because block passed regardless.
60
+ @remote_actions << Action.new(self, block) if block_given?
61
+ self
62
+ end
63
+
64
+ # Execute this action. Local actions will be performed first, then remote
65
+ # actions will be performed in parallel on each host configured for this
66
+ # RemoteTask.
67
+ def execute(args = nil)
68
+ raise(Rake::RemoteTask::ConfigurationError,
69
+ "No target hosts specified on task #{self.name} for roles #{options[:roles].inspect}") if
70
+ ! defined_target_hosts?
71
+
72
+ super args
73
+
74
+ @remote_actions.each { |act| act.execute(target_hosts, self, args) }
75
+ end
76
+
77
+ # Pulls +files+ from the +target_host+ using rsync to +local_directory+.
78
+ def get(local_directory, *files)
79
+ rsync(files.map { |f| "#{target_host}:#{f}" }, local_directory)
80
+ end
81
+
82
+ # Copys a (usually generated) file to +remote_path+. Contents of block
83
+ # are copied to +remote_path+ and you may specify an optional
84
+ # base_name for the tempfile (aids in debugging).
85
+ def put(remote_path, base_name = File.basename(remote_path))
86
+ require 'tempfile'
87
+ Tempfile.open(base_name) do |fp|
88
+ fp.puts yield
89
+ fp.flush
90
+ rsync(fp.path, "#{target_host}:#{remote_path}")
91
+ end
92
+ end
93
+
94
+ # Use rsync to send +local+ to +remote+.
95
+ def rsync(local, remote)
96
+ cmd = [rsync_cmd, rsync_flags, local, remote].flatten.compact
97
+ cmdstr = cmd.join(' ')
98
+
99
+ warn cmdstr if $TRACE
100
+
101
+ success = system(*cmd)
102
+
103
+ unless success then
104
+ raise Rake::RemoteTask::CommandFailedError, "execution failed: #{cmdstr}"
105
+ end
106
+ end
107
+
108
+ # Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
109
+ # sudo password will be prompted for then saved for subsequent sudo commands.
110
+ def run(command)
111
+ cmd = [ssh_cmd, ssh_flags, target_host, command].flatten
112
+ result = []
113
+
114
+ trace = [ssh_cmd, ssh_flags, target_host, "'#{command}'"].flatten.join(' ')
115
+ warn trace if $TRACE
116
+
117
+ pid, inn, out, err = popen4(*cmd)
118
+
119
+ inn.sync = true
120
+ streams = [out, err]
121
+ out_stream = {
122
+ out => $stdout,
123
+ err => $stderr,
124
+ }
125
+
126
+ # Handle process termination ourselves
127
+ status = nil
128
+ Thread.start do
129
+ status = Process.waitpid2(pid).last
130
+ end
131
+
132
+ until streams.empty? do
133
+ # don't busy loop
134
+ selected, = select streams, nil, nil, 0.1
135
+
136
+ next if selected.nil? or selected.empty?
137
+
138
+ selected.each do |stream|
139
+ if stream.eof? then
140
+ streams.delete stream if status # we've quit, so no more writing
141
+ next
142
+ end
143
+
144
+ data = stream.readpartial(1024)
145
+ out_stream[stream].write data
146
+
147
+ if stream == err and data =~ sudo_prompt then
148
+ inn.puts sudo_password
149
+ data << "\n"
150
+ $stderr.write "\n"
151
+ end
152
+
153
+ result << data
154
+ end
155
+ end
156
+
157
+ unless status.success? then
158
+ raise(Rake::RemoteTask::CommandFailedError,
159
+ "execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
160
+ end
161
+
162
+ result.join
163
+ ensure
164
+ inn.close rescue nil
165
+ out.close rescue nil
166
+ err.close rescue nil
167
+ end
168
+
169
+ # Returns an Array with every host configured.
170
+ def self.all_hosts
171
+ hosts_for(roles.keys)
172
+ end
173
+
174
+ # The default environment values. Used for resetting (mostly for
175
+ # tests).
176
+ def self.default_env
177
+ @@default_env
178
+ end
179
+
180
+ def self.per_thread
181
+ @@per_thread
182
+ end
183
+
184
+ # The remote task environment.
185
+ def self.env
186
+ @@env
187
+ end
188
+
189
+ # Fetches environment variable +name+ from the environment using
190
+ # default +default+.
191
+ def self.fetch(name, default = nil)
192
+ name = name.to_s if Symbol === name
193
+ if @@env.has_key? name then
194
+ protect_env(name) do
195
+ v = @@env[name]
196
+ v = @@env[name] = v.call if Proc === v unless per_thread[name]
197
+ v = v.call if Proc === v
198
+ v
199
+ end
200
+ elsif default || default == false
201
+ v = @@env[name] = default
202
+ else
203
+ raise Rake::RemoteTask::FetchError
204
+ end
205
+ end
206
+
207
+ # Fetches a set of options and turns it into a hash to be passed to
208
+ # the application specific delegate tasks.
209
+ def self.get_options_hash(*options)
210
+ options_hash = {}
211
+ options.each do |option|
212
+ option_value = Rake::RemoteTask.fetch(option, false)
213
+ options_hash[option] = option_value if option_value
214
+ end
215
+ options_hash
216
+ end
217
+
218
+ # Add host +host_name+ that belongs to +roles+. Extra arguments may
219
+ # be specified for the host as a hash as the last argument.
220
+ #
221
+ # host is the inversion of role:
222
+ #
223
+ # host 'db1.example.com', :db, :master_db
224
+ #
225
+ # Is equivalent to:
226
+ #
227
+ # role :db, 'db1.example.com'
228
+ # role :master_db, 'db1.example.com'
229
+ def self.host(host_name, *roles)
230
+ opts = Hash === roles.last ? roles.pop : {}
231
+
232
+ roles.each do |role_name|
233
+ role role_name, host_name, opts.dup
234
+ end
235
+ end
236
+
237
+ # Returns an Array of all hosts in +roles+.
238
+ def self.hosts_for(*roles)
239
+ roles.flatten.map { |r|
240
+ self.roles[r].keys
241
+ }.flatten.uniq.sort
242
+ end
243
+
244
+ def self.mandatory(name, desc) # :nodoc:
245
+ self.set(name) do
246
+ raise(Rake::RemoteTask::ConfigurationError,
247
+ "Please specify the #{desc} via the #{name.inspect} variable")
248
+ end
249
+ end
250
+
251
+ # Ensures exclusive access to +name+.
252
+ def self.protect_env(name) # :nodoc:
253
+ @@env_locks[name].synchronize do
254
+ yield
255
+ end
256
+ end
257
+
258
+ # Adds a remote task named +name+ with options +options+ that will
259
+ # execute +block+.
260
+ def self.remote_task(name, *args, &block)
261
+ options = (Hash === args.last) ? args.pop : {}
262
+ t = Rake::RemoteTask.define_task(name, *args, &block)
263
+ options[:roles] = Array options[:roles]
264
+ options[:roles] |= @@current_roles
265
+ t.options = options
266
+ t
267
+ end
268
+
269
+ # Ensures +name+ does not conflict with an existing method.
270
+ def self.reserved_name?(name) # :nodoc:
271
+ !@@env.has_key?(name.to_s) && self.respond_to?(name)
272
+ end
273
+
274
+ # Resets vlad, restoring all roles, tasks and environment variables
275
+ # to the defaults.
276
+ def self.reset
277
+ @@def_role_hash = {} # official default role value
278
+ @@env = {}
279
+ @@tasks = {}
280
+ @@roles = Hash.new { |h,k| h[k] = @@def_role_hash }
281
+ @@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
282
+
283
+ @@default_env.each do |k,v|
284
+ case v
285
+ when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah.
286
+ @@env[k] = v
287
+ else
288
+ @@env[k] = v.dup
289
+ end
290
+ end
291
+ end
292
+
293
+ # Adds role +role_name+ with +host+ and +args+ for that host.
294
+ def self.role(role_name, host = nil, args = {})
295
+ if block_given? then
296
+ raise ArgumentError, 'host not allowed with block' unless host.nil?
297
+
298
+ begin
299
+ current_roles << role_name
300
+ yield
301
+ ensure
302
+ current_roles.delete role_name
303
+ end
304
+ else
305
+ raise ArgumentError, 'host required' if host.nil?
306
+
307
+ [*host].each do |hst|
308
+ raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty?
309
+ end
310
+ @@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name]
311
+ @@roles[role_name][host] = args
312
+ end
313
+ end
314
+
315
+ # The configured roles.
316
+ def self.roles
317
+ host domain, :app, :web, :db if @@roles.empty?
318
+
319
+ @@roles
320
+ end
321
+
322
+ # Set environment variable +name+ to +value+ or +default_block+.
323
+ #
324
+ # If +default_block+ is defined, the block will be executed the
325
+ # first time the variable is fetched, and the value will be used for
326
+ # every subsequent fetch.
327
+ def self.set(name, value = nil, &default_block)
328
+ raise ArgumentError, "cannot provide both a value and a block" if
329
+ value and default_block unless
330
+ value == :per_thread
331
+ raise ArgumentError, "cannot set reserved name: '#{name}'" if
332
+ Rake::RemoteTask.reserved_name?(name) unless $TESTING
333
+
334
+ name = name.to_s
335
+
336
+ Rake::RemoteTask.per_thread[name] = true if
337
+ default_block && value == :per_thread
338
+
339
+ Rake::RemoteTask.default_env[name] = Rake::RemoteTask.env[name] =
340
+ default_block || value
341
+
342
+ Object.send :define_method, name do
343
+ Rake::RemoteTask.fetch name
344
+ end
345
+ end
346
+
347
+ # Sets all the default values. Should only be called once. Use reset
348
+ # if you need to restore values.
349
+ def self.set_defaults
350
+ @@default_env ||= {}
351
+ @@per_thread ||= {}
352
+ self.reset
353
+
354
+ mandatory :repository, "repository path"
355
+ mandatory :deploy_to, "deploy path"
356
+ mandatory :domain, "server domain"
357
+
358
+ simple_set(:app_env, "production",
359
+ :deploy_timestamped, true,
360
+ :keep_releases, 5,
361
+ :rake_cmd, "rake",
362
+ :rsync_cmd, "rsync",
363
+ :rsync_flags, ['-azP', '--delete'],
364
+ :ssh_cmd, "ssh",
365
+ :ssh_flags, [],
366
+ :sudo_cmd, "sudo",
367
+ :sudo_flags, ['-p Password:'],
368
+ :sudo_prompt, /^Password:/)
369
+
370
+ set(:latest_release) { deploy_timestamped ? File.join(releases_path, releases[-1]) : releases_path }
371
+ set(:previous_release) do
372
+ if deploy_timestamped
373
+ if releases[-2]
374
+ File.join(releases_path, releases[-2])
375
+ else
376
+ nil
377
+ end
378
+ else
379
+ releases_path
380
+ end
381
+ end
382
+ set(:release_name) { deploy_timestamped ? Time.now.utc.strftime("%Y%m%d%H%M%S") : nil }
383
+ set(:release_path) { release_name ? File.join(releases_path, release_name) : releases_path }
384
+ set(:releases) { task.run("ls -x #{releases_path}").split.sort }
385
+
386
+ set_path :current_path, "current"
387
+ set_path :releases_path, "releases"
388
+ set_path :scm_path, "scm"
389
+ set_path :shared_path, "shared"
390
+ set_path :shared_config_path, File.join("shared", "config")
391
+
392
+ set(:sudo_password) do
393
+ state = `stty -g`
394
+
395
+ raise Rake::RemoteTask::Error, "stty(1) not found" unless $?.success?
396
+
397
+ begin
398
+ system "stty -echo"
399
+ $stdout.print "sudo password: "
400
+ $stdout.flush
401
+ sudo_password = $stdin.gets
402
+ $stdout.puts
403
+ ensure
404
+ system "stty #{state}"
405
+ end
406
+ sudo_password
407
+ end
408
+ end
409
+
410
+ def self.set_path(name, subdir) # :nodoc:
411
+ set(name) { File.join(deploy_to, subdir) }
412
+ end
413
+
414
+ def self.simple_set(*args) # :nodoc:
415
+ args = Hash[*args]
416
+ args.each do |k, v|
417
+ set k, v
418
+ end
419
+ end
420
+
421
+ # The Rake::RemoteTask executing in this Thread.
422
+ def self.task
423
+ Thread.current[:task]
424
+ end
425
+
426
+ # The configured Rake::RemoteTasks.
427
+ def self.tasks
428
+ @@tasks
429
+ end
430
+
431
+ # Execute +command+ under sudo using run.
432
+ def sudo(command)
433
+ run [sudo_cmd, sudo_flags, command].flatten.compact.join(" ")
434
+ end
435
+
436
+ # The hosts this task will execute on. The hosts are determined from
437
+ # the role this task belongs to.
438
+ #
439
+ # The target hosts may be overridden by providing a comma-separated
440
+ # list of commands to the HOSTS environment variable:
441
+ #
442
+ # rake my_task HOSTS=app1.example.com,app2.example.com
443
+ def target_hosts
444
+ if hosts = ENV["HOSTS"] then
445
+ hosts.strip.gsub(/\s+/, '').split(",")
446
+ else
447
+ roles = Array options[:roles]
448
+
449
+ if roles.empty? then
450
+ Rake::RemoteTask.all_hosts
451
+ else
452
+ Rake::RemoteTask.hosts_for roles
453
+ end
454
+ end
455
+ end
456
+
457
+ # Similar to target_hosts, but returns true if user defined any hosts, even
458
+ # an empty list.
459
+ def defined_target_hosts?
460
+ return true if ENV["HOSTS"]
461
+ roles = Array options[:roles]
462
+ return true if roles.empty?
463
+ # borrowed from hosts_for:
464
+ roles.flatten.each { |r|
465
+ return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r]
466
+ }
467
+ return false
468
+ end
469
+
470
+ # Action is used to run a task's remote_actions in parallel on each
471
+ # of its hosts. Actions are created automatically in
472
+ # Rake::RemoteTask#enhance.
473
+ class Action
474
+
475
+ # The task this action is attached to.
476
+ attr_reader :task
477
+
478
+ # The block this action will execute.
479
+ attr_reader :block
480
+
481
+ # An Array of threads, one for each host this action executes on.
482
+ attr_reader :workers
483
+
484
+ # Creates a new Action that will run +block+ for +task+.
485
+ def initialize(task, block)
486
+ @task = task
487
+ @block = block
488
+ @workers = ThreadGroup.new
489
+ end
490
+
491
+ def ==(other) # :nodoc:
492
+ return false unless Action === other
493
+ block == other.block && task == other.task
494
+ end
495
+
496
+ # Execute this action on +hosts+ in parallel. Returns when block
497
+ # has completed for each host.
498
+ def execute(hosts, task, args)
499
+ hosts.each do |host|
500
+ t = task.clone
501
+ t.target_host = host
502
+ thread = Thread.new(t) do |task|
503
+ Thread.current.abort_on_exception = true
504
+ Thread.current[:task] = task
505
+ case block.arity
506
+ when 1
507
+ block.call task
508
+ else
509
+ block.call task, args
510
+ end
511
+ Thread.current[:task] = nil
512
+ end
513
+ @workers.add thread
514
+ end
515
+ @workers.list.each { |thr| thr.join }
516
+
517
+ end
518
+ end
519
+ end
520
+
521
+ Rake::RemoteTask.set_defaults