capistrano 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -110,6 +110,11 @@ module Capistrano
110
110
  "be specified, and are loaded in the given order."
111
111
  ) { |value| @options[:actions] << value }
112
112
 
113
+ opts.on("-f", "--file FILE",
114
+ "A recipe file to load. Multiple recipes may",
115
+ "be specified, and are loaded in the given order."
116
+ ) { |value| @options[:recipes] << value }
117
+
113
118
  opts.on("-p", "--password [PASSWORD]",
114
119
  "The password to use when connecting. If the switch",
115
120
  "is given without a password, the password will be",
@@ -119,8 +124,12 @@ module Capistrano
119
124
 
120
125
  opts.on("-r", "--recipe RECIPE",
121
126
  "A recipe file to load. Multiple recipes may",
122
- "be specified, and are loaded in the given order."
123
- ) { |value| @options[:recipes] << value }
127
+ "be specified, and are loaded in the given order.",
128
+ "(This option is deprecated--please use -f instead)"
129
+ ) do |value|
130
+ warn "Deprecated -r/--recipe flag used. Please use -f instead"
131
+ @options[:recipes] << value
132
+ end
124
133
 
125
134
  opts.on("-s", "--set NAME=VALUE",
126
135
  "Specify a variable and it's value to set. This",
@@ -188,13 +197,14 @@ You can use the --apply-to switch to generate a minimal set of capistrano
188
197
  scripts and recipes for an application. Just specify the path to the application
189
198
  as the argument to --apply-to, like this:
190
199
 
191
- capistrano --apply-to ~/projects/myapp
200
+ cap --apply-to ~/projects/myapp
192
201
 
193
- You'll wind up with a sample deployment recipe in config/deploy.rb, some new
194
- rake tasks in config/tasks, and a capistrano script in your script directory.
202
+ You'll wind up with a sample deployment recipe in config/deploy.rb and some new
203
+ rake tasks in config/tasks.
195
204
 
196
205
  (Currently, --apply-to only works with Rails applications.)
197
206
  DETAIL
207
+ #' # vim syntax highlighting fix
198
208
 
199
209
  if args.empty?
200
210
  puts opts
@@ -217,10 +227,10 @@ DETAIL
217
227
 
218
228
  # Beginning running Capistrano based on the configured options.
219
229
  def execute!
220
- if !@options[:recipes].empty?
221
- execute_recipes!
222
- elsif @options[:apply_to]
230
+ if @options[:apply_to]
223
231
  execute_apply_to!
232
+ else
233
+ execute_recipes!
224
234
  end
225
235
  end
226
236
 
@@ -271,7 +281,6 @@ DETAIL
271
281
  elsif !apply_to_given
272
282
  look_for_default_recipe_file! if @options[:recipes].empty?
273
283
  look_for_raw_actions!
274
- abort "You must specify at least one recipe" if @options[:recipes].empty?
275
284
  abort "You must specify at least one action" if @options[:actions].empty?
276
285
  else
277
286
  @options[:application] = args.shift
@@ -48,6 +48,14 @@ module Capistrano
48
48
  self
49
49
  end
50
50
 
51
+ # Force the command to stop processing, by closing all open channels
52
+ # associated with this command.
53
+ def stop!
54
+ @channels.each do |ch|
55
+ ch.close unless ch[:closed]
56
+ end
57
+ end
58
+
51
59
  private
52
60
 
53
61
  def open_channels
@@ -150,7 +150,7 @@ module Capistrano
150
150
  instance_eval(options[:string], options[:name] || "<eval>")
151
151
 
152
152
  elsif options[:proc]
153
- logger.trace "loading configuration #{options[:proc].inspect}"
153
+ logger.trace "loading configuration #{eval("__FILE__", options[:proc])}"
154
154
  instance_eval(&options[:proc])
155
155
 
156
156
  else
@@ -31,28 +31,23 @@ module Capistrano
31
31
  def initialize(server, config) #:nodoc:
32
32
  @config = config
33
33
  @pending_forward_requests = {}
34
- @mutex = Mutex.new
35
34
  @next_port = MAX_PORT
36
35
  @terminate_thread = false
36
+ @port_guard = Mutex.new
37
37
 
38
+ mutex = Mutex.new
38
39
  waiter = ConditionVariable.new
39
40
 
40
41
  @thread = Thread.new do
41
42
  @config.logger.trace "starting connection to gateway #{server}"
42
43
  SSH.connect(server, @config) do |@session|
43
44
  @config.logger.trace "gateway connection established"
44
- @mutex.synchronize { waiter.signal }
45
- connection = @session.registry[:connection][:driver]
46
- loop do
47
- break if @terminate_thread
48
- sleep 0.1 unless connection.reader_ready?
49
- connection.process true
50
- Thread.new { process_next_pending_connection_request }
51
- end
45
+ mutex.synchronize { waiter.signal }
46
+ @session.loop { !@terminate_thread }
52
47
  end
53
48
  end
54
49
 
55
- @mutex.synchronize { waiter.wait(@mutex) }
50
+ mutex.synchronize { waiter.wait(mutex) }
56
51
  end
57
52
 
58
53
  # Shuts down all forwarded connections and terminates the gateway.
@@ -73,45 +68,36 @@ module Capistrano
73
68
  # host to the server, via the gateway, and then opens and returns a new
74
69
  # Net::SSH connection via that port.
75
70
  def connect_to(server)
76
- @mutex.synchronize do
77
- @pending_forward_requests[server] = ConditionVariable.new
78
- @pending_forward_requests[server].wait(@mutex)
79
- @pending_forward_requests.delete(server)
71
+ connection = nil
72
+ @config.logger.trace "establishing connection to #{server} via gateway"
73
+ port = next_port
74
+
75
+ thread = Thread.new do
76
+ begin
77
+ @session.forward.local(port, server, 22)
78
+ connection = SSH.connect('127.0.0.1', @config, port)
79
+ @config.logger.trace "connection to #{server} via gateway established"
80
+ rescue Errno::EADDRINUSE
81
+ port = next_port
82
+ retry
83
+ rescue Exception => e
84
+ puts e.class.name
85
+ puts e.backtrace.join("\n")
86
+ end
80
87
  end
88
+
89
+ thread.join
90
+ connection or raise "Could not establish connection to #{server}"
81
91
  end
82
92
 
83
93
  private
84
94
 
85
95
  def next_port
86
- port = @next_port
87
- @next_port -= 1
88
- @next_port = MAX_PORT if @next_port < MIN_PORT
89
- port
90
- end
91
-
92
- def process_next_pending_connection_request
93
- @mutex.synchronize do
94
- key = @pending_forward_requests.keys.detect { |k| ConditionVariable === @pending_forward_requests[k] } or return
95
- var = @pending_forward_requests[key]
96
-
97
- @config.logger.trace "establishing connection to #{key} via gateway"
98
-
99
- port = next_port
100
-
101
- begin
102
- @session.forward.local(port, key, 22)
103
- @pending_forward_requests[key] = SSH.connect('127.0.0.1', @config,
104
- port)
105
- @config.logger.trace "connection to #{key} via gateway established"
106
- rescue Errno::EADDRINUSE
107
- port = next_port
108
- retry
109
- rescue Object
110
- @pending_forward_requests[key] = nil
111
- raise
112
- ensure
113
- var.signal
114
- end
96
+ @port_guard.synchronize do
97
+ port = @next_port
98
+ @next_port -= 1
99
+ @next_port = MAX_PORT if @next_port < MIN_PORT
100
+ port
115
101
  end
116
102
  end
117
103
  end
@@ -35,10 +35,11 @@ task :show_tasks do
35
35
  end
36
36
 
37
37
  desc "Set up the expected application directory structure on all boxes"
38
- task :setup, :roles => [:app, :db, :web] do
38
+ task :setup, :except => { :no_release => true } do
39
39
  run <<-CMD
40
40
  mkdir -p -m 775 #{releases_path} #{shared_path}/system &&
41
- mkdir -p -m 777 #{shared_path}/log
41
+ mkdir -p -m 777 #{shared_path}/log &&
42
+ mkdir -p -m 777 #{shared_path}/pids
42
43
  CMD
43
44
  end
44
45
 
@@ -64,7 +65,7 @@ desc <<-DESC
64
65
  Update all servers with the latest release of the source code. All this does
65
66
  is do a checkout (as defined by the selected scm module).
66
67
  DESC
67
- task :update_code, :roles => [:app, :db, :web] do
68
+ task :update_code, :except => { :no_release => true } do
68
69
  on_rollback { delete release_path, :recursive => true }
69
70
 
70
71
  source.checkout(self)
@@ -74,13 +75,23 @@ task :update_code, :roles => [:app, :db, :web] do
74
75
  ln -nfs #{shared_path}/log #{release_path}/log &&
75
76
  ln -nfs #{shared_path}/system #{release_path}/public/system
76
77
  CMD
78
+
79
+ run <<-CMD
80
+ test -d #{shared_path}/pids &&
81
+ rm -rf #{release_path}/tmp/pids &&
82
+ ln -nfs #{shared_path}/pids #{release_path}/tmp/pids; true
83
+ CMD
84
+
85
+ # uncache the list of releases, so that the next time it is called it will
86
+ # include the newly released path.
87
+ @releases = nil
77
88
  end
78
89
 
79
90
  desc <<-DESC
80
91
  Rollback the latest checked-out version to the previous one by fixing the
81
92
  symlinks and deleting the current release from all servers.
82
93
  DESC
83
- task :rollback_code, :roles => [:app, :db, :web] do
94
+ task :rollback_code, :except => { :no_release => true } do
84
95
  if releases.length < 2
85
96
  raise "could not rollback the code because there is no prior release"
86
97
  else
@@ -95,7 +106,7 @@ desc <<-DESC
95
106
  Update the 'current' symlink to point to the latest version of
96
107
  the application's code.
97
108
  DESC
98
- task :symlink, :roles => [:app, :db, :web] do
109
+ task :symlink, :except => { :no_release => true } do
99
110
  on_rollback { run "ln -nfs #{previous_release} #{current_path}" }
100
111
  run "ln -nfs #{current_release} #{current_path}"
101
112
  end
@@ -109,6 +120,16 @@ task :restart, :roles => :app do
109
120
  send(run_method, "#{current_path}/script/process/reaper")
110
121
  end
111
122
 
123
+ desc <<-DESC
124
+ Updates the code and fixes the symlink under a transaction
125
+ DESC
126
+ task :update do
127
+ transaction do
128
+ update_code
129
+ symlink
130
+ end
131
+ end
132
+
112
133
  desc <<-DESC
113
134
  Run the migrate rake task. By default, it runs this in the version of the app
114
135
  indicated by the 'current' symlink. (This means you should not invoke this task
@@ -137,11 +158,7 @@ A macro-task that updates the code, fixes the symlink, and restarts the
137
158
  application servers.
138
159
  DESC
139
160
  task :deploy do
140
- transaction do
141
- update_code
142
- symlink
143
- end
144
-
161
+ update
145
162
  restart
146
163
  end
147
164
 
@@ -240,3 +257,14 @@ task :invoke, :roles => Capistrano.str2roles(ENV["ROLES"] || "") do
240
257
  method = ENV["SUDO"] ? :sudo : :run
241
258
  send(method, ENV["COMMAND"])
242
259
  end
260
+
261
+ desc <<-DESC
262
+ Begin an interactive Capistrano session. This gives you an interactive
263
+ terminal from which to execute tasks and commands on all of your servers.
264
+ (This is still an experimental feature, and is subject to change without
265
+ notice!)
266
+ DESC
267
+ task(:shell) do
268
+ require 'capistrano/shell'
269
+ Capistrano::Shell.run!(self)
270
+ end
@@ -28,7 +28,6 @@ module Capistrano
28
28
  private
29
29
 
30
30
  def run_checkout(actor, guts, &block)
31
- log = "#{configuration.deploy_to}/revisions.log"
32
31
  directory = File.basename(configuration.release_path)
33
32
 
34
33
  command = <<-STR
@@ -53,7 +52,7 @@ module Capistrano
53
52
  def logging_commands(directory = nil)
54
53
  log = "#{configuration.deploy_to}/revisions.log"
55
54
 
56
- "(test -e #{log} || touch #{log} && chmod 666 #{log}) && " +
55
+ "(test -e #{log} || (touch #{log} && chmod 666 #{log})) && " +
57
56
  "echo `date +\"%Y-%m-%d %H:%M:%S\"` $USER #{configuration.revision} #{directory} >> #{log};"
58
57
  end
59
58
  end
@@ -33,6 +33,11 @@ module Capistrano
33
33
  class Cvs < Base
34
34
  def initialize(configuration)
35
35
  super(configuration)
36
+
37
+ if not @configuration.respond_to?(:local) then
38
+ @configuration.set(:local,".")
39
+ end
40
+
36
41
  if not configuration.respond_to?(:branch) then
37
42
  configuration.set(:branch) { self.current_branch }
38
43
  else
@@ -0,0 +1,83 @@
1
+ require 'capistrano/scm/base'
2
+
3
+ module Capistrano
4
+ module SCM
5
+
6
+ # An SCM module for using Mercurial as your source control tool.
7
+ # You can use it by placing the following line in your configuration:
8
+ #
9
+ # set :scm, :mercurial
10
+ #
11
+ # Also, this module accepts a <tt>:mercurial</tt> configuration variable,
12
+ # which (if specified) will be used as the full path to the hg
13
+ # executable on the remote machine:
14
+ #
15
+ # set :mercurial, "/usr/local/bin/hg"
16
+ class Mercurial < Base
17
+ # Return a string identifying the tip changeset in the mercurial
18
+ # repository. Note that this fetches the tip changeset from the
19
+ # local repository, but capistrano will deploy from your _remote_
20
+ # repository. So just make sure your local repository is synchronized
21
+ # with your remote one.
22
+ def latest_revision
23
+ `#{mercurial} tip --template '{node|short}'`
24
+ end
25
+
26
+ # Return the changeset currently deployed.
27
+ def current_revision(actor)
28
+ # NOTE:
29
+ # copied almost verbatim from svn except its not cast into an int
30
+ # this should be the same for almost _every_ scm can we take it out
31
+ # of SCM-specific code?
32
+ latest = actor.releases.last
33
+ grep = %(grep " #{latest}$" #{configuration.deploy_to}/revisions.log)
34
+ result = ""
35
+ actor.run(grep, :once => true) do |ch, str, out|
36
+ result << out if str == :out
37
+ raise "could not determine current changeset" if str == :err
38
+ end
39
+
40
+ date, time, user, changeset, dir = result.split
41
+ raise "current changeset not found in revisions.log" unless dir == latest
42
+ changeset
43
+ end
44
+
45
+ # Return a string containing the diff between the two changesets. +from+
46
+ # and +to+ may be in any format that mercurial recognizes as a valid
47
+ # changeset. If +from+ is +nil+, it defaults to the last deployed
48
+ # changeset. If +to+ is +nil+, it defaults to the current working
49
+ # directory.
50
+ def diff(actor, from = current_revision(actor), to = nil)
51
+ cmd = "#{mercurial} diff -r #{from}"
52
+ cmd << " -r #{to}" if to
53
+ `#{cmd}`
54
+ end
55
+
56
+ # Check out (on all servers associated with the current task) the latest
57
+ # revision. Uses the given actor instance to execute the command. If
58
+ # mercurial asks for a password this will automatically provide it
59
+ # (assuming the requested password is the same as the password for
60
+ # logging into the remote server.) If ssh repository method is used,
61
+ # authorized keys must be setup.
62
+ def checkout(actor)
63
+ command = "#{mercurial} clone -U #{configuration.repository} " +
64
+ "#{actor.release_path} && " +
65
+ "#{mercurial} -R #{actor.release_path} update " +
66
+ "-C #{configuration.revision} &&"
67
+ run_checkout(actor, command, &hg_stream_handler(actor))
68
+ end
69
+
70
+ private
71
+ def mercurial
72
+ configuration[:mercurial] || "hg"
73
+ end
74
+
75
+ def hg_stream_handler(actor)
76
+ Proc.new do |ch, stream, out|
77
+ prefix = "#{stream} :: #{ch[:host]}"
78
+ actor.logger.info out, prefix
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -16,22 +16,14 @@ module Capistrano
16
16
  # set :svn, "/opt/local/bin/svn"
17
17
  class Subversion < Base
18
18
  # Return an integer identifying the last known revision in the svn
19
- # repository. (This integer is currently the revision number.) If latest
20
- # revision does not exist in the given repository, this routine will
21
- # walk up the directory tree until it finds it.
19
+ # repository. (This integer is currently the revision number.)
22
20
  def latest_revision
23
- configuration.logger.debug "querying latest revision..." unless @latest_revision
24
- repo = configuration.repository
25
- until @latest_revision
26
- match = svn_log(repo).scan(/r(\d+)/).first
27
- @latest_revision = match ? match.first : nil
28
- if @latest_revision.nil?
29
- # if a revision number was not reported, move up a level in the path
30
- # and try again.
31
- repo = File.dirname(repo)
21
+ @latest_revision ||= begin
22
+ configuration.logger.debug "querying latest revision..."
23
+ match = svn_log(configuration.repository).scan(/r(\d+)/).first or
24
+ raise "Could not determine latest revision"
25
+ match.first
32
26
  end
33
- end
34
- @latest_revision
35
27
  end
36
28
 
37
29
  # Return the number of the revision currently deployed.
@@ -87,13 +79,17 @@ module Capistrano
87
79
  end
88
80
 
89
81
  def svn_log(path)
90
- `svn log -q -rhead #{path}`
82
+ `svn log -q --limit 1 #{path}`
91
83
  end
92
84
 
93
85
  def svn_password
94
86
  configuration[:svn_password] || configuration[:password]
95
87
  end
96
88
 
89
+ def svn_passphrase
90
+ configuration[:svn_passphrase] || svn_password
91
+ end
92
+
97
93
  def svn_stream_handler(actor)
98
94
  Proc.new do |ch, stream, out|
99
95
  prefix = "#{stream} :: #{ch[:host]}"
@@ -105,14 +101,18 @@ module Capistrano
105
101
  actor.logger.info "subversion is asking whether to connect or not",
106
102
  prefix
107
103
  ch.send_data "yes\n"
108
- elsif out =~ %r{passphrase}
109
- message = "subversion needs your key's passphrase, sending empty string"
104
+ elsif out =~ %r{passphrase}i
105
+ message = "subversion needs your key's passphrase"
110
106
  actor.logger.info message, prefix
111
- ch.send_data "\n"
107
+ ch.send_data "#{svn_passphrase}\n"
112
108
  elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
113
109
  message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
114
110
  actor.logger.info message, prefix
115
111
  raise message
112
+ elsif out =~ %r{accept \(t\)emporarily}
113
+ message = "accepting certificate temporarily"
114
+ actor.logger.info message, prefix
115
+ ch.send_data "t\n"
116
116
  end
117
117
  end
118
118
  end