capistrano 1.4.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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