capistrano 1.2.0 → 1.3.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/CHANGELOG CHANGED
@@ -1,4 +1,34 @@
1
- *SVN*
1
+ *1.3.0* (December 23, 2006)
2
+
3
+ * Deprecate rake integration in favor of invoking `cap' directly [Jamis Buck]
4
+
5
+ * Make sure the CVS module references the repository explicitly in cvs_log [weyus@att.net]
6
+
7
+ * Remove trace messages when loading a file [Jamis Buck]
8
+
9
+ * Cleaner error messages for authentication failures and command errors [Jamis Buck]
10
+
11
+ * Added support for ~/.caprc, also -x and -c switches. [Jamis Buck]
12
+
13
+ * Updated migrate action to use db:migrate task in Rails instead of the deprecated migrate task [DHH]
14
+
15
+ * Allow SSH user and port to be encoded in the hostname strings [Ezra Zygmuntowicz]
16
+
17
+ * Fixed that new checkouts were not group-writable [DHH, Jamis Buck]
18
+
19
+ * Fixed that cap setup would use 755 on the deploy_to and shared directory roots instead of 775 [DHH]
20
+
21
+ * Don't run the cleanup task on servers marked no_release [Jamis Buck]
22
+
23
+ * Fix typo in default_io_proc so it correctly checks the stream parameter to see if it is the error stream [Stephen Haberman]
24
+
25
+ * Make sure assets in images, javascripts, and stylesheets are touched after updating the code, to ensure the asset timestamping feature of rails works correctly [Jamis Buck]
26
+
27
+ * Added warning if password is prompted for and termios is not installed [John Labovitz]
28
+
29
+ * Added :as option to sudo, so you can specify who the command is executed as [Mark Imbriaco]
30
+
31
+ *1.2.0* (September 14, 2006)
2
32
 
3
33
  * Add experimental 'shell' task [Jamis Buck]
4
34
 
@@ -38,7 +38,7 @@ module Capistrano
38
38
  self.transfer_factory = Transfer
39
39
 
40
40
  self.default_io_proc = Proc.new do |ch, stream, out|
41
- level = out == :error ? :important : :info
41
+ level = stream == :err ? :important : :info
42
42
  ch[:actor].logger.send(level, out, "#{stream} :: #{ch[:host]}")
43
43
  end
44
44
 
@@ -277,9 +277,10 @@ module Capistrano
277
277
  # in order to prevent _each host_ from prompting when the password was
278
278
  # wrong, let's track which host prompted first and only allow subsequent
279
279
  # prompts from that host.
280
- prompt_host = nil
280
+ prompt_host = nil
281
+ user = options[:as].nil? ? '' : "-u #{options[:as]}"
281
282
 
282
- run "#{sudo_command} #{command}", options do |ch, stream, out|
283
+ run "#{sudo_command} #{user} #{command}", options do |ch, stream, out|
283
284
  if out =~ /^Password:/
284
285
  ch.send_data "#{password}\n"
285
286
  elsif out =~ /try again/
@@ -16,11 +16,7 @@ module Capistrano
16
16
  # This requires the termios library to be installed (which, unfortunately,
17
17
  # is not available for Windows).
18
18
  begin
19
- if !defined?(USE_TERMIOS) || USE_TERMIOS
20
- require 'termios'
21
- else
22
- raise LoadError
23
- end
19
+ require 'termios'
24
20
 
25
21
  # Enable or disable stdin echoing to the terminal.
26
22
  def self.echo(enable)
@@ -43,6 +39,10 @@ module Capistrano
43
39
  # if termios is not available, echo suppression will not be available
44
40
  # either.
45
41
  def self.with_echo
42
+ unless @warned_about_echo
43
+ puts "WARNING: Password will echo -- install the 'termios' gem to hide your password." if !defined?(Termios) && RUBY_PLATFORM !~ /mswin/
44
+ @warned_about_echo = true
45
+ end
46
46
  echo(false)
47
47
  yield
48
48
  ensure
@@ -96,7 +96,7 @@ module Capistrano
96
96
  def initialize(args = ARGV)
97
97
  @args = args
98
98
  @options = { :recipes => [], :actions => [], :vars => {},
99
- :pre_vars => {} }
99
+ :pre_vars => {}, :dotfile => default_dotfile }
100
100
 
101
101
  OptionParser.new do |opts|
102
102
  opts.banner = "Usage: #{$0} [options] [args]"
@@ -110,6 +110,14 @@ module Capistrano
110
110
  "be specified, and are loaded in the given order."
111
111
  ) { |value| @options[:actions] << value }
112
112
 
113
+ opts.on("-c", "--caprc FILE",
114
+ "Specify an alternate personal config file to load.",
115
+ "(Default: #{@options[:dotfile]})"
116
+ ) do |value|
117
+ abort "The config file `#{value}' does not exist" unless File.exist?(value)
118
+ @options[:dotfile] = value
119
+ end
120
+
113
121
  opts.on("-f", "--file FILE",
114
122
  "A recipe file to load. Multiple recipes may",
115
123
  "be specified, and are loaded in the given order."
@@ -147,6 +155,12 @@ module Capistrano
147
155
  @options[:pre_vars][name.to_sym] = value
148
156
  end
149
157
 
158
+ opts.on("-x", "--skip-config",
159
+ "Disables the loading of the default personal config",
160
+ "file. Specifying -C after this option will reenable",
161
+ "it. (Default: config file is loaded)"
162
+ ) { @options[:dotfile] = nil }
163
+
150
164
  opts.separator ""
151
165
  opts.separator "Framework Integration Options --------"
152
166
  opts.separator ""
@@ -245,6 +259,7 @@ DETAIL
245
259
  config.set :pretend, options[:pretend]
246
260
 
247
261
  options[:pre_vars].each { |name, value| config.set(name, value) }
262
+ config.load(@options[:dotfile]) if @options[:dotfile] && File.exist?(@options[:dotfile])
248
263
 
249
264
  # load the standard recipe definition
250
265
  config.load "standard"
@@ -254,6 +269,8 @@ DETAIL
254
269
 
255
270
  actor = config.actor
256
271
  options[:actions].each { |action| actor.send action }
272
+ rescue Exception => error
273
+ handle_error(error)
257
274
  end
258
275
 
259
276
  # Load the Rails generator and apply it to the specified directory.
@@ -288,6 +305,16 @@ DETAIL
288
305
  end
289
306
  end
290
307
 
308
+ def default_dotfile
309
+ File.join(home_directory, ".caprc")
310
+ end
311
+
312
+ def home_directory
313
+ ENV["HOME"] ||
314
+ (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
315
+ "/"
316
+ end
317
+
291
318
  def look_for_default_recipe_file!
292
319
  DEFAULT_RECIPES.each do |file|
293
320
  if File.exist?(file)
@@ -300,5 +327,15 @@ DETAIL
300
327
  def look_for_raw_actions!
301
328
  @options[:actions].concat(@args)
302
329
  end
330
+
331
+ def handle_error(error)
332
+ case error
333
+ when Net::SSH::AuthenticationFailed
334
+ abort "authentication failed for `#{error.message}'"
335
+ when Capistrano::Command::Error
336
+ abort(error.message)
337
+ else raise error
338
+ end
339
+ end
303
340
  end
304
341
  end
@@ -3,6 +3,8 @@ module Capistrano
3
3
  # This class encapsulates a single command to be executed on a set of remote
4
4
  # machines, in parallel.
5
5
  class Command
6
+ class Error < RuntimeError; end
7
+
6
8
  attr_reader :servers, :command, :options, :actor
7
9
 
8
10
  def initialize(servers, command, callback, options, actor) #:nodoc:
@@ -42,7 +44,7 @@ module Capistrano
42
44
  logger.trace "command finished"
43
45
 
44
46
  if failed = @channels.detect { |ch| ch[:status] != 0 }
45
- raise "command #{@command.inspect} failed on #{failed[:host]}"
47
+ raise Error, "command #{@command.inspect} failed on #{failed[:host]}"
46
48
  end
47
49
 
48
50
  self
@@ -146,11 +146,9 @@ module Capistrano
146
146
  load :string => File.read(file), :name => options[:name] || file
147
147
 
148
148
  elsif options[:string]
149
- logger.trace "loading configuration #{options[:name] || "<eval>"}"
150
149
  instance_eval(options[:string], options[:name] || "<eval>")
151
150
 
152
151
  elsif options[:proc]
153
- logger.trace "loading configuration #{eval("__FILE__", options[:proc])}"
154
152
  instance_eval(&options[:proc])
155
153
 
156
154
  else
@@ -30,7 +30,6 @@ module Capistrano
30
30
 
31
31
  def initialize(server, config) #:nodoc:
32
32
  @config = config
33
- @pending_forward_requests = {}
34
33
  @next_port = MAX_PORT
35
34
  @terminate_thread = false
36
35
  @port_guard = Mutex.new
@@ -70,12 +69,15 @@ module Capistrano
70
69
  def connect_to(server)
71
70
  connection = nil
72
71
  @config.logger.trace "establishing connection to #{server} via gateway"
73
- port = next_port
72
+ local_port = next_port
74
73
 
75
74
  thread = Thread.new do
76
75
  begin
77
- @session.forward.local(port, server, 22)
78
- connection = SSH.connect('127.0.0.1', @config, port)
76
+ user, server_stripped, port = SSH.parse_server(server)
77
+ @config.ssh_options[:username] = user if user
78
+ remote_port = port || 22
79
+ @session.forward.local(local_port, server_stripped, remote_port)
80
+ connection = SSH.connect('127.0.0.1', @config, local_port)
79
81
  @config.logger.trace "connection to #{server} via gateway established"
80
82
  rescue Errno::EADDRINUSE
81
83
  port = next_port
@@ -12,6 +12,9 @@ def cap(*parameters)
12
12
 
13
13
  require 'capistrano/cli'
14
14
 
15
+ STDERR.puts "Capistrano/Rake integration is deprecated."
16
+ STDERR.puts "Please invoke the 'cap' command directly: `cap #{parameters.join(" ")}'"
17
+
15
18
  Capistrano::CLI.new(parameters.map { |param| param.to_s }).execute!
16
19
  end
17
20
 
@@ -37,7 +37,7 @@ end
37
37
  desc "Set up the expected application directory structure on all boxes"
38
38
  task :setup, :except => { :no_release => true } do
39
39
  run <<-CMD
40
- mkdir -p -m 775 #{releases_path} #{shared_path}/system &&
40
+ mkdir -p -m 775 #{deploy_to} #{releases_path} #{shared_path} #{shared_path}/system &&
41
41
  mkdir -p -m 777 #{shared_path}/log &&
42
42
  mkdir -p -m 777 #{shared_path}/pids
43
43
  CMD
@@ -70,6 +70,8 @@ task :update_code, :except => { :no_release => true } do
70
70
 
71
71
  source.checkout(self)
72
72
 
73
+ run "chmod -R g+w #{release_path}"
74
+
73
75
  run <<-CMD
74
76
  rm -rf #{release_path}/log #{release_path}/public/system &&
75
77
  ln -nfs #{shared_path}/log #{release_path}/log &&
@@ -82,6 +84,12 @@ task :update_code, :except => { :no_release => true } do
82
84
  ln -nfs #{shared_path}/pids #{release_path}/tmp/pids; true
83
85
  CMD
84
86
 
87
+ # update the asset timestamps so they are in sync across all servers. This
88
+ # lets the asset timestamping feature of rails work correctly
89
+ stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
90
+ asset_paths = %w(images stylesheets javascripts).map { |p| "#{release_path}/public/#{p}" }
91
+ run "find #{asset_paths.join(" ")} -exec touch -t #{stamp} {} \\;; true"
92
+
85
93
  # uncache the list of releases, so that the next time it is called it will
86
94
  # include the newly released path.
87
95
  @releases = nil
@@ -150,7 +158,7 @@ task :migrate, :roles => :db, :only => { :primary => true } do
150
158
  end
151
159
 
152
160
  run "cd #{directory} && " +
153
- "#{rake} RAILS_ENV=#{rails_env} #{migrate_env} migrate"
161
+ "#{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate"
154
162
  end
155
163
 
156
164
  desc <<-DESC
@@ -212,7 +220,7 @@ releases are retained, but this can be configured with the 'keep_releases'
212
220
  variable. This will use sudo to do the delete by default, but you can specify
213
221
  that run should be used by setting the :use_sudo variable to false.
214
222
  DESC
215
- task :cleanup do
223
+ task :cleanup, :except => { :no_release => true } do
216
224
  count = (self[:keep_releases] || 5).to_i
217
225
  if count >= releases.length
218
226
  logger.important "no old releases to clean up"
@@ -117,7 +117,7 @@ module Capistrano
117
117
  end
118
118
 
119
119
  def cvs_log(path,branch)
120
- `cd #{path || "."} && cvs -q log -N -r#{branch}`
120
+ `cd #{path || "."} && cvs -d #{configuration.repository} -q log -N -r#{branch}`
121
121
  end
122
122
 
123
123
  def cvs_local
@@ -27,12 +27,35 @@ module Capistrano
27
27
  :password => password_value,
28
28
  :port => port,
29
29
  :auth_methods => methods.shift }.merge(config.ssh_options)
30
- Net::SSH.start(server,ssh_options,&block)
30
+
31
+ user, server_stripped, port = parse_server(server)
32
+ ssh_options[:username] = user if user
33
+ ssh_options[:port] = port if port
34
+
35
+ Net::SSH.start(server_stripped,ssh_options,&block)
31
36
  rescue Net::SSH::AuthenticationFailed
32
37
  raise if methods.empty?
33
38
  password_value = config.password
34
39
  retry
35
40
  end
36
41
  end
42
+
43
+ # This regex is used for its byproducts, the $1-9 match vars.
44
+ # This regex will always match the ssh hostname and if there
45
+ # is a username or port they will be matched as well. This
46
+ # allows us to set the username and ssh port right in the
47
+ # server string: "username@123.12.123.12:8088"
48
+ # This remains fully backwards compatible and can still be
49
+ # intermixed with the old way of doing things. usernames
50
+ # and ports will be used from the server string if present
51
+ # but they will fall back to the regular defaults when not
52
+ # present. Returns and array like:
53
+ # ['bob', 'demo.server.com', '8088']
54
+ # will always at least return the server:
55
+ # [nil, 'demo.server.com', nil]
56
+ def self.parse_server(server)
57
+ server =~ /^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/
58
+ [$1, $2, $3]
59
+ end
37
60
  end
38
61
  end
@@ -19,7 +19,7 @@ module Capistrano
19
19
  end
20
20
 
21
21
  MAJOR = 1
22
- MINOR = 2
22
+ MINOR = 3
23
23
  TINY = 0
24
24
 
25
25
  STRING = [MAJOR, MINOR, TINY].join(".")
@@ -316,6 +316,15 @@ class ActorTest < Test::Unit::TestCase
316
316
  @actor.foo
317
317
  assert_instance_of GatewayConnectionFactory, @actor.factory
318
318
  end
319
+
320
+ def test_establish_connection_uses_gateway_if_specified_with_username_and_port
321
+ @actor.configuration.gateway = "demo@10.example.com:8088"
322
+ @actor.define_task :foo, :roles => :db do
323
+ run "do this"
324
+ end
325
+ @actor.foo
326
+ assert_instance_of GatewayConnectionFactory, @actor.factory
327
+ end
319
328
 
320
329
  def test_run_when_not_pretend
321
330
  @actor.define_task :foo do
@@ -42,6 +42,39 @@ class SSHTest < Test::Unit::TestCase
42
42
  MockSSH.invocations.first[1][:auth_methods]
43
43
  assert_nil MockSSH.invocations.first[2]
44
44
  end
45
+
46
+ def test_explicit_ssh_ports_in_server_string_no_block
47
+ Net.const_during(:SSH, MockSSH) do
48
+ Capistrano::SSH.connect('demo.server.i:8088', @config)
49
+ end
50
+
51
+ assert_equal 1, MockSSH.invocations.length
52
+ assert_equal 'demo.server.i', MockSSH.invocations.first[0]
53
+ assert_equal '8088', MockSSH.invocations.first[1][:port]
54
+ assert_equal 'demo', MockSSH.invocations.first[1][:username]
55
+ end
56
+
57
+ def test_explicit_ssh_username_in_server_string_no_block
58
+ Net.const_during(:SSH, MockSSH) do
59
+ Capistrano::SSH.connect('bob@demo.server.i', @config)
60
+ end
61
+
62
+ assert_equal 1, MockSSH.invocations.length
63
+ assert_equal 'demo.server.i', MockSSH.invocations.first[0]
64
+ assert_equal 22, MockSSH.invocations.first[1][:port]
65
+ assert_equal 'bob', MockSSH.invocations.first[1][:username]
66
+ end
67
+
68
+ def test_explicit_ssh_username_and_port_in_server_string_no_block
69
+ Net.const_during(:SSH, MockSSH) do
70
+ Capistrano::SSH.connect('bob@demo.server.i:8088', @config)
71
+ end
72
+
73
+ assert_equal 1, MockSSH.invocations.length
74
+ assert_equal 'demo.server.i', MockSSH.invocations.first[0]
75
+ assert_equal '8088', MockSSH.invocations.first[1][:port]
76
+ assert_equal 'bob', MockSSH.invocations.first[1][:username]
77
+ end
45
78
 
46
79
  def test_publickey_auth_succeeds_explicit_port_no_block
47
80
  Net.const_during(:SSH, MockSSH) do
@@ -53,6 +86,53 @@ class SSHTest < Test::Unit::TestCase
53
86
  assert_nil MockSSH.invocations.first[2]
54
87
  end
55
88
 
89
+
90
+ def test_explicit_ssh_ports_in_server_string_with_block
91
+ Net.const_during(:SSH, MockSSH) do
92
+ Capistrano::SSH.connect('demo.server.i:8088', @config) do |session|
93
+ end
94
+ end
95
+ assert_equal 'demo.server.i', MockSSH.invocations.first[0]
96
+ assert_equal '8088', MockSSH.invocations.first[1][:port]
97
+ assert_equal 1, MockSSH.invocations.length
98
+ assert_instance_of Proc, MockSSH.invocations.first[2]
99
+ end
100
+
101
+ def test_explicit_ssh_username_in_server_string_with_block
102
+ Net.const_during(:SSH, MockSSH) do
103
+ Capistrano::SSH.connect('bob@demo.server.i', @config) do |session|
104
+ end
105
+ end
106
+ assert_equal 'demo.server.i', MockSSH.invocations.first[0]
107
+ assert_equal 22, MockSSH.invocations.first[1][:port]
108
+ assert_equal 1, MockSSH.invocations.length
109
+ assert_equal 'bob', MockSSH.invocations.first[1][:username]
110
+ assert_instance_of Proc, MockSSH.invocations.first[2]
111
+ end
112
+
113
+ def test_explicit_ssh_username_and_port_in_server_string_with_block
114
+ Net.const_during(:SSH, MockSSH) do
115
+ Capistrano::SSH.connect('bob@demo.server.i:8088', @config) do |session|
116
+ end
117
+ end
118
+ assert_equal 'demo.server.i', MockSSH.invocations.first[0]
119
+ assert_equal '8088', MockSSH.invocations.first[1][:port]
120
+ assert_equal 1, MockSSH.invocations.length
121
+ assert_equal 'bob', MockSSH.invocations.first[1][:username]
122
+ assert_instance_of Proc, MockSSH.invocations.first[2]
123
+ end
124
+
125
+ def test_parse_server
126
+ assert_equal(['bob', 'demo.server.i', '8088'],
127
+ Capistrano::SSH.parse_server("bob@demo.server.i:8088"))
128
+ assert_equal([nil, 'demo.server.i', '8088'],
129
+ Capistrano::SSH.parse_server("demo.server.i:8088"))
130
+ assert_equal(['bob', 'demo.server.i', nil],
131
+ Capistrano::SSH.parse_server("bob@demo.server.i"))
132
+ assert_equal([nil, 'demo.server.i', nil],
133
+ Capistrano::SSH.parse_server("demo.server.i"))
134
+ end
135
+
56
136
  def test_publickey_auth_succeeds_with_block
57
137
  Net.const_during(:SSH, MockSSH) do
58
138
  Capistrano::SSH.connect('demo.server.i', @config) do |session|
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: capistrano
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.0
7
- date: 2006-09-14 00:00:00 -06:00
6
+ version: 1.3.0
7
+ date: 2006-12-23 00:00:00 -07:00
8
8
  summary: Capistrano is a framework and utility for executing commands in parallel on multiple remote machines, via SSH. The primary goal is to simplify and automate the deployment of web applications.
9
9
  require_paths:
10
10
  - lib