capistrano 1.4.2 → 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.
Files changed (113) hide show
  1. data/CHANGELOG +140 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README +22 -14
  4. data/bin/cap +1 -8
  5. data/bin/capify +77 -0
  6. data/examples/sample.rb +10 -109
  7. data/lib/capistrano.rb +1 -0
  8. data/lib/capistrano/callback.rb +41 -0
  9. data/lib/capistrano/cli.rb +17 -317
  10. data/lib/capistrano/cli/execute.rb +82 -0
  11. data/lib/capistrano/cli/help.rb +102 -0
  12. data/lib/capistrano/cli/help.txt +53 -0
  13. data/lib/capistrano/cli/options.rb +183 -0
  14. data/lib/capistrano/cli/ui.rb +28 -0
  15. data/lib/capistrano/command.rb +62 -29
  16. data/lib/capistrano/configuration.rb +25 -226
  17. data/lib/capistrano/configuration/actions/file_transfer.rb +35 -0
  18. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  19. data/lib/capistrano/configuration/actions/invocation.rb +127 -0
  20. data/lib/capistrano/configuration/callbacks.rb +148 -0
  21. data/lib/capistrano/configuration/connections.rb +159 -0
  22. data/lib/capistrano/configuration/execution.rb +126 -0
  23. data/lib/capistrano/configuration/loading.rb +112 -0
  24. data/lib/capistrano/configuration/namespaces.rb +190 -0
  25. data/lib/capistrano/configuration/roles.rb +51 -0
  26. data/lib/capistrano/configuration/servers.rb +75 -0
  27. data/lib/capistrano/configuration/variables.rb +127 -0
  28. data/lib/capistrano/errors.rb +15 -0
  29. data/lib/capistrano/extensions.rb +27 -8
  30. data/lib/capistrano/gateway.rb +54 -29
  31. data/lib/capistrano/logger.rb +11 -11
  32. data/lib/capistrano/recipes/compat.rb +32 -0
  33. data/lib/capistrano/recipes/deploy.rb +483 -0
  34. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  35. data/lib/capistrano/recipes/deploy/local_dependency.rb +46 -0
  36. data/lib/capistrano/recipes/deploy/remote_dependency.rb +65 -0
  37. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  38. data/lib/capistrano/recipes/deploy/scm/base.rb +180 -0
  39. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  40. data/lib/capistrano/recipes/deploy/scm/cvs.rb +151 -0
  41. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  42. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +129 -0
  43. data/lib/capistrano/recipes/deploy/scm/perforce.rb +126 -0
  44. data/lib/capistrano/recipes/deploy/scm/subversion.rb +103 -0
  45. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  46. data/lib/capistrano/recipes/deploy/strategy/base.rb +64 -0
  47. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  48. data/lib/capistrano/recipes/deploy/strategy/copy.rb +143 -0
  49. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  50. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  51. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +47 -0
  52. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  53. data/lib/capistrano/recipes/standard.rb +26 -276
  54. data/lib/capistrano/recipes/templates/maintenance.rhtml +1 -1
  55. data/lib/capistrano/recipes/upgrade.rb +33 -0
  56. data/lib/capistrano/server_definition.rb +51 -0
  57. data/lib/capistrano/shell.rb +125 -81
  58. data/lib/capistrano/ssh.rb +80 -36
  59. data/lib/capistrano/task_definition.rb +69 -0
  60. data/lib/capistrano/upload.rb +146 -0
  61. data/lib/capistrano/version.rb +13 -17
  62. data/test/cli/execute_test.rb +132 -0
  63. data/test/cli/help_test.rb +139 -0
  64. data/test/cli/options_test.rb +226 -0
  65. data/test/cli/ui_test.rb +28 -0
  66. data/test/cli_test.rb +17 -0
  67. data/test/command_test.rb +284 -25
  68. data/test/configuration/actions/file_transfer_test.rb +40 -0
  69. data/test/configuration/actions/inspect_test.rb +62 -0
  70. data/test/configuration/actions/invocation_test.rb +195 -0
  71. data/test/configuration/callbacks_test.rb +206 -0
  72. data/test/configuration/connections_test.rb +288 -0
  73. data/test/configuration/execution_test.rb +159 -0
  74. data/test/configuration/loading_test.rb +119 -0
  75. data/test/configuration/namespace_dsl_test.rb +283 -0
  76. data/test/configuration/roles_test.rb +47 -0
  77. data/test/configuration/servers_test.rb +90 -0
  78. data/test/configuration/variables_test.rb +180 -0
  79. data/test/configuration_test.rb +60 -212
  80. data/test/deploy/scm/base_test.rb +55 -0
  81. data/test/deploy/strategy/copy_test.rb +146 -0
  82. data/test/extensions_test.rb +69 -0
  83. data/test/fixtures/cli_integration.rb +5 -0
  84. data/test/fixtures/custom.rb +2 -2
  85. data/test/gateway_test.rb +167 -0
  86. data/test/logger_test.rb +123 -0
  87. data/test/server_definition_test.rb +108 -0
  88. data/test/shell_test.rb +64 -0
  89. data/test/ssh_test.rb +67 -154
  90. data/test/task_definition_test.rb +101 -0
  91. data/test/upload_test.rb +131 -0
  92. data/test/utils.rb +31 -39
  93. data/test/version_test.rb +24 -0
  94. metadata +145 -98
  95. data/THANKS +0 -4
  96. data/lib/capistrano/actor.rb +0 -567
  97. data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +0 -25
  98. data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +0 -49
  99. data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +0 -122
  100. data/lib/capistrano/generators/rails/loader.rb +0 -20
  101. data/lib/capistrano/scm/base.rb +0 -61
  102. data/lib/capistrano/scm/baz.rb +0 -118
  103. data/lib/capistrano/scm/bzr.rb +0 -70
  104. data/lib/capistrano/scm/cvs.rb +0 -129
  105. data/lib/capistrano/scm/darcs.rb +0 -27
  106. data/lib/capistrano/scm/mercurial.rb +0 -83
  107. data/lib/capistrano/scm/perforce.rb +0 -139
  108. data/lib/capistrano/scm/subversion.rb +0 -128
  109. data/lib/capistrano/transfer.rb +0 -97
  110. data/lib/capistrano/utils.rb +0 -26
  111. data/test/actor_test.rb +0 -402
  112. data/test/scm/cvs_test.rb +0 -196
  113. data/test/scm/subversion_test.rb +0 -145
@@ -43,7 +43,7 @@
43
43
  as of <%= Time.now.strftime("%H:%M %Z") %>.
44
44
  </p>
45
45
  <p style="color: #666;">
46
- It'll be back <%= deadline ? "by #{deadline}" : "shortly" %>.
46
+ It'll be back <%= deadline ? deadline : "shortly" %>.
47
47
  </p>
48
48
  </div>
49
49
  </div>
@@ -0,0 +1,33 @@
1
+ # Tasks to aid the migration of an established Capistrano 1.x installation to
2
+ # Capistrano 2.x.
3
+
4
+ namespace :upgrade do
5
+ desc <<-DESC
6
+ Migrate from the revisions log to REVISION. Capistrano 1.x recorded each \
7
+ deployment to a revisions.log file. Capistrano 2.x is cleaner, and just \
8
+ puts a REVISION file in the root of the deployed revision. This task \
9
+ migrates from the revisions.log used in Capistrano 1.x, to the REVISION \
10
+ tag file used in Capistrano 2.x. It is non-destructive and may be safely \
11
+ run any number of times.
12
+ DESC
13
+ task :revisions do
14
+ revisions = capture("cat #{deploy_to}/revisions.log")
15
+
16
+ mapping = {}
17
+ revisions.each do |line|
18
+ revision, directory = line.chomp.split[-2,2]
19
+ mapping[directory] = revision
20
+ end
21
+
22
+ commands = mapping.keys.map do |directory|
23
+ "echo '.'; test -d #{directory} && echo '#{mapping[directory]}' > #{directory}/REVISION"
24
+ end
25
+
26
+ command = commands.join(";")
27
+
28
+ run "cd #{releases_path}; #{command}; true" do |ch, stream, out|
29
+ STDOUT.print(".")
30
+ STDOUT.flush
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module Capistrano
2
+ class ServerDefinition
3
+ include Comparable
4
+
5
+ attr_reader :host
6
+ attr_reader :user
7
+ attr_reader :port
8
+ attr_reader :options
9
+
10
+ def initialize(string, options={})
11
+ @user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3]
12
+
13
+ @options = options.dup
14
+ user_opt, port_opt = @options.delete(:user), @options.delete(:port)
15
+
16
+ @user ||= user_opt
17
+ @port ||= port_opt
18
+
19
+ @port = @port.to_i if @port
20
+ end
21
+
22
+ def <=>(server)
23
+ [host, port, user] <=> [server.host, server.port, server.user]
24
+ end
25
+
26
+ # Redefined, so that Array#uniq will work to remove duplicate server
27
+ # definitions, based solely on their host names.
28
+ def eql?(server)
29
+ host == server.host &&
30
+ user == server.user &&
31
+ port == server.port
32
+ end
33
+
34
+ alias :== :eql?
35
+
36
+ # Redefined, so that Array#uniq will work to remove duplicate server
37
+ # definitions, based on their connection information.
38
+ def hash
39
+ @hash ||= [host, user, port].hash
40
+ end
41
+
42
+ def to_s
43
+ @to_s ||= begin
44
+ s = host
45
+ s = "#{user}@#{s}" if user
46
+ s = "#{s}:#{port}" if port && port != 22
47
+ s
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,23 +1,34 @@
1
1
  require 'thread'
2
2
 
3
- # The Capistrano::Shell class is the guts of the "shell" task. It implements
4
- # an interactive REPL interface that users can employ to execute tasks and
5
- # commands. It makes for a GREAT way to monitor systems, and perform quick
6
- # maintenance on one or more machines.
7
-
8
3
  module Capistrano
4
+ # The Capistrano::Shell class is the guts of the "shell" task. It implements
5
+ # an interactive REPL interface that users can employ to execute tasks and
6
+ # commands. It makes for a GREAT way to monitor systems, and perform quick
7
+ # maintenance on one or more machines.
9
8
  class Shell
10
- # The actor instance employed by this shell
11
- attr_reader :actor
9
+ # A Readline replacement for platforms where readline is either
10
+ # unavailable, or has not been installed.
11
+ class ReadlineFallback #:nodoc:
12
+ HISTORY = []
13
+
14
+ def self.readline(prompt)
15
+ STDOUT.print(prompt)
16
+ STDOUT.flush
17
+ STDIN.gets
18
+ end
19
+ end
20
+
21
+ # The configuration instance employed by this shell
22
+ attr_reader :configuration
12
23
 
13
24
  # Instantiate a new shell and begin executing it immediately.
14
- def self.run!(actor)
15
- new(actor).run!
25
+ def self.run(config)
26
+ new(config).run!
16
27
  end
17
28
 
18
29
  # Instantiate a new shell
19
- def initialize(actor)
20
- @actor = actor
30
+ def initialize(config)
31
+ @configuration = config
21
32
  end
22
33
 
23
34
  # Start the shell running. This method will block until the shell
@@ -28,78 +39,88 @@ module Capistrano
28
39
  puts <<-INTRO
29
40
  ====================================================================
30
41
  Welcome to the interactive Capistrano shell! This is an experimental
31
- feature, and is liable to change in future releases.
42
+ feature, and is liable to change in future releases. Type 'help' for
43
+ a summary of how to use the shell.
32
44
  --------------------------------------------------------------------
33
45
  INTRO
34
46
 
35
47
  loop do
36
- command = @reader.readline("cap> ", true)
37
-
38
- case command ? command.strip : command
39
- when "" then next
40
- when "help" then help
41
- when nil, "quit", "exit" then
42
- puts if command.nil?
43
- puts "exiting"
44
- break
45
- when /^set -(\w)\s*(\S+)/
46
- set_option($1, $2)
47
- when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
48
- process_command($1, $2, $3)
49
- else
50
- raise "eh?"
51
- end
48
+ break if !read_and_execute
52
49
  end
53
50
 
54
51
  @bgthread.kill
55
52
  end
56
53
 
54
+ def read_and_execute
55
+ command = read_line
56
+
57
+ case command
58
+ when "?", "help" then help
59
+ when "quit", "exit" then
60
+ puts "exiting"
61
+ return false
62
+ when /^set -(\w)\s*(\S+)/
63
+ set_option($1, $2)
64
+ when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
65
+ process_command($1, $2, $3)
66
+ else
67
+ raise "eh?"
68
+ end
69
+
70
+ return true
71
+ end
72
+
57
73
  private
58
74
 
59
- # A Readline replacement for platforms where readline is either
60
- # unavailable, or has not been installed.
61
- class ReadlineFallback
62
- def self.readline(prompt, *args)
63
- STDOUT.print(prompt)
64
- STDOUT.flush
65
- STDIN.gets
75
+ # Present the prompt and read a single line from the console. It also
76
+ # detects ^D and returns "exit" in that case. Adds the input to the
77
+ # history, unless the input is empty. Loops repeatedly until a non-empty
78
+ # line is input.
79
+ def read_line
80
+ loop do
81
+ command = reader.readline("cap> ")
82
+
83
+ if command.nil?
84
+ command = "exit"
85
+ puts(command)
86
+ else
87
+ command.strip!
88
+ end
89
+
90
+ unless command.empty?
91
+ reader::HISTORY << command
92
+ return command
93
+ end
66
94
  end
67
95
  end
68
96
 
69
97
  # Display a verbose help message.
70
98
  def help
71
99
  puts <<-HELP
72
- Welcome to the interactive Capistrano shell! To quit, just type quit,
73
- or exit. Or press ctrl-D. This shell is still experimental, so expect
74
- it to change (or even disappear!) in future releases.
100
+ --- HELP! ---------------------------------------------------
101
+ "Get me out of this thing. I just want to quit."
102
+ -> Easy enough. Just type "exit", or "quit". Or press ctrl-D.
75
103
 
76
- To execute a command on all servers, just type it directly, like:
104
+ "I want to execute a command on all servers."
105
+ -> Just type the command, and press enter. It will be passed,
106
+ verbatim, to all defined servers.
77
107
 
78
- cap> echo ping
108
+ "What if I only want it to execute on a subset of them?"
109
+ -> No problem, just specify the list of servers, separated by
110
+ commas, before the command, with the `on' keyword:
79
111
 
80
- To execute a command on a specific set of servers, specify an 'on' clause.
81
- Note that if you specify more than one host name, they must be comma-
82
- delimited, with NO SPACES between them.
112
+ cap> on app1.foo.com,app2.foo.com echo ping
83
113
 
84
- cap> on app1.foo.com,app2.foo.com echo ping
114
+ "Nice, but can I specify the servers by role?"
115
+ -> You sure can. Just use the `with' keyword, followed by the
116
+ comma-delimited list of role names:
85
117
 
86
- To execute a command on all servers matching a set of roles:
118
+ cap> with app,db echo ping
87
119
 
88
- cap> with app,db echo ping
120
+ "Can I execute a Capistrano task from within this shell?"
121
+ -> Yup. Just prefix the task with an exclamation mark:
89
122
 
90
- To execute a Capistrano task, prefix the name with a bang:
91
-
92
- cap> !deploy
93
-
94
- You can specify multiple tasks to execute, separated by spaces:
95
-
96
- cap> !update_code symlink
97
-
98
- And, lastly, you can specify 'on' or 'with' with tasks:
99
-
100
- cap> on app6.foo.com !setup
101
-
102
- Enjoy!
123
+ cap> !deploy
103
124
  HELP
104
125
  end
105
126
 
@@ -107,11 +128,11 @@ HELP
107
128
  # establish connections to them if necessary. Return the list of
108
129
  # servers (names).
109
130
  def connect(task)
110
- servers = task.servers(:refresh)
111
- needing_connections = servers - actor.sessions.keys
131
+ servers = configuration.find_servers_for_task(task)
132
+ needing_connections = servers - configuration.sessions.keys
112
133
  unless needing_connections.empty?
113
134
  puts "[establishing connection(s) to #{needing_connections.join(', ')}]"
114
- actor.send(:establish_connections, servers)
135
+ configuration.establish_connections_to(needing_connections)
115
136
  end
116
137
  servers
117
138
  end
@@ -124,7 +145,7 @@ HELP
124
145
  if command[0] == ?!
125
146
  exec_tasks(command[1..-1].split)
126
147
  else
127
- servers = connect(actor.current_task)
148
+ servers = connect(configuration.current_task)
128
149
  exec_command(command, servers)
129
150
  end
130
151
  ensure
@@ -134,10 +155,13 @@ HELP
134
155
  # Given an array of task names, invoke them in sequence.
135
156
  def exec_tasks(list)
136
157
  list.each do |task_name|
137
- task = task_name.to_sym
138
- connect(actor.tasks[task])
139
- actor.send(task)
158
+ task = configuration.find_task(task_name)
159
+ raise Capistrano::NoSuchTaskError, "no such task `#{task_name}'" unless task
160
+ connect(task)
161
+ configuration.execute_task(task)
140
162
  end
163
+ rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error
164
+ warn "error: #{error.message}"
141
165
  end
142
166
 
143
167
  # Execute a command on the given list of servers.
@@ -147,36 +171,45 @@ HELP
147
171
  out.each do |line|
148
172
  if stream == :out
149
173
  if out =~ /Password:\s*/i
150
- ch.send_data "#{actor.password}\n"
174
+ ch.send_data "#{configuration[:password]}\n"
151
175
  else
152
- puts "[#{ch[:host]}] #{line.chomp}"
176
+ puts "[#{ch[:server]}] #{line.chomp}"
153
177
  end
154
178
  elsif stream == :err
155
- puts "[#{ch[:host]} ERR] #{line.chomp}"
179
+ puts "[#{ch[:server]} ERR] #{line.chomp}"
156
180
  end
157
181
  end
158
182
  end
159
183
 
160
- cmd = Command.new(servers, command, processor, {}, actor)
161
184
  previous = trap("INT") { cmd.stop! }
162
- cmd.process! rescue nil
185
+ sessions = servers.map { |server| configuration.sessions[server] }
186
+ Command.process(command, sessions, :logger => configuration.logger, &Capistrano::Configuration.default_io_proc)
187
+ rescue Capistrano::Error => error
188
+ warn "error: #{error.message}"
189
+ ensure
163
190
  trap("INT", previous)
164
191
  end
165
192
 
166
- # Prepare every little thing for the shell. Starts the background
167
- # thread and generally gets things ready for the REPL.
168
- def setup
169
- begin
193
+ # Return the object that will be used to query input from the console.
194
+ # The returned object will quack (more or less) like Readline.
195
+ def reader
196
+ @reader ||= begin
170
197
  require 'readline'
171
- @reader = Readline
198
+ Readline
172
199
  rescue LoadError
173
- @reader = ReadlineFallback
200
+ ReadlineFallback
174
201
  end
202
+ end
203
+
204
+ # Prepare every little thing for the shell. Starts the background
205
+ # thread and generally gets things ready for the REPL.
206
+ def setup
207
+ configuration.logger.level = Capistrano::Logger::INFO
175
208
 
176
209
  @mutex = Mutex.new
177
210
  @bgthread = Thread.new do
178
211
  loop do
179
- ready = actor.sessions.values.select { |sess| sess.connection.reader_ready? }
212
+ ready = configuration.sessions.values.select { |sess| sess.connection.reader_ready? }
180
213
  if ready.empty?
181
214
  sleep 0.1
182
215
  else
@@ -193,9 +226,20 @@ HELP
193
226
  case opt
194
227
  when "v" then
195
228
  puts "setting log verbosity to #{value.to_i}"
196
- actor.logger.level = value.to_i
229
+ configuration.logger.level = value.to_i
230
+ when "o" then
231
+ case value
232
+ when "vi" then
233
+ puts "using vi edit mode"
234
+ reader.vi_editing_mode
235
+ when "emacs" then
236
+ puts "using emacs edit mode"
237
+ reader.emacs_editing_mode
238
+ else
239
+ puts "unknown -o option #{value.inspect}"
240
+ end
197
241
  else
198
- puts "unknown setting #{value.inspect}"
242
+ puts "unknown setting #{opt.inspect}"
199
243
  end
200
244
  end
201
245
 
@@ -220,5 +264,5 @@ HELP
220
264
  puts "scoping #{scope_type} #{scope_value}"
221
265
  end
222
266
  end
223
- end
267
+ end
224
268
  end
@@ -1,9 +1,3 @@
1
- begin
2
- require 'rubygems'
3
- gem 'net-ssh', '< 1.99.0'
4
- rescue LoadError, NameError
5
- end
6
-
7
1
  require 'net/ssh'
8
2
 
9
3
  module Capistrano
@@ -11,55 +5,105 @@ module Capistrano
11
5
  require 'capistrano/version'
12
6
  require 'net/ssh/version'
13
7
  ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
14
- if !Version.check(ssh_version, Version::MINIMUM_SSH_REQUIRED, Version::MAXIMUM_SSH_REQUIRED)
15
- raise "You have Net::SSH #{ssh_version.join(".")}, but you need a version between #{Version::MINIMUM_SSH_REQUIRED.join(".")}...#{Version::MAXIMUM_SSH_REQUIRED.join(".")}"
8
+ if !Version.check(Version::SSH_REQUIRED, ssh_version)
9
+ raise "You have Net::SSH #{ssh_version.join(".")}, but you need at least #{Version::SSH_REQUIRED.join(".")}"
10
+ end
11
+ end
12
+
13
+ # Now, Net::SSH is kind of silly, and tries to lazy-load everything. This
14
+ # wreaks havoc with the parallel connection trick that Capistrano wants to
15
+ # use, so we're going to do something hideously ugly here and force all the
16
+ # files that Net::SSH uses to load RIGHT NOW, rather than lazily.
17
+
18
+ net_ssh_dependencies = %w(connection/services connection/channel connection/driver
19
+ service/agentforward/services service/agentforward/driver
20
+ service/process/driver util/prompter
21
+ service/forward/services service/forward/driver service/forward/local-network-handler service/forward/remote-network-handler
22
+ service/shell/services service/shell/driver
23
+ lenient-host-key-verifier
24
+ transport/compress/services transport/compress/zlib-compressor transport/compress/none-compressor transport/compress/zlib-decompressor transport/compress/none-decompressor
25
+ transport/kex/services transport/kex/dh transport/kex/dh-gex
26
+ transport/ossl/services
27
+ transport/ossl/hmac/services transport/ossl/hmac/sha1 transport/ossl/hmac/sha1-96 transport/ossl/hmac/md5 transport/ossl/hmac/md5-96 transport/ossl/hmac/none
28
+ transport/ossl/cipher-factory transport/ossl/hmac-factory transport/ossl/buffer-factory transport/ossl/key-factory transport/ossl/digest-factory
29
+ transport/identity-cipher transport/packet-stream transport/version-negotiator transport/algorithm-negotiator transport/session
30
+ userauth/methods/services userauth/methods/password userauth/methods/keyboard-interactive userauth/methods/publickey userauth/methods/hostbased
31
+ userauth/services userauth/agent userauth/userkeys userauth/driver
32
+ transport/services service/services
33
+ )
34
+
35
+ net_ssh_dependencies << "userauth/pageant" if File::ALT_SEPARATOR
36
+ net_ssh_dependencies.each do |path|
37
+ begin
38
+ require "net/ssh/#{path}"
39
+ rescue LoadError
40
+ # Ignore load errors from this, since some files are in the list which
41
+ # do not exist in different (supported) versions of Net::SSH. We know
42
+ # (by this point) that Net::SSH is installed, though, since we do a
43
+ # require 'net/ssh' at the very top of this file, and we know the
44
+ # installed version meets the minimum version requirements because of
45
+ # the version check, also at the top of this file. So, if we get a
46
+ # LoadError, it's simply because the file in question does not exist in
47
+ # the version of Net::SSH that is installed.
48
+ #
49
+ # Whew!
16
50
  end
17
51
  end
18
52
 
19
53
  # A helper class for dealing with SSH connections.
20
54
  class SSH
55
+ # Patch an accessor onto an SSH connection so that we can record the server
56
+ # definition object that defines the connection. This is useful because
57
+ # the gateway returns connections whose "host" is 127.0.0.1, instead of
58
+ # the host on the other side of the tunnel.
59
+ module Server #:nodoc:
60
+ def self.apply_to(connection, server)
61
+ connection.extend(Server)
62
+ connection.xserver = server
63
+ connection
64
+ end
65
+
66
+ attr_accessor :xserver
67
+ end
68
+
69
+ # The default port for SSH.
70
+ DEFAULT_PORT = 22
71
+
21
72
  # An abstraction to make it possible to connect to the server via public key
22
73
  # without prompting for the password. If the public key authentication fails
23
74
  # this will fall back to password authentication.
24
75
  #
76
+ # +server+ must be an instance of ServerDefinition.
77
+ #
25
78
  # If a block is given, the new session is yielded to it, otherwise the new
26
79
  # session is returned.
27
- def self.connect(server, config, port=22, &block)
80
+ #
81
+ # If an :ssh_options key exists in +options+, it is passed to the Net::SSH
82
+ # constructor. Values in +options+ are then merged into it, and any
83
+ # connection information in +server+ is added last, so that +server+ info
84
+ # takes precedence over +options+, which takes precendence over ssh_options.
85
+ def self.connect(server, options={}, &block)
28
86
  methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
29
87
  password_value = nil
30
88
 
31
- user, server_stripped, pport = parse_server(server)
89
+ ssh_options = (options[:ssh_options] || {}).dup
90
+ ssh_options[:username] = server.user || options[:user] || ssh_options[:username]
91
+ ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
32
92
 
33
93
  begin
34
- ssh_options = { :username => (user || config.user),
35
- :password => password_value,
36
- :port => ((pport && pport != port) ? pport : port),
37
- :auth_methods => methods.shift }.merge(config.ssh_options)
38
-
39
- Net::SSH.start(server_stripped,ssh_options,&block)
94
+ connection_options = ssh_options.merge(
95
+ :password => password_value,
96
+ :auth_methods => ssh_options[:auth_methods] || methods.shift
97
+ )
98
+
99
+ connection = Net::SSH.start(server.host, connection_options, &block)
100
+ Server.apply_to(connection, server)
101
+
40
102
  rescue Net::SSH::AuthenticationFailed
41
- raise if methods.empty?
42
- password_value = config.password
103
+ raise if methods.empty? || ssh_options[:auth_methods]
104
+ password_value = options[:password]
43
105
  retry
44
106
  end
45
107
  end
46
-
47
- # This regex is used for its byproducts, the $1-3 match vars.
48
- # This regex will always match the ssh hostname and if there
49
- # is a username or port they will be matched as well. This
50
- # allows us to set the username and ssh port right in the
51
- # server string: "username@123.12.123.12:8088"
52
- # This remains fully backwards compatible and can still be
53
- # intermixed with the old way of doing things. usernames
54
- # and ports will be used from the server string if present
55
- # but they will fall back to the regular defaults when not
56
- # present. Returns and array like:
57
- # ['bob', 'demo.server.com', '8088']
58
- # will always at least return the server:
59
- # [nil, 'demo.server.com', nil]
60
- def self.parse_server(server)
61
- server =~ /^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/
62
- [$1, $2, $3]
63
- end
64
108
  end
65
109
  end