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
@@ -1,50 +1,42 @@
1
- class Module
2
- def const_during(constant, value)
3
- if const_defined?(constant)
4
- overridden = true
5
- saved = const_get(constant)
6
- remove_const(constant)
7
- end
1
+ unless defined?(TestExtensions)
2
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
8
3
 
9
- const_set(constant, value)
10
- yield
11
- ensure
12
- if overridden
13
- remove_const(constant)
14
- const_set(constant, saved)
15
- end
4
+ begin
5
+ require 'rubygems'
6
+ gem 'mocha'
7
+ rescue LoadError
16
8
  end
17
- end
18
9
 
19
- class MockLogger
20
- def info(msg,pfx=nil) end
21
- def debug(msg,pfx=nil) end
22
- end
10
+ require 'test/unit'
11
+ require 'mocha'
12
+ require 'capistrano/server_definition'
23
13
 
24
- class MockConfiguration < Hash
25
- def initialize(*args)
26
- super
27
- self[:release_path] = "/path/to/releases/version"
28
- self[:ssh_options] = {}
29
- end
14
+ module TestExtensions
15
+ def server(host, options={})
16
+ Capistrano::ServerDefinition.new(host, options)
17
+ end
30
18
 
31
- def logger
32
- @logger ||= MockLogger.new
33
- end
19
+ def namespace(fqn=nil)
20
+ space = stub(:roles => {}, :fully_qualified_name => fqn, :default_task => nil)
21
+ yield(space) if block_given?
22
+ space
23
+ end
34
24
 
35
- def set(variable, value=nil, &block)
36
- self[variable] = value
37
- end
25
+ def role(space, name, *args)
26
+ opts = args.last.is_a?(Hash) ? args.pop : {}
27
+ space.roles[name] ||= []
28
+ space.roles[name].concat(args.map { |h| Capistrano::ServerDefinition.new(h, opts) })
29
+ end
38
30
 
39
- def respond_to?(sym)
40
- self.has_key?(sym)
31
+ def new_task(name, namespace=@namespace, options={}, &block)
32
+ block ||= Proc.new {}
33
+ task = Capistrano::TaskDefinition.new(name, namespace, options, &block)
34
+ assert_equal block, task.body
35
+ return task
36
+ end
41
37
  end
42
38
 
43
- def method_missing(sym, *args)
44
- if args.length == 0
45
- self[sym]
46
- else
47
- super
48
- end
39
+ class Test::Unit::TestCase
40
+ include TestExtensions
49
41
  end
50
42
  end
@@ -0,0 +1,24 @@
1
+ require "#{File.dirname(__FILE__)}/utils"
2
+ require 'capistrano/version'
3
+
4
+ class VersionTest < Test::Unit::TestCase
5
+ def test_check_should_return_true_for_matching_parameters
6
+ assert Capistrano::Version.check([2], [2])
7
+ assert Capistrano::Version.check([2,1], [2,1])
8
+ assert Capistrano::Version.check([2,1,5], [2,1,5])
9
+ end
10
+
11
+ def test_check_should_return_true_if_first_is_less_than_second
12
+ assert Capistrano::Version.check([2], [3])
13
+ assert Capistrano::Version.check([2], [2,1])
14
+ assert Capistrano::Version.check([2,1], [2,2])
15
+ assert Capistrano::Version.check([2,1], [2,1,1])
16
+ end
17
+
18
+ def test_check_should_return_false_if_first_is_greater_than_second
19
+ assert !Capistrano::Version.check([3], [2])
20
+ assert !Capistrano::Version.check([3,1], [3])
21
+ assert !Capistrano::Version.check([3,2], [3,1])
22
+ assert !Capistrano::Version.check([3,2,1], [3,2])
23
+ end
24
+ end
metadata CHANGED
@@ -1,136 +1,183 @@
1
1
  --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
2
4
  name: capistrano
3
5
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
6
+ version: 2.0.0
7
+ date: 2007-07-21 00:00:00 -06:00
8
+ summary: Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.
9
+ require_paths:
10
+ - lib
11
+ email: jamis@37signals.com
12
+ homepage: http://www.capify.org
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: capistrano
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
5
25
  platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
6
29
  authors:
7
30
  - Jamis Buck
8
- autorequire: capistrano
9
- bindir: bin
10
- cert_chain: []
11
-
12
- date: 2008-04-29 00:00:00 -06:00
13
- default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: rake
17
- version_requirement:
18
- version_requirements: !ruby/object:Gem::Requirement
19
- requirements:
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 0.7.0
23
- version:
24
- - !ruby/object:Gem::Dependency
25
- name: net-ssh
26
- version_requirement:
27
- version_requirements: !ruby/object:Gem::Requirement
28
- requirements:
29
- - - ">="
30
- - !ruby/object:Gem::Version
31
- version: 1.0.10
32
- - - <
33
- - !ruby/object:Gem::Version
34
- version: 1.99.0
35
- version:
36
- - !ruby/object:Gem::Dependency
37
- name: net-sftp
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: 1.1.0
44
- - - <
45
- - !ruby/object:Gem::Version
46
- version: 1.99.0
47
- version:
48
- description:
49
- email: jamis@37signals.com
50
- executables:
51
- - cap
52
- extensions: []
53
-
54
- extra_rdoc_files: []
55
-
56
31
  files:
57
32
  - bin/cap
33
+ - bin/capify
58
34
  - lib/capistrano
59
- - lib/capistrano/actor.rb
35
+ - lib/capistrano/callback.rb
36
+ - lib/capistrano/cli
37
+ - lib/capistrano/cli/execute.rb
38
+ - lib/capistrano/cli/help.rb
39
+ - lib/capistrano/cli/help.txt
40
+ - lib/capistrano/cli/options.rb
41
+ - lib/capistrano/cli/ui.rb
60
42
  - lib/capistrano/cli.rb
61
43
  - lib/capistrano/command.rb
44
+ - lib/capistrano/configuration
45
+ - lib/capistrano/configuration/actions
46
+ - lib/capistrano/configuration/actions/file_transfer.rb
47
+ - lib/capistrano/configuration/actions/inspect.rb
48
+ - lib/capistrano/configuration/actions/invocation.rb
49
+ - lib/capistrano/configuration/callbacks.rb
50
+ - lib/capistrano/configuration/connections.rb
51
+ - lib/capistrano/configuration/execution.rb
52
+ - lib/capistrano/configuration/loading.rb
53
+ - lib/capistrano/configuration/namespaces.rb
54
+ - lib/capistrano/configuration/roles.rb
55
+ - lib/capistrano/configuration/servers.rb
56
+ - lib/capistrano/configuration/variables.rb
62
57
  - lib/capistrano/configuration.rb
58
+ - lib/capistrano/errors.rb
63
59
  - lib/capistrano/extensions.rb
64
60
  - lib/capistrano/gateway.rb
65
- - lib/capistrano/generators
66
- - lib/capistrano/generators/rails
67
- - lib/capistrano/generators/rails/deployment
68
- - lib/capistrano/generators/rails/deployment/deployment_generator.rb
69
- - lib/capistrano/generators/rails/deployment/templates
70
- - lib/capistrano/generators/rails/deployment/templates/capistrano.rake
71
- - lib/capistrano/generators/rails/deployment/templates/deploy.rb
72
- - lib/capistrano/generators/rails/loader.rb
73
61
  - lib/capistrano/logger.rb
74
62
  - lib/capistrano/recipes
63
+ - lib/capistrano/recipes/compat.rb
64
+ - lib/capistrano/recipes/deploy
65
+ - lib/capistrano/recipes/deploy/dependencies.rb
66
+ - lib/capistrano/recipes/deploy/local_dependency.rb
67
+ - lib/capistrano/recipes/deploy/remote_dependency.rb
68
+ - lib/capistrano/recipes/deploy/scm
69
+ - lib/capistrano/recipes/deploy/scm/base.rb
70
+ - lib/capistrano/recipes/deploy/scm/bzr.rb
71
+ - lib/capistrano/recipes/deploy/scm/cvs.rb
72
+ - lib/capistrano/recipes/deploy/scm/darcs.rb
73
+ - lib/capistrano/recipes/deploy/scm/mercurial.rb
74
+ - lib/capistrano/recipes/deploy/scm/perforce.rb
75
+ - lib/capistrano/recipes/deploy/scm/subversion.rb
76
+ - lib/capistrano/recipes/deploy/scm.rb
77
+ - lib/capistrano/recipes/deploy/strategy
78
+ - lib/capistrano/recipes/deploy/strategy/base.rb
79
+ - lib/capistrano/recipes/deploy/strategy/checkout.rb
80
+ - lib/capistrano/recipes/deploy/strategy/copy.rb
81
+ - lib/capistrano/recipes/deploy/strategy/export.rb
82
+ - lib/capistrano/recipes/deploy/strategy/remote.rb
83
+ - lib/capistrano/recipes/deploy/strategy/remote_cache.rb
84
+ - lib/capistrano/recipes/deploy/strategy.rb
85
+ - lib/capistrano/recipes/deploy/templates
86
+ - lib/capistrano/recipes/deploy/templates/maintenance.rhtml
87
+ - lib/capistrano/recipes/deploy.rb
75
88
  - lib/capistrano/recipes/standard.rb
76
89
  - lib/capistrano/recipes/templates
77
90
  - lib/capistrano/recipes/templates/maintenance.rhtml
78
- - lib/capistrano/scm
79
- - lib/capistrano/scm/base.rb
80
- - lib/capistrano/scm/baz.rb
81
- - lib/capistrano/scm/bzr.rb
82
- - lib/capistrano/scm/cvs.rb
83
- - lib/capistrano/scm/darcs.rb
84
- - lib/capistrano/scm/mercurial.rb
85
- - lib/capistrano/scm/perforce.rb
86
- - lib/capistrano/scm/subversion.rb
91
+ - lib/capistrano/recipes/upgrade.rb
92
+ - lib/capistrano/server_definition.rb
87
93
  - lib/capistrano/shell.rb
88
94
  - lib/capistrano/ssh.rb
89
- - lib/capistrano/transfer.rb
90
- - lib/capistrano/utils.rb
95
+ - lib/capistrano/task_definition.rb
96
+ - lib/capistrano/upload.rb
91
97
  - lib/capistrano/version.rb
92
98
  - lib/capistrano.rb
93
99
  - examples/sample.rb
94
- - test/actor_test.rb
100
+ - test/cli
101
+ - test/cli/execute_test.rb
102
+ - test/cli/help_test.rb
103
+ - test/cli/options_test.rb
104
+ - test/cli/ui_test.rb
105
+ - test/cli_test.rb
95
106
  - test/command_test.rb
107
+ - test/configuration
108
+ - test/configuration/actions
109
+ - test/configuration/actions/file_transfer_test.rb
110
+ - test/configuration/actions/inspect_test.rb
111
+ - test/configuration/actions/invocation_test.rb
112
+ - test/configuration/callbacks_test.rb
113
+ - test/configuration/connections_test.rb
114
+ - test/configuration/execution_test.rb
115
+ - test/configuration/loading_test.rb
116
+ - test/configuration/namespace_dsl_test.rb
117
+ - test/configuration/roles_test.rb
118
+ - test/configuration/servers_test.rb
119
+ - test/configuration/variables_test.rb
96
120
  - test/configuration_test.rb
121
+ - test/deploy
122
+ - test/deploy/scm
123
+ - test/deploy/scm/base_test.rb
124
+ - test/deploy/strategy
125
+ - test/deploy/strategy/copy_test.rb
126
+ - test/extensions_test.rb
97
127
  - test/fixtures
128
+ - test/fixtures/cli_integration.rb
98
129
  - test/fixtures/config.rb
99
130
  - test/fixtures/custom.rb
100
- - test/scm
101
- - test/scm/cvs_test.rb
102
- - test/scm/subversion_test.rb
131
+ - test/gateway_test.rb
132
+ - test/logger_test.rb
133
+ - test/server_definition_test.rb
134
+ - test/shell_test.rb
103
135
  - test/ssh_test.rb
136
+ - test/task_definition_test.rb
137
+ - test/upload_test.rb
104
138
  - test/utils.rb
139
+ - test/version_test.rb
105
140
  - README
106
141
  - MIT-LICENSE
107
142
  - CHANGELOG
108
- - THANKS
109
- has_rdoc: false
110
- homepage: http://www.rubyonrails.org
111
- post_install_message:
143
+ test_files: []
144
+
112
145
  rdoc_options: []
113
146
 
114
- require_paths:
115
- - lib
116
- required_ruby_version: !ruby/object:Gem::Requirement
117
- requirements:
118
- - - ">="
119
- - !ruby/object:Gem::Version
120
- version: "0"
121
- version:
122
- required_rubygems_version: !ruby/object:Gem::Requirement
123
- requirements:
124
- - - ">="
125
- - !ruby/object:Gem::Version
126
- version: "0"
127
- version:
128
- requirements: []
147
+ extra_rdoc_files: []
129
148
 
130
- rubyforge_project:
131
- rubygems_version: 1.1.1
132
- signing_key:
133
- specification_version: 2
134
- 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.
135
- test_files: []
149
+ executables:
150
+ - cap
151
+ - capify
152
+ extensions: []
136
153
 
154
+ requirements: []
155
+
156
+ dependencies:
157
+ - !ruby/object:Gem::Dependency
158
+ name: net-ssh
159
+ version_requirement:
160
+ version_requirements: !ruby/object:Gem::Version::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: 1.0.10
165
+ version:
166
+ - !ruby/object:Gem::Dependency
167
+ name: net-sftp
168
+ version_requirement:
169
+ version_requirements: !ruby/object:Gem::Version::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 1.1.0
174
+ version:
175
+ - !ruby/object:Gem::Dependency
176
+ name: highline
177
+ version_requirement:
178
+ version_requirements: !ruby/object:Gem::Version::Requirement
179
+ requirements:
180
+ - - ">"
181
+ - !ruby/object:Gem::Version
182
+ version: 0.0.0
183
+ version:
data/THANKS DELETED
@@ -1,4 +0,0 @@
1
- * Bazaar (v1) SCM Module: Edd Dumbill <edd@usefulinc.com>
2
- * Perforce SCM Module: Richard McMahon <richie.mcmahon@gmail.com>
3
- * Bazaar-NG SCM Module: Damien Merenne <dam@capsule.org>
4
- * Mercurial SCM Module: Matthew Elder <mae@mindflowsolutions.com>
@@ -1,567 +0,0 @@
1
- require 'erb'
2
- require 'capistrano/command'
3
- require 'capistrano/transfer'
4
- require 'capistrano/gateway'
5
- require 'capistrano/ssh'
6
- require 'capistrano/utils'
7
-
8
- module Capistrano
9
-
10
- # An Actor is the entity that actually does the work of determining which
11
- # servers should be the target of a particular task, and of executing the
12
- # task on each of them in parallel. An Actor is never instantiated
13
- # directly--rather, you create a new Configuration instance, and access the
14
- # new actor via Configuration#actor.
15
- class Actor
16
-
17
- # An adaptor for making the SSH interface look and act like that of the
18
- # Gateway class.
19
- class DefaultConnectionFactory #:nodoc:
20
- def initialize(config)
21
- @config= config
22
- end
23
-
24
- def connect_to(server)
25
- SSH.connect(server, @config)
26
- end
27
- end
28
-
29
- class <<self
30
- attr_accessor :connection_factory
31
- attr_accessor :command_factory
32
- attr_accessor :transfer_factory
33
- attr_accessor :default_io_proc
34
- end
35
-
36
- self.connection_factory = DefaultConnectionFactory
37
- self.command_factory = Command
38
- self.transfer_factory = Transfer
39
-
40
- self.default_io_proc = Proc.new do |ch, stream, out|
41
- level = stream == :err ? :important : :info
42
- ch[:actor].logger.send(level, out, "#{stream} :: #{ch[:host]}")
43
- end
44
-
45
- # The configuration instance associated with this actor.
46
- attr_reader :configuration
47
-
48
- # A hash of the tasks known to this actor, keyed by name. The values are
49
- # instances of Actor::Task.
50
- attr_reader :tasks
51
-
52
- # A hash of the SSH sessions that are currently open and available.
53
- # Because sessions are constructed lazily, this will only contain
54
- # connections to those servers that have been the targets of one or more
55
- # executed tasks.
56
- attr_reader :sessions
57
-
58
- # The call stack of the tasks. The currently executing task may inspect
59
- # this to see who its caller was. The current task is always the last
60
- # element of this stack.
61
- attr_reader :task_call_frames
62
-
63
- # The history of executed tasks. This will be an array of all tasks that
64
- # have been executed, in the order in which they were called.
65
- attr_reader :task_call_history
66
-
67
- # A struct for representing a single instance of an invoked task.
68
- TaskCallFrame = Struct.new(:name, :rollback)
69
-
70
- # Represents the definition of a single task.
71
- class Task #:nodoc:
72
- attr_reader :name, :actor, :options
73
-
74
- def initialize(name, actor, options)
75
- @name, @actor, @options = name, actor, options
76
- @servers = nil
77
- end
78
-
79
- # Returns the list of servers (_not_ connections to servers) that are
80
- # the target of this task.
81
- def servers(reevaluate=false)
82
- @servers = nil if reevaluate
83
- @servers ||=
84
- if hosts = find_hosts
85
- hosts
86
- else
87
- roles = find_roles
88
- apply_only!(roles)
89
- apply_except!(roles)
90
-
91
- roles.map { |role| role.host }.uniq
92
- end
93
- end
94
-
95
- private
96
- def find_roles
97
- role_names = [ *(environment_values(:roles, true) || @options[:roles] || actor.configuration.roles.keys) ]
98
- role_names.collect do |name|
99
- actor.configuration.roles[name] ||
100
- raise(ArgumentError, "task #{self.name.inspect} references non-existant role #{name.inspect}")
101
- end.flatten
102
- end
103
-
104
- def find_hosts
105
- environment_values(:hosts) || @options[:hosts]
106
- end
107
-
108
- def environment_values(key, use_symbols = false)
109
- if variable = ENV[key.to_s.upcase]
110
- values = variable.split(",")
111
- use_symbols ? values.collect { |e| e.to_sym } : values
112
- end
113
- end
114
-
115
- def apply_only!(roles)
116
- only = @options[:only] || {}
117
-
118
- unless only.empty?
119
- roles = roles.delete_if do |role|
120
- catch(:done) do
121
- only.keys.each { |key| throw(:done, true) if role.options[key] != only[key] }
122
- false
123
- end
124
- end
125
- end
126
- end
127
-
128
- def apply_except!(roles)
129
- except = @options[:except] || {}
130
-
131
- unless except.empty?
132
- roles = roles.delete_if do |role|
133
- catch(:done) do
134
- except.keys.each { |key| throw(:done, true) if role.options[key] == except[key] }
135
- false
136
- end
137
- end
138
- end
139
- end
140
- end
141
-
142
- def initialize(config) #:nodoc:
143
- @configuration = config
144
- @tasks = {}
145
- @task_call_frames = []
146
- @sessions = {}
147
- @factory = self.class.connection_factory.new(configuration)
148
- end
149
-
150
- # Define a new task for this actor. The block will be invoked when this
151
- # task is called.
152
- def define_task(name, options={}, &block)
153
- @tasks[name] = (options[:task_class] || Task).new(name, self, options)
154
- define_method(name) do
155
- send "before_#{name}" if respond_to? "before_#{name}"
156
- logger.debug "executing task #{name}"
157
- begin
158
- push_task_call_frame name
159
- result = instance_eval(&block)
160
- ensure
161
- pop_task_call_frame
162
- end
163
- send "after_#{name}" if respond_to? "after_#{name}"
164
- result
165
- end
166
- end
167
-
168
- # Iterates over each task, in alphabetical order. A hash object is
169
- # yielded for each task, which includes the task's name (:name), the
170
- # length of the longest task name (:longest), and the task's description,
171
- # reformatted as a single line (:desc).
172
- def each_task
173
- keys = tasks.keys.sort_by { |a| a.to_s }
174
- longest = keys.inject(0) { |len,key| key.to_s.length > len ? key.to_s.length : len } + 2
175
-
176
- keys.sort_by { |a| a.to_s }.each do |key|
177
- desc = (tasks[key].options[:desc] || "").gsub(/(?:\r?\n)+[ \t]*/, " ").strip
178
- info = { :task => key, :longest => longest, :desc => desc }
179
- yield info
180
- end
181
- end
182
-
183
- # Dump all tasks and (brief) descriptions in YAML format for consumption
184
- # by other processes. Returns a string containing the YAML-formatted data.
185
- def dump_tasks
186
- data = ""
187
- each_task do |info|
188
- desc = info[:desc].split(/\. /).first || ""
189
- desc << "." if !desc.empty? && desc[-1] != ?.
190
- data << "#{info[:task]}: #{desc}\n"
191
- end
192
- data
193
- end
194
-
195
- # Execute the given command on all servers that are the target of the
196
- # current task. If a block is given, it is invoked for all output
197
- # generated by the command, and should accept three parameters: the SSH
198
- # channel (which may be used to send data back to the remote process),
199
- # the stream identifier (<tt>:err</tt> for stderr, and <tt>:out</tt> for
200
- # stdout), and the data that was received.
201
- #
202
- # If +pretend+ mode is active, this does nothing.
203
- def run(cmd, options={}, &block)
204
- block ||= default_io_proc
205
- logger.debug "executing #{cmd.strip.inspect}"
206
-
207
- execute_on_servers(options) do |servers|
208
- # execute the command on each server in parallel
209
- command = self.class.command_factory.new(servers, cmd, block, options, self)
210
- command.process! # raises an exception if command fails on any server
211
- end
212
- end
213
-
214
- # Streams the result of the command from all servers that are the target of the
215
- # current task. All these streams will be joined into a single one,
216
- # so you can, say, watch 10 log files as though they were one. Do note that this
217
- # is quite expensive from a bandwidth perspective, so use it with care.
218
- #
219
- # Example:
220
- #
221
- # desc "Run a tail on multiple log files at the same time"
222
- # task :tail_fcgi, :roles => :app do
223
- # stream "tail -f #{shared_path}/log/fastcgi.crash.log"
224
- # end
225
- def stream(command)
226
- run(command) do |ch, stream, out|
227
- puts out if stream == :out
228
- if stream == :err
229
- puts "[err : #{ch[:host]}] #{out}"
230
- break
231
- end
232
- end
233
- end
234
-
235
- # Deletes the given file from all servers targetted by the current task.
236
- # If <tt>:recursive => true</tt> is specified, it may be used to remove
237
- # directories.
238
- def delete(path, options={})
239
- cmd = "rm -%sf #{path}" % (options[:recursive] ? "r" : "")
240
- run(cmd, options)
241
- end
242
-
243
- # Store the given data at the given location on all servers targetted by
244
- # the current task. If <tt>:mode</tt> is specified it is used to set the
245
- # mode on the file.
246
- def put(data, path, options={})
247
- if Capistrano::SFTP
248
- execute_on_servers(options) do |servers|
249
- transfer = self.class.transfer_factory.new(servers, self, path, :data => data,
250
- :mode => options[:mode])
251
- transfer.process!
252
- end
253
- else
254
- # Poor-man's SFTP... just run a cat on the remote end, and send data
255
- # to it.
256
-
257
- cmd = "cat > #{path}"
258
- cmd << " && chmod #{options[:mode].to_s(8)} #{path}" if options[:mode]
259
- run(cmd, options.merge(:data => data + "\n\4")) do |ch, stream, out|
260
- logger.important out, "#{stream} :: #{ch[:host]}" if stream == :err
261
- end
262
- end
263
- end
264
-
265
- # Get file remote_path from FIRST server targetted by
266
- # the current task and transfer it to local machine as path. It will use
267
- # SFTP if Net::SFTP is installed; otherwise it will fall back to using
268
- # 'cat', which may cause corruption in binary files.
269
- #
270
- # get "#{deploy_to}/current/log/production.log", "log/production.log.web"
271
- def get(remote_path, path, options = {})
272
- if Capistrano::SFTP && options.fetch(:sftp, true)
273
- execute_on_servers(options.merge(:once => true)) do |servers|
274
- logger.debug "downloading #{servers.first}:#{remote_path} to #{path}"
275
- sftp = sessions[servers.first].sftp
276
- sftp.connect unless sftp.state == :open
277
- sftp.get_file remote_path, path
278
- logger.trace "download finished"
279
- end
280
- else
281
- logger.important "Net::SFTP is not available; using remote 'cat' to get file, which may cause file corruption"
282
- File.open(path, "w") do |destination|
283
- run "cat #{remote_path}", :once => true do |ch, stream, data|
284
- case stream
285
- when :out then destination << data
286
- when :err then raise "error while downloading #{remote_path}: #{data.inspect}"
287
- end
288
- end
289
- end
290
- end
291
- end
292
-
293
- # Executes the given command on the first server targetted by the current
294
- # task, collects it's stdout into a string, and returns the string.
295
- def capture(command, options={})
296
- output = ""
297
- run(command, options.merge(:once => true)) do |ch, stream, data|
298
- case stream
299
- when :out then output << data
300
- when :err then raise "error processing #{command.inspect}: #{data.inspect}"
301
- end
302
- end
303
- output
304
- end
305
-
306
- # Like #run, but executes the command via <tt>sudo</tt>. This assumes that
307
- # the sudo password (if required) is the same as the password for logging
308
- # in to the server.
309
- #
310
- # Also, this module accepts a <tt>:sudo</tt> configuration variable,
311
- # which (if specified) will be used as the full path to the sudo
312
- # executable on the remote machine:
313
- #
314
- # set :sudo, "/opt/local/bin/sudo"
315
- def sudo(command, options={}, &block)
316
- block ||= default_io_proc
317
-
318
- # in order to prevent _each host_ from prompting when the password was
319
- # wrong, let's track which host prompted first and only allow subsequent
320
- # prompts from that host.
321
- prompt_host = nil
322
- user = options[:as].nil? ? '' : "-u #{options[:as]}"
323
-
324
- run "#{sudo_command} #{user} #{command}", options do |ch, stream, out|
325
- if out =~ /^Password:/
326
- ch.send_data "#{password}\n"
327
- elsif out =~ /try again/
328
- if prompt_host.nil? || prompt_host == ch[:host]
329
- prompt_host = ch[:host]
330
- logger.important out, "#{stream} :: #{ch[:host]}"
331
- # reset the password to it's original value and prepare for another
332
- # pass (the reset allows the password prompt to be attempted again
333
- # if the password variable was originally a proc (the default)
334
- set :password, self[:original_value][:password] || self[:password]
335
- end
336
- else
337
- block.call(ch, stream, out)
338
- end
339
- end
340
- end
341
-
342
- # Renders an ERb template and returns the result. This is useful for
343
- # dynamically building documents to store on the remote servers.
344
- #
345
- # Usage:
346
- #
347
- # render("something", :foo => "hello")
348
- # look for "something.rhtml" in the current directory, or in the
349
- # capistrano/recipes/templates directory, and render it with
350
- # foo defined as a local variable with the value "hello".
351
- #
352
- # render(:file => "something", :foo => "hello")
353
- # same as above
354
- #
355
- # render(:template => "<%= foo %> world", :foo => "hello")
356
- # treat the given string as an ERb template and render it with
357
- # the given hash of local variables active.
358
- def render(*args)
359
- options = args.last.is_a?(Hash) ? args.pop : {}
360
- options[:file] = args.shift if args.first.is_a?(String)
361
- raise ArgumentError, "too many parameters" unless args.empty?
362
-
363
- case
364
- when options[:file]
365
- file = options.delete :file
366
- unless file[0] == ?/
367
- dirs = [".",
368
- File.join(File.dirname(__FILE__), "recipes", "templates")]
369
- dirs.each do |dir|
370
- if File.file?(File.join(dir, file))
371
- file = File.join(dir, file)
372
- break
373
- elsif File.file?(File.join(dir, file + ".rhtml"))
374
- file = File.join(dir, file + ".rhtml")
375
- break
376
- end
377
- end
378
- end
379
-
380
- render options.merge(:template => File.read(file))
381
-
382
- when options[:template]
383
- erb = ERB.new(options[:template])
384
- b = Proc.new { binding }.call
385
- options.each do |key, value|
386
- next if key == :template
387
- eval "#{key} = options[:#{key}]", b
388
- end
389
- erb.result(b)
390
-
391
- else
392
- raise ArgumentError, "no file or template given for rendering"
393
- end
394
- end
395
-
396
- # Inspects the remote servers to determine the list of all released versions
397
- # of the software. Releases are sorted with the most recent release last.
398
- def releases
399
- @releases ||= begin
400
- buffer = ""
401
- run "ls -x1 #{releases_path}", :once => true do |ch, str, out|
402
- buffer << out if str == :out
403
- raise "could not determine releases #{out.inspect}" if str == :err
404
- end
405
- buffer.split.sort
406
- end
407
- end
408
-
409
- # Returns the most recent deployed release
410
- def current_release
411
- release_path(releases.last)
412
- end
413
-
414
- # Returns the release immediately before the currently deployed one
415
- def previous_release
416
- release_path(releases[-2]) if releases[-2]
417
- end
418
-
419
- # Invoke a set of tasks in a transaction. If any task fails (raises an
420
- # exception), all tasks executed within the transaction are inspected to
421
- # see if they have an associated on_rollback hook, and if so, that hook
422
- # is called.
423
- def transaction
424
- if task_call_history
425
- yield
426
- else
427
- logger.info "transaction: start"
428
- begin
429
- @task_call_history = []
430
- yield
431
- logger.info "transaction: commit"
432
- rescue Object => e
433
- current = task_call_history.last
434
- logger.important "transaction: rollback", current ? current.name : "transaction start"
435
- task_call_history.reverse.each do |task|
436
- begin
437
- # throw the task back on the stack so that roles are properly
438
- # interpreted in the scope of the task in question.
439
- push_task_call_frame(task.name)
440
-
441
- logger.debug "rolling back", task.name
442
- task.rollback.call if task.rollback
443
- rescue Object => e
444
- logger.info "exception while rolling back: #{e.class}, #{e.message}", task.name
445
- ensure
446
- pop_task_call_frame
447
- end
448
- end
449
- raise
450
- ensure
451
- @task_call_history = nil
452
- end
453
- end
454
- end
455
-
456
- # Specifies an on_rollback hook for the currently executing task. If this
457
- # or any subsequent task then fails, and a transaction is active, this
458
- # hook will be executed.
459
- def on_rollback(&block)
460
- task_call_frames.last.rollback = block
461
- end
462
-
463
- # An instance-level reader for the class' #default_io_proc attribute.
464
- def default_io_proc
465
- self.class.default_io_proc
466
- end
467
-
468
- # Used to force connections to be made to the current task's servers.
469
- # Connections are normally made lazily in Capistrano--you can use this
470
- # to force them open before performing some operation that might be
471
- # time-sensitive.
472
- def connect!(options={})
473
- execute_on_servers(options) { }
474
- end
475
-
476
- def current_task
477
- return nil if task_call_frames.empty?
478
- tasks[task_call_frames.last.name]
479
- end
480
-
481
- def metaclass
482
- class << self; self; end
483
- end
484
-
485
- private
486
-
487
- def sudo_command
488
- configuration[:sudo] || "sudo"
489
- end
490
-
491
- def define_method(name, &block)
492
- metaclass.send(:define_method, name, &block)
493
- end
494
-
495
- def push_task_call_frame(name)
496
- frame = TaskCallFrame.new(name)
497
- task_call_frames.push frame
498
- task_call_history.push frame if task_call_history
499
- end
500
-
501
- def pop_task_call_frame
502
- task_call_frames.pop
503
- end
504
-
505
- def establish_connections(servers)
506
- @factory = establish_gateway if needs_gateway?
507
- servers = Array(servers)
508
-
509
- # because Net::SSH uses lazy loading for things, we need to make sure
510
- # that at least one connection has been made successfully, to kind of
511
- # "prime the pump", before we go gung-ho and do mass connection in
512
- # parallel. Otherwise, the threads start doing things in wierd orders
513
- # and causing Net::SSH to die of confusion.
514
-
515
- if !@established_gateway && @sessions.empty?
516
- server, servers = servers.first, servers[1..-1]
517
- @sessions[server] = @factory.connect_to(server)
518
- end
519
-
520
- servers.map { |server| establish_connection_to(server) }.each { |t| t.join }
521
- end
522
-
523
- # We establish the connection by creating a thread in a new method--this
524
- # prevents problems with the thread's scope seeing the wrong 'server'
525
- # variable if the thread just happens to take too long to start up.
526
- def establish_connection_to(server)
527
- Thread.new { @sessions[server] ||= @factory.connect_to(server) }
528
- end
529
-
530
- def establish_gateway
531
- logger.debug "establishing connection to gateway #{gateway}"
532
- @established_gateway = true
533
- Gateway.new(gateway, configuration)
534
- end
535
-
536
- def needs_gateway?
537
- gateway && !@established_gateway
538
- end
539
-
540
- def execute_on_servers(options)
541
- task = current_task
542
- servers = task.servers
543
-
544
- if servers.empty?
545
- raise "The #{task.name} task is only run for servers matching #{task.options.inspect}, but no servers matched"
546
- end
547
-
548
- servers = [servers.first] if options[:once]
549
- logger.trace "servers: #{servers.inspect}"
550
-
551
- if !pretend
552
- # establish connections to those servers, as necessary
553
- establish_connections(servers)
554
- yield servers
555
- end
556
- end
557
-
558
- def method_missing(sym, *args, &block)
559
- if @configuration.respond_to?(sym)
560
- @configuration.send(sym, *args, &block)
561
- else
562
- super
563
- end
564
- end
565
-
566
- end
567
- end