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
@@ -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