capistrano 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/CHANGELOG +29 -0
  2. data/README +4 -7
  3. data/bin/cap +0 -0
  4. data/bin/capify +0 -0
  5. data/lib/capistrano/command.rb +32 -38
  6. data/lib/capistrano/configuration/actions/file_transfer.rb +21 -13
  7. data/lib/capistrano/configuration/actions/invocation.rb +1 -1
  8. data/lib/capistrano/configuration/connections.rb +30 -20
  9. data/lib/capistrano/errors.rb +1 -1
  10. data/lib/capistrano/processable.rb +53 -0
  11. data/lib/capistrano/recipes/deploy/remote_dependency.rb +6 -0
  12. data/lib/capistrano/recipes/deploy/scm/git.rb +26 -11
  13. data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
  14. data/lib/capistrano/recipes/deploy/strategy/base.rb +6 -0
  15. data/lib/capistrano/recipes/deploy/strategy/copy.rb +74 -3
  16. data/lib/capistrano/recipes/deploy.rb +15 -13
  17. data/lib/capistrano/role.rb +0 -14
  18. data/lib/capistrano/server_definition.rb +5 -0
  19. data/lib/capistrano/shell.rb +21 -17
  20. data/lib/capistrano/ssh.rb +24 -58
  21. data/lib/capistrano/transfer.rb +216 -0
  22. data/lib/capistrano/version.rb +1 -1
  23. data/test/cli/execute_test.rb +1 -1
  24. data/test/cli/help_test.rb +1 -1
  25. data/test/cli/options_test.rb +1 -1
  26. data/test/cli/ui_test.rb +1 -1
  27. data/test/cli_test.rb +1 -1
  28. data/test/command_test.rb +31 -51
  29. data/test/configuration/actions/file_transfer_test.rb +21 -19
  30. data/test/configuration/actions/inspect_test.rb +1 -1
  31. data/test/configuration/actions/invocation_test.rb +6 -6
  32. data/test/configuration/callbacks_test.rb +1 -1
  33. data/test/configuration/connections_test.rb +11 -12
  34. data/test/configuration/execution_test.rb +1 -1
  35. data/test/configuration/loading_test.rb +1 -1
  36. data/test/configuration/namespace_dsl_test.rb +1 -1
  37. data/test/configuration/roles_test.rb +1 -1
  38. data/test/configuration/servers_test.rb +1 -1
  39. data/test/configuration/variables_test.rb +1 -1
  40. data/test/configuration_test.rb +1 -1
  41. data/test/deploy/scm/accurev_test.rb +1 -1
  42. data/test/deploy/scm/base_test.rb +1 -1
  43. data/test/deploy/scm/git_test.rb +10 -6
  44. data/test/deploy/scm/mercurial_test.rb +1 -1
  45. data/test/deploy/strategy/copy_test.rb +120 -27
  46. data/test/extensions_test.rb +1 -1
  47. data/test/logger_test.rb +1 -1
  48. data/test/server_definition_test.rb +1 -1
  49. data/test/shell_test.rb +27 -1
  50. data/test/ssh_test.rb +27 -21
  51. data/test/task_definition_test.rb +1 -1
  52. data/test/transfer_test.rb +160 -0
  53. data/test/utils.rb +30 -34
  54. data/test/version_test.rb +1 -1
  55. metadata +26 -14
  56. data/lib/capistrano/gateway.rb +0 -131
  57. data/lib/capistrano/upload.rb +0 -152
  58. data/test/gateway_test.rb +0 -167
  59. data/test/upload_test.rb +0 -131
@@ -1,152 +0,0 @@
1
- begin
2
- require 'rubygems'
3
- gem 'net-sftp', "< 1.99.0"
4
- rescue LoadError, NameError
5
- end
6
-
7
- require 'net/sftp'
8
- require 'net/sftp/operations/errors'
9
- require 'capistrano/errors'
10
-
11
- module Capistrano
12
- unless ENV['SKIP_VERSION_CHECK']
13
- require 'capistrano/version'
14
- require 'net/sftp/version'
15
- sftp_version = [Net::SFTP::Version::MAJOR, Net::SFTP::Version::MINOR, Net::SFTP::Version::TINY]
16
- required_version = [1,1,0]
17
- if !Capistrano::Version.check(required_version, sftp_version)
18
- raise "You have Net::SFTP #{sftp_version.join(".")}, but you need at least #{required_version.join(".")}. Net::SFTP will not be used."
19
- end
20
- end
21
-
22
- # This class encapsulates a single file upload to be performed in parallel
23
- # across multiple machines, using the SFTP protocol. Although it is intended
24
- # to be used primarily from within Capistrano, it may also be used standalone
25
- # if you need to simply upload a file to multiple servers.
26
- #
27
- # Basic Usage:
28
- #
29
- # begin
30
- # uploader = Capistrano::Upload.new(sessions, "remote-file.txt",
31
- # :data => "the contents of the file to upload")
32
- # uploader.process!
33
- # rescue Capistrano::UploadError => e
34
- # warn "Could not upload the file: #{e.message}"
35
- # end
36
- class Upload
37
- def self.process(sessions, filename, options)
38
- new(sessions, filename, options).process!
39
- end
40
-
41
- attr_reader :sessions, :filename, :options
42
- attr_reader :failed, :completed
43
-
44
- # Creates and prepares a new Upload instance. The +sessions+ parameter
45
- # must be an array of open Net::SSH sessions. The +filename+ is the name
46
- # (including path) of the destination file on the remote server. The
47
- # +options+ hash accepts the following keys (as symbols):
48
- #
49
- # * data: required. Should refer to a String containing the contents of
50
- # the file to upload.
51
- # * mode: optional. The "mode" of the destination file. Defaults to 0664.
52
- # * logger: optional. Should point to a Capistrano::Logger instance, if
53
- # given.
54
- def initialize(sessions, filename, options)
55
- raise ArgumentError, "you must specify the data to upload via the :data option" unless options[:data]
56
-
57
- @sessions = sessions
58
- @filename = filename
59
- @options = options
60
-
61
- @completed = @failed = 0
62
- @sftps = setup_sftp
63
- end
64
-
65
- # Uploads to all specified servers in parallel. If any one of the servers
66
- # fails, an exception will be raised (UploadError).
67
- def process!
68
- logger.debug "uploading #{filename}" if logger
69
- while running?
70
- @sftps.each do |sftp|
71
- next if sftp.channel[:done]
72
- begin
73
- sftp.channel.connection.process(true)
74
- rescue Net::SFTP::Operations::StatusException => error
75
- logger.important "uploading failed: #{error.description}", sftp.channel[:server] if logger
76
- failed!(sftp)
77
- end
78
- end
79
- sleep 0.01 # a brief respite, to keep the CPU from going crazy
80
- end
81
- logger.trace "upload finished" if logger
82
-
83
- if (failed = @sftps.select { |sftp| sftp.channel[:failed] }).any?
84
- hosts = failed.map { |sftp| sftp.channel[:server] }
85
- error = UploadError.new("upload of #{filename} failed on #{hosts.join(',')}")
86
- error.hosts = hosts
87
- raise error
88
- end
89
-
90
- self
91
- end
92
-
93
- private
94
-
95
- def logger
96
- options[:logger]
97
- end
98
-
99
- def setup_sftp
100
- sessions.map do |session|
101
- server = session.xserver
102
- sftp = session.sftp
103
- sftp.connect unless sftp.state == :open
104
-
105
- sftp.channel[:server] = server
106
- sftp.channel[:done] = false
107
- sftp.channel[:failed] = false
108
-
109
- real_filename = filename.gsub(/\$CAPISTRANO:HOST\$/, server.host)
110
- sftp.open(real_filename, IO::WRONLY | IO::CREAT | IO::TRUNC, options[:mode] || 0664) do |status, handle|
111
- break unless check_status(sftp, "open #{real_filename}", server, status)
112
-
113
- logger.info "uploading data to #{server}:#{real_filename}" if logger
114
- sftp.write(handle, options[:data] || "") do |status|
115
- break unless check_status(sftp, "write to #{server}:#{real_filename}", server, status)
116
- sftp.close_handle(handle) do
117
- logger.debug "done uploading data to #{server}:#{real_filename}" if logger
118
- completed!(sftp)
119
- end
120
- end
121
- end
122
-
123
- sftp
124
- end
125
- end
126
-
127
- def check_status(sftp, action, server, status)
128
- return true if status.code == Net::SFTP::Session::FX_OK
129
-
130
- logger.error "could not #{action} on #{server} (#{status.message})" if logger
131
- failed!(sftp)
132
-
133
- return false
134
- end
135
-
136
- def running?
137
- completed < @sftps.length
138
- end
139
-
140
- def failed!(sftp)
141
- completed!(sftp)
142
- @failed += 1
143
- sftp.channel[:failed] = true
144
- end
145
-
146
- def completed!(sftp)
147
- @completed += 1
148
- sftp.channel[:done] = true
149
- end
150
- end
151
-
152
- end
data/test/gateway_test.rb DELETED
@@ -1,167 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/utils"
2
- require 'capistrano/gateway'
3
-
4
- class GatewayTest < Test::Unit::TestCase
5
- def teardown
6
- Thread.list { |t| t.kill unless Thread.current == t }
7
- end
8
-
9
- def test_initialize_should_open_and_set_session_value
10
- run_test_initialize_should_open_and_set_session_value
11
- end
12
-
13
- def test_initialize_when_connect_lags_should_open_and_set_session_value
14
- run_test_initialize_should_open_and_set_session_value do |expects|
15
- expects.with { |*args| sleep 0.2; true }
16
- end
17
- end
18
-
19
- def test_shutdown_without_any_open_connections_should_terminate_session
20
- gateway = new_gateway
21
- gateway.shutdown!
22
- assert !gateway.thread.alive?
23
- assert !gateway.session.looping?
24
- end
25
-
26
- def test_connect_to_should_start_local_ports_at_65535
27
- gateway = new_gateway
28
- expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(result = sess_with_xserver("app1"))
29
- newsess = gateway.connect_to(server("app1"))
30
- assert_equal result, newsess
31
- assert_equal [65535, "app1", 22], gateway.session.forward.active_locals[65535]
32
- end
33
-
34
- def test_connect_to_should_decrement_port_and_retry_if_ports_are_in_use
35
- gateway = new_gateway(:reserved => lambda { |n| n > 65000 })
36
- expect_connect_to(:host => "127.0.0.1", :port => 65000).returns(result = sess_with_xserver("app1"))
37
- newsess = gateway.connect_to(server("app1"))
38
- assert_equal result, newsess
39
- assert_equal [65000, "app1", 22], gateway.session.forward.active_locals[65000]
40
- end
41
-
42
- def test_connect_to_should_honor_user_specification_in_server_definition
43
- gateway = new_gateway
44
- expect_connect_to(:host => "127.0.0.1", :user => "jamis", :port => 65535).returns(result = sess_with_xserver("app1"))
45
- newsess = gateway.connect_to(server("jamis@app1"))
46
- assert_equal result, newsess
47
- assert_equal [65535, "app1", 22], gateway.session.forward.active_locals[65535]
48
- end
49
-
50
- def test_connect_to_should_honor_port_specification_in_server_definition
51
- gateway = new_gateway
52
- expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(result = sess_with_xserver("app1"))
53
- newsess = gateway.connect_to(server("app1:1234"))
54
- assert_equal result, newsess
55
- assert_equal [65535, "app1", 1234], gateway.session.forward.active_locals[65535]
56
- end
57
-
58
- def test_connect_to_should_set_xserver_to_tunnel_target
59
- gateway = new_gateway
60
- expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(result = sess_with_xserver("app1"))
61
- newsess = gateway.connect_to(server("app1:1234"))
62
- assert_equal result, newsess
63
- end
64
-
65
- def test_shutdown_should_cancel_active_forwarded_ports
66
- gateway = new_gateway
67
- expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(sess_with_xserver("app1"))
68
- gateway.connect_to(server("app1"))
69
- assert !gateway.session.forward.active_locals.empty?
70
- gateway.shutdown!
71
- assert gateway.session.forward.active_locals.empty?
72
- end
73
-
74
- def test_error_while_connecting_should_cause_connection_to_fail
75
- gateway = new_gateway
76
- expect_connect_to(:host => "127.0.0.1").raises(RuntimeError)
77
- gateway.expects(:warn).times(2)
78
- assert_raises(Capistrano::ConnectionError) { gateway.connect_to(server("app1")) }
79
- end
80
-
81
- def test_connection_error_should_include_accessor_with_host_array
82
- gateway = new_gateway
83
- expect_connect_to(:host => "127.0.0.1").raises(RuntimeError)
84
- gateway.expects(:warn).times(2)
85
-
86
- begin
87
- gateway.connect_to(server("app1"))
88
- flunk "expected an exception to be raised"
89
- rescue Capistrano::ConnectionError => e
90
- assert e.respond_to?(:hosts)
91
- assert_equal %w(app1), e.hosts.map { |h| h.to_s }
92
- end
93
- end
94
-
95
- private
96
-
97
- def sess_with_xserver(host)
98
- s = server(host)
99
- sess = mock("session")
100
- sess.expects(:xserver=).with { |v| v.host == host }
101
- sess
102
- end
103
-
104
- def expect_connect_to(options={})
105
- Capistrano::SSH.expects(:connect).with do |server,config|
106
- options.all? do |key, value|
107
- case key
108
- when :host then server.host == value
109
- when :user then server.user == value
110
- when :port then server.port == value
111
- else false
112
- end
113
- end
114
- end
115
- end
116
-
117
- def new_gateway(options={})
118
- expect_connect_to(:host => "capistrano").yields(MockSession.new(options))
119
- Capistrano::Gateway.new(server("capistrano"))
120
- end
121
-
122
- def run_test_initialize_should_open_and_set_session_value
123
- session = mock("Net::SSH session")
124
- session.expects(:loop)
125
- expectation = Capistrano::SSH.expects(:connect).yields(session)
126
- yield expectation if block_given?
127
- gateway = Capistrano::Gateway.new(server("capistrano"))
128
- gateway.thread.join
129
- assert_equal session, gateway.session
130
- end
131
-
132
- class MockForward
133
- attr_reader :active_locals
134
-
135
- def initialize(options)
136
- @options = options
137
- @active_locals = {}
138
- end
139
-
140
- def cancel_local(port)
141
- @active_locals.delete(port)
142
- end
143
-
144
- def local(lport, host, rport)
145
- raise Errno::EADDRINUSE if @options[:reserved] && @options[:reserved][lport]
146
- @active_locals[lport] = [lport, host, rport]
147
- end
148
- end
149
-
150
- class MockSession
151
- attr_reader :forward
152
-
153
- def initialize(options={})
154
- @forward = MockForward.new(options)
155
- end
156
-
157
- def looping?
158
- @looping
159
- end
160
-
161
- def loop
162
- @looping = true
163
- sleep 0.1 while yield
164
- @looping = false
165
- end
166
- end
167
- end
data/test/upload_test.rb DELETED
@@ -1,131 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/utils"
2
- require 'capistrano/upload'
3
-
4
- class UploadTest < Test::Unit::TestCase
5
- def setup
6
- @mode = IO::WRONLY | IO::CREAT | IO::TRUNC
7
- end
8
-
9
- def test_initialize_should_raise_error_if_data_is_missing
10
- assert_raises(ArgumentError) do
11
- Capistrano::Upload.new([], "test.txt", :foo => "bar")
12
- end
13
- end
14
-
15
- def test_initialize_should_get_sftp_for_each_session
16
- new_sftp = Proc.new do |state|
17
- sftp = mock("sftp", :state => state, :open => nil)
18
- sftp.expects(:connect) unless state == :open
19
- sftp.stubs(:channel).returns({})
20
- sftp
21
- end
22
-
23
- sessions = [mock("session", :xserver => server("a"), :sftp => new_sftp[:closed]),
24
- mock("session", :xserver => server("b"), :sftp => new_sftp[:closed]),
25
- mock("session", :xserver => server("c"), :sftp => new_sftp[:open])]
26
- Capistrano::Upload.new(sessions, "test.txt", :data => "data")
27
- end
28
-
29
- def test_self_process_should_instantiate_uploader_and_start_process
30
- Capistrano::Upload.expects(:new).with([:s1, :s2], "test.txt", :data => "data").returns(mock(:process! => nil))
31
- Capistrano::Upload.process([:s1, :s2], "test.txt", :data => "data")
32
- end
33
-
34
- def test_process_when_sftp_open_fails_should_raise_error
35
- sftp = mock_sftp
36
- sftp.expects(:open).with("test.txt", @mode, 0664).yields(mock("status", :code => "bad status", :message => "bad status"), :file_handle)
37
- session = mock("session", :sftp => sftp, :xserver => server("capistrano"))
38
- upload = Capistrano::Upload.new([session], "test.txt", :data => "data", :logger => stub_everything)
39
- assert_raises(Capistrano::UploadError) { upload.process! }
40
- assert_equal 1, upload.failed
41
- assert_equal 1, upload.completed
42
- end
43
-
44
- def test_process_when_sftp_write_fails_should_raise_error
45
- sftp = mock_sftp
46
- sftp.expects(:open).with("test.txt", @mode, 0664).yields(mock("status1", :code => Net::SFTP::Session::FX_OK), :file_handle)
47
- sftp.expects(:write).with(:file_handle, "data").yields(mock("status2", :code => "bad status", :message => "bad status"))
48
- session = mock("session", :sftp => sftp, :xserver => server("capistrano"))
49
- upload = Capistrano::Upload.new([session], "test.txt", :data => "data", :logger => stub_everything)
50
- assert_raises(Capistrano::UploadError) { upload.process! }
51
- assert_equal 1, upload.failed
52
- assert_equal 1, upload.completed
53
- end
54
-
55
- def test_upload_error_should_include_accessor_with_host_array
56
- sftp = mock_sftp
57
- sftp.expects(:open).with("test.txt", @mode, 0664).yields(mock("status1", :code => Net::SFTP::Session::FX_OK), :file_handle)
58
- sftp.expects(:write).with(:file_handle, "data").yields(mock("status2", :code => "bad status", :message => "bad status"))
59
- session = mock("session", :sftp => sftp, :xserver => server("capistrano"))
60
- upload = Capistrano::Upload.new([session], "test.txt", :data => "data", :logger => stub_everything)
61
-
62
- begin
63
- upload.process!
64
- flunk "expected an exception to be raised"
65
- rescue Capistrano::UploadError => e
66
- assert e.respond_to?(:hosts)
67
- assert_equal %w(capistrano), e.hosts.map { |h| h.to_s }
68
- end
69
- end
70
-
71
- def test_process_when_sftp_succeeds_should_raise_nothing
72
- sftp = mock_sftp
73
- sftp.expects(:open).with("test.txt", @mode, 0664).yields(mock("status1", :code => Net::SFTP::Session::FX_OK), :file_handle)
74
- sftp.expects(:write).with(:file_handle, "data").yields(mock("status2", :code => Net::SFTP::Session::FX_OK))
75
- sftp.expects(:close_handle).with(:file_handle).yields
76
- session = mock("session", :sftp => sftp, :xserver => server("capistrano"))
77
- upload = Capistrano::Upload.new([session], "test.txt", :data => "data", :logger => stub_everything)
78
- assert_nothing_raised { upload.process! }
79
- assert_equal 0, upload.failed
80
- assert_equal 1, upload.completed
81
- end
82
-
83
- def test_process_should_loop_while_running
84
- con = mock("connection")
85
- con.expects(:process).with(true).times(10)
86
- channel = {}
87
- channel.expects(:connection).returns(con).times(10)
88
- sftp = mock("sftp", :state => :open, :open => nil)
89
- sftp.stubs(:channel).returns(channel)
90
- session = mock("session", :sftp => sftp, :xserver => server("capistrano"))
91
- upload = Capistrano::Upload.new([session], "test.txt", :data => "data")
92
- upload.expects(:running?).times(11).returns(*([true]*10 + [false]))
93
- upload.process!
94
- end
95
-
96
- def test_process_should_loop_but_not_process_done_channels
97
- new_sftp = Proc.new do |done|
98
- channel = {}
99
- channel[:needs_done] = done
100
-
101
- if !done
102
- con = mock("connection")
103
- con.expects(:process).with(true).times(10)
104
- channel.expects(:connection).returns(con).times(10)
105
- end
106
-
107
- sftp = mock("sftp", :state => :open, :open => nil)
108
- sftp.stubs(:channel).returns(channel)
109
- sftp
110
- end
111
-
112
- sessions = [stub("session", :sftp => new_sftp[true], :xserver => server("capistrano")),
113
- stub("session", :sftp => new_sftp[false], :xserver => server("cap2"))]
114
- upload = Capistrano::Upload.new(sessions, "test.txt", :data => "data")
115
-
116
- # make sure the sftp channels we wanted to be done, start as done
117
- # (Upload.new marks each channel as not-done, so we have to do it here)
118
- sessions.each { |s| s.sftp.channel[:done] = true if s.sftp.channel[:needs_done] }
119
- upload.expects(:running?).times(11).returns(*([true]*10 + [false]))
120
- upload.process!
121
- end
122
-
123
- private
124
-
125
- def mock_sftp
126
- sftp = mock("sftp", :state => :open)
127
- sftp.stubs(:channel).returns(Hash.new)
128
- yield sftp if block_given?
129
- sftp
130
- end
131
- end