yad 0.0.4

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.
@@ -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