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
@@ -0,0 +1,53 @@
1
+ -----------------------------
2
+ <%= color('Capistrano', :bold) %>
3
+ -----------------------------
4
+
5
+ Capistrano is a utility for automating the execution of commands across multiple remote machines. It was originally conceived as an aid to deploy Ruby on Rails web applications, but has since evolved to become a much more general-purpose tool.
6
+
7
+ The command-line interface to Capistrano is via the `cap' command.
8
+
9
+ cap [ option ] ... action ...
10
+
11
+ The following options are understood:
12
+
13
+ <%= color '-e, --explain TASK', :bold %>
14
+ Displays the extended description of the given task. Not all tasks will have an extended description, but for those that do, this can provide a wealth of additional usage information, such as describing environment variables or settings that can affect the execution of the task.
15
+
16
+ <%= color '-F, --default-config', :bold %>
17
+ By default, cap will search for a config file named `Capfile' or `capfile' in the current directory, or in any parent directory, and will automatically load it. However, if you specify the -f flag (see below), cap will use that file instead of the default config. If you want to use both the default config, and files loaded via -f, you can specify -F to force cap to search for and load the default config, even if additional files were specified via -f.
18
+
19
+ <%= color '-f, --file FILE', :bold %>
20
+ Causes the named file to be loaded. Capistrano will search both its own recipe directory, as well as the current directory, looking for the named file. An ".rb" extension is optional. The -f option may be given any number of times, but if it is given, it will take the place of the normal `Capfile' or `capfile' detection. Use -F if you want the default capfile to be loaded when you use -f.
21
+
22
+ <%= color '-H, --long-help', :bold %>
23
+ Displays this document and exits.
24
+
25
+ <%= color '-h, --help', :bold %>
26
+ Shows a brief summary of these options and exits.
27
+
28
+ <%= color '-p, --password', :bold %>
29
+ Normally, cap will prompt for the password on-demand, the first time it is needed. This can make it hard to walk away from Capistrano, since you might not know if it will prompt for a password down the road. In such cases, you can use the -p option to force cap to prompt for the password immediately.
30
+
31
+ <%= color '-q, --quiet', :bold %>
32
+ Display only critical error messages. All other output is suppressed.
33
+
34
+ <%= color '-S, --set-before NAME=VALUE', :bold %>
35
+ Sets the given variable to the given value, before loading any recipe files. This is useful if you have a recipe file that depends on a certain variable being set, at the time it is loaded.
36
+
37
+ <%= color '-s, --set NAME=VALUE', :bold %>
38
+ Sets the given variable to the given value, after loading all recipe files. This is useful when you want to override the value of a variable which is used in a task. Note that this will set the variables too late for them to affect conditions that are executed as the recipes are loaded.
39
+
40
+ <%= color '-T, --tasks', :bold %>
41
+ Displays the list of all tasks in all loaded recipe files. If a task has no description, or if the description starts with the [internal] tag, the task will not be listed unless you also specify -v.
42
+
43
+ <%= color '-V, --version', :bold %>
44
+ Shows the current Capistrano version number and exits.
45
+
46
+ <%= color '-v, --verbose', :bold %>
47
+ Increase the verbosity. You can specify this option up to three times to further increase verbosity. By default, cap will use maximum verbosity, but if you specify an explicit verbosity, that will be used instead. See also -q.
48
+
49
+ <%= color '-X, --skip-system-config', :bold %>
50
+ By default, cap will look for and (if it exists) load the global system configuration file located in /etc/capistrano.conf. If you don't want cap to load that file, give this option.
51
+
52
+ <%= color '-x, --skip-user-config', :bold %>
53
+ By default, cap will look for and (if it exists) load the user-specific configuration file located in $HOME/.caprc. If you don't want cap to load that file, give this option.
@@ -0,0 +1,183 @@
1
+ require 'optparse'
2
+
3
+ module Capistrano
4
+ class CLI
5
+ module Options
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ # Return a new CLI instance with the given arguments pre-parsed and
12
+ # ready for execution.
13
+ def parse(args)
14
+ cli = new(args)
15
+ cli.parse_options!
16
+ cli
17
+ end
18
+ end
19
+
20
+ # The hash of (parsed) command-line options
21
+ attr_reader :options
22
+
23
+ # Return an OptionParser instance that defines the acceptable command
24
+ # line switches for Capistrano, and what their corresponding behaviors
25
+ # are.
26
+ def option_parser #:nodoc:
27
+ @option_parser ||= OptionParser.new do |opts|
28
+ opts.banner = "Usage: #{File.basename($0)} [options] action ..."
29
+
30
+ opts.on("-e", "--explain TASK",
31
+ "Displays help (if available) for the task."
32
+ ) { |value| options[:explain] = value }
33
+
34
+ opts.on("-F", "--default-config",
35
+ "Always use default config, even with -f."
36
+ ) { options[:default_config] = true }
37
+
38
+ opts.on("-f", "--file FILE",
39
+ "A recipe file to load. May be given more than once."
40
+ ) { |value| options[:recipes] << value }
41
+
42
+ opts.on("-H", "--long-help", "Explain these options.") do
43
+ long_help
44
+ exit
45
+ end
46
+
47
+ opts.on("-h", "--help", "Display this help message.") do
48
+ puts opts
49
+ exit
50
+ end
51
+
52
+ opts.on("-p", "--password",
53
+ "Immediately prompt for the password."
54
+ ) { options[:password] = nil }
55
+
56
+ opts.on("-q", "--quiet",
57
+ "Make the output as quiet as possible (default)"
58
+ ) { options[:verbose] = 0 }
59
+
60
+ opts.on("-S", "--set-before NAME=VALUE",
61
+ "Set a variable before the recipes are loaded."
62
+ ) do |pair|
63
+ name, value = pair.split(/=/, 2)
64
+ options[:pre_vars][name.to_sym] = value
65
+ end
66
+
67
+ opts.on("-s", "--set NAME=VALUE",
68
+ "Set a variable after the recipes are loaded."
69
+ ) do |pair|
70
+ name, value = pair.split(/=/, 2)
71
+ options[:vars][name.to_sym] = value
72
+ end
73
+
74
+ opts.on("-T", "--tasks",
75
+ "List all tasks in the loaded recipe files."
76
+ ) do
77
+ options[:tasks] = true
78
+ options[:verbose] ||= 0
79
+ end
80
+
81
+ opts.on("-V", "--version",
82
+ "Display the Capistrano version, and exit."
83
+ ) do
84
+ require 'capistrano/version'
85
+ puts "Capistrano v#{Capistrano::Version::STRING}"
86
+ exit
87
+ end
88
+
89
+ opts.on("-v", "--verbose",
90
+ "Be more verbose. May be given more than once."
91
+ ) { options[:verbose] ||= 0; options[:verbose] += 1 }
92
+
93
+ opts.on("-X", "--skip-system-config",
94
+ "Don't load the system config file (capistrano.conf)"
95
+ ) { options.delete(:sysconf) }
96
+
97
+ opts.on("-x", "--skip-user-config",
98
+ "Don't load the user config file (.caprc)"
99
+ ) { options.delete(:dotfile) }
100
+ end
101
+ end
102
+
103
+ # If the arguments to the command are empty, this will print the
104
+ # allowed options and exit. Otherwise, it will parse the command
105
+ # line and set up any default options.
106
+ def parse_options! #:nodoc:
107
+ @options = { :recipes => [], :actions => [],
108
+ :vars => {}, :pre_vars => {},
109
+ :sysconf => default_sysconf, :dotfile => default_dotfile }
110
+
111
+ if args.empty?
112
+ warn "Please specify at least one action to execute."
113
+ warn option_parser
114
+ exit
115
+ end
116
+
117
+ option_parser.parse!(args)
118
+
119
+ # if no verbosity has been specified, be verbose
120
+ options[:verbose] = 3 if !options.has_key?(:verbose)
121
+
122
+ look_for_default_recipe_file! if options[:default_config] || options[:recipes].empty?
123
+ extract_environment_variables!
124
+
125
+ options[:actions].concat(args)
126
+
127
+ password = options.has_key?(:password)
128
+ options[:password] = Proc.new { self.class.password_prompt }
129
+ options[:password] = options[:password].call if password
130
+ end
131
+
132
+ # Extracts name=value pairs from the remaining command-line arguments
133
+ # and assigns them as environment variables.
134
+ def extract_environment_variables! #:nodoc:
135
+ args.delete_if do |arg|
136
+ next unless arg.match(/^(\w+)=(.*)$/)
137
+ ENV[$1] = $2
138
+ end
139
+ end
140
+
141
+ # Looks for a default recipe file in the current directory.
142
+ def look_for_default_recipe_file! #:nodoc:
143
+ current = Dir.pwd
144
+
145
+ loop do
146
+ %w(Capfile capfile).each do |file|
147
+ if File.file?(file)
148
+ options[:recipes] << file
149
+ return
150
+ end
151
+ end
152
+
153
+ pwd = Dir.pwd
154
+ Dir.chdir("..")
155
+ break if pwd == Dir.pwd # if changing the directory made no difference, then we're at the top
156
+ end
157
+
158
+ Dir.chdir(current)
159
+ end
160
+
161
+ def default_sysconf #:nodoc:
162
+ File.join(sysconf_directory, "capistrano.conf")
163
+ end
164
+
165
+ def default_dotfile #:nodoc:
166
+ File.join(home_directory, ".caprc")
167
+ end
168
+
169
+ def sysconf_directory #:nodoc:
170
+ # TODO if anyone cares, feel free to submit a patch that uses a more
171
+ # appropriate location for this file in Windows.
172
+ ENV["SystemRoot"] || '/etc'
173
+ end
174
+
175
+ def home_directory #:nodoc:
176
+ ENV["HOME"] ||
177
+ (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
178
+ "/"
179
+ end
180
+
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,28 @@
1
+ require 'highline'
2
+
3
+ # work around problem where HighLine detects an eof on $stdin and raises an
4
+ # error.
5
+ HighLine.track_eof = false
6
+
7
+ module Capistrano
8
+ class CLI
9
+ module UI
10
+ def self.included(base) #:nodoc:
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ # Return the object that provides UI-specific methods, such as prompts
16
+ # and more.
17
+ def ui
18
+ @ui ||= HighLine.new
19
+ end
20
+
21
+ # Prompt for a password using echo suppression.
22
+ def password_prompt(prompt="Password: ")
23
+ ui.ask(prompt) { |q| q.echo = false }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,28 +1,36 @@
1
+ require 'capistrano/errors'
2
+
1
3
  module Capistrano
2
4
 
3
5
  # This class encapsulates a single command to be executed on a set of remote
4
6
  # machines, in parallel.
5
7
  class Command
6
- class Error < RuntimeError; end
8
+ attr_reader :command, :sessions, :options
7
9
 
8
- attr_reader :servers, :command, :options, :actor
10
+ def self.process(command, sessions, options={}, &block)
11
+ new(command, sessions, options, &block).process!
12
+ end
9
13
 
10
- def initialize(servers, command, callback, options, actor) #:nodoc:
11
- @servers = servers
12
- @command = extract_environment(options) + command.strip.gsub(/\r?\n/, "\\\n")
13
- @callback = callback
14
+ # Instantiates a new command object. The +command+ must be a string
15
+ # containing the command to execute. +sessions+ is an array of Net::SSH
16
+ # session instances, and +options+ must be a hash containing any of the
17
+ # following keys:
18
+ #
19
+ # * +logger+: (optional), a Capistrano::Logger instance
20
+ # * +data+: (optional), a string to be sent to the command via it's stdin
21
+ # * +env+: (optional), a string or hash to be interpreted as environment
22
+ # variables that should be defined for this command invocation.
23
+ def initialize(command, sessions, options={}, &block)
24
+ @command = command.strip.gsub(/\r?\n/, "\\\n")
25
+ @sessions = sessions
14
26
  @options = options
15
- @actor = actor
27
+ @callback = block
16
28
  @channels = open_channels
17
29
  end
18
30
 
19
- def logger #:nodoc:
20
- actor.logger
21
- end
22
-
23
31
  # Processes the command in parallel on all specified hosts. If the command
24
32
  # fails (non-zero return code) on any of the hosts, this will raise a
25
- # RuntimeError.
33
+ # Capistrano::CommandError.
26
34
  def process!
27
35
  since = Time.now
28
36
  loop do
@@ -41,10 +49,13 @@ module Capistrano
41
49
  sleep 0.01 # a brief respite, to keep the CPU from going crazy
42
50
  end
43
51
 
44
- logger.trace "command finished"
52
+ logger.trace "command finished" if logger
45
53
 
46
- if failed = @channels.detect { |ch| ch[:status] != 0 }
47
- raise Error, "command #{@command.inspect} failed on #{failed[:host]}"
54
+ if (failed = @channels.select { |ch| ch[:status] != 0 }).any?
55
+ hosts = failed.map { |ch| ch[:server] }
56
+ error = CommandError.new("command #{command.inspect} failed on #{hosts.join(',')}")
57
+ error.hosts = hosts
58
+ raise error
48
59
  end
49
60
 
50
61
  self
@@ -60,21 +71,33 @@ module Capistrano
60
71
 
61
72
  private
62
73
 
74
+ def logger
75
+ options[:logger]
76
+ end
77
+
63
78
  def open_channels
64
- @servers.map do |server|
65
- @actor.sessions[server].open_channel do |channel|
66
- channel[:host] = server
67
- channel[:actor] = @actor # so callbacks can access the actor instance
79
+ sessions.map do |session|
80
+ session.open_channel do |channel|
81
+ server = session.xserver
82
+
83
+ channel[:server] = server
84
+ channel[:host] = server.host
85
+ channel[:options] = options
68
86
  channel.request_pty :want_reply => true
69
87
 
70
88
  channel.on_success do |ch|
71
- logger.trace "executing command", ch[:host]
72
- ch.exec command
73
- ch.send_data options[:data] if options[:data]
89
+ logger.trace "executing command", ch[:server] if logger
90
+ escaped = replace_placeholders(command, ch).gsub(/[$\\`"]/) { |m| "\\#{m}" }
91
+ command_line = [environment, options[:shell] || "sh", "-c", "\"#{escaped}\""].compact.join(" ")
92
+ ch.exec(command_line)
93
+ ch.send_data(options[:data]) if options[:data]
74
94
  end
75
95
 
76
96
  channel.on_failure do |ch|
77
- logger.important "could not open channel", ch[:host]
97
+ # just log it, don't actually raise an exception, since the
98
+ # process method will see that the status is not zero and will
99
+ # raise an exception then.
100
+ logger.important "could not open channel", ch[:server] if logger
78
101
  ch.close
79
102
  end
80
103
 
@@ -97,17 +120,27 @@ module Capistrano
97
120
  end
98
121
  end
99
122
 
123
+ def replace_placeholders(command, channel)
124
+ command.gsub(/\$CAPISTRANO:HOST\$/, channel[:host])
125
+ end
126
+
100
127
  # prepare a space-separated sequence of variables assignments
101
128
  # intended to be prepended to a command, so the shell sets
102
129
  # the environment before running the command.
103
130
  # i.e.: options[:env] = {'PATH' => '/opt/ruby/bin:$PATH',
104
131
  # 'TEST' => '( "quoted" )'}
105
- # extract_environment(options) returns:
106
- # "TEST=(\ \"quoted\"\ ) PATH=/opt/ruby/bin:$PATH"
107
- def extract_environment(options)
108
- Array(options[:env]).inject("") do |string, (name, value)|
109
- string << %|#{name}=#{value.gsub(/"/, "\\\"").gsub(/ /, "\\ ")} |
110
- end
132
+ # environment returns:
133
+ # "env TEST=(\ \"quoted\"\ ) PATH=/opt/ruby/bin:$PATH"
134
+ def environment
135
+ return if options[:env].nil? || options[:env].empty?
136
+ @environment ||= if String === options[:env]
137
+ "env #{options[:env]}"
138
+ else
139
+ options[:env].inject("env") do |string, (name, value)|
140
+ value = value.to_s.gsub(/[ "]/) { |m| "\\#{m}" }
141
+ string << " #{name}=#{value}"
142
+ end
143
+ end
111
144
  end
112
145
  end
113
146
  end
@@ -1,242 +1,41 @@
1
- require 'capistrano/actor'
2
1
  require 'capistrano/logger'
3
- require 'capistrano/scm/subversion'
4
- require 'capistrano/extensions'
2
+
3
+ require 'capistrano/configuration/callbacks'
4
+ require 'capistrano/configuration/connections'
5
+ require 'capistrano/configuration/execution'
6
+ require 'capistrano/configuration/loading'
7
+ require 'capistrano/configuration/namespaces'
8
+ require 'capistrano/configuration/roles'
9
+ require 'capistrano/configuration/servers'
10
+ require 'capistrano/configuration/variables'
11
+
12
+ require 'capistrano/configuration/actions/file_transfer'
13
+ require 'capistrano/configuration/actions/inspect'
14
+ require 'capistrano/configuration/actions/invocation'
5
15
 
6
16
  module Capistrano
7
17
  # Represents a specific Capistrano configuration. A Configuration instance
8
18
  # may be used to load multiple recipe files, define and describe tasks,
9
- # define roles, create an actor, and set configuration variables.
19
+ # define roles, and set configuration variables.
10
20
  class Configuration
11
- Role = Struct.new(:host, :options)
12
-
13
- DEFAULT_VERSION_DIR_NAME = "releases" #:nodoc:
14
- DEFAULT_CURRENT_DIR_NAME = "current" #:nodoc:
15
- DEFAULT_SHARED_DIR_NAME = "shared" #:nodoc:
16
-
17
- # The actor created for this configuration instance.
18
- attr_reader :actor
19
-
20
- # The list of Role instances defined for this configuration.
21
- attr_reader :roles
22
-
23
21
  # The logger instance defined for this configuration.
24
- attr_reader :logger
22
+ attr_accessor :logger
25
23
 
26
- # The load paths used for locating recipe files.
27
- attr_reader :load_paths
28
-
29
- # The time (in UTC) at which this configuration was created, used for
30
- # determining the release path.
31
- attr_reader :now
32
-
33
- # The has of variables currently known by the configuration
34
- attr_reader :variables
35
-
36
- def initialize(actor_class=Actor) #:nodoc:
37
- @roles = Hash.new { |h,k| h[k] = [] }
38
- @actor = actor_class.new(self)
24
+ def initialize #:nodoc:
39
25
  @logger = Logger.new
40
- @load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
41
- @variables = {}
42
- @now = Time.now.utc
43
-
44
- # for preserving the original value of Proc-valued variables
45
- set :original_value, Hash.new
46
-
47
- set :application, nil
48
- set :repository, nil
49
- set :gateway, nil
50
- set :user, nil
51
- set :password, nil
52
-
53
- set :ssh_options, Hash.new
54
-
55
- set(:deploy_to) { "/u/apps/#{application}" }
56
-
57
- set :version_dir, DEFAULT_VERSION_DIR_NAME
58
- set :current_dir, DEFAULT_CURRENT_DIR_NAME
59
- set :shared_dir, DEFAULT_SHARED_DIR_NAME
60
- set :scm, :subversion
61
-
62
- set(:revision) { source.latest_revision }
63
- end
64
-
65
- # Set a variable to the given value.
66
- def set(variable, value=nil, &block)
67
- # if the variable is uppercase, then we add it as a constant to the
68
- # actor. This is to allow uppercase "variables" to be set and referenced
69
- # in recipes.
70
- if variable.to_s[0].between?(?A, ?Z)
71
- warn "[DEPRECATION] You setting an upper-cased variable, `#{variable}'. Variables should start with a lower-case letter. Support for upper-cased variables will be removed in version 2."
72
- klass = @actor.metaclass
73
- klass.send(:remove_const, variable) if klass.const_defined?(variable)
74
- klass.const_set(variable, value)
75
- end
76
-
77
- value = block if value.nil? && block_given?
78
- @variables[variable] = value
79
- end
80
-
81
- alias :[]= :set
82
-
83
- # Access a named variable. If the value of the variable responds_to? :call,
84
- # #call will be invoked (without parameters) and the return value cached
85
- # and returned.
86
- def [](variable)
87
- if @variables[variable].respond_to?(:call)
88
- self[:original_value][variable] = @variables[variable]
89
- set variable, @variables[variable].call
90
- end
91
- @variables[variable]
92
- end
93
-
94
- # Based on the current value of the <tt>:scm</tt> variable, instantiate and
95
- # return an SCM module representing the desired source control behavior.
96
- def source
97
- @source ||= case scm
98
- when Class then
99
- scm.new(self)
100
- when String, Symbol then
101
- require "capistrano/scm/#{scm.to_s.downcase}"
102
- Capistrano::SCM.const_get(scm.to_s.downcase.capitalize).new(self)
103
- else
104
- raise "invalid scm specification: #{scm.inspect}"
105
- end
106
- end
107
-
108
- # Load a configuration file or string into this configuration.
109
- #
110
- # Usage:
111
- #
112
- # load("recipe"):
113
- # Look for and load the contents of 'recipe.rb' into this
114
- # configuration.
115
- #
116
- # load(:file => "recipe"):
117
- # same as above
118
- #
119
- # load(:string => "set :scm, :subversion"):
120
- # Load the given string as a configuration specification.
121
- #
122
- # load { ... }
123
- # Load the block in the context of the configuration.
124
- def load(*args, &block)
125
- options = args.last.is_a?(Hash) ? args.pop : {}
126
- args.each { |arg| load options.merge(:file => arg) }
127
- return unless args.empty?
128
-
129
- if block
130
- raise "loading a block requires 0 parameters" unless args.empty?
131
- load(options.merge(:proc => block))
132
-
133
- elsif options[:file]
134
- file = options[:file]
135
- unless file[0] == ?/
136
- load_paths.each do |path|
137
- if File.file?(File.join(path, file))
138
- file = File.join(path, file)
139
- break
140
- elsif File.file?(File.join(path, file) + ".rb")
141
- file = File.join(path, file + ".rb")
142
- break
143
- end
144
- end
145
- end
146
-
147
- load :string => File.read(file), :name => options[:name] || file
148
-
149
- elsif options[:string]
150
- instance_eval(options[:string], options[:name] || "<eval>")
151
-
152
- elsif options[:proc]
153
- instance_eval(&options[:proc])
154
-
155
- else
156
- raise ArgumentError, "don't know how to load #{options.inspect}"
157
- end
158
26
  end
159
27
 
160
- # Define a new role and its associated servers. You must specify at least
161
- # one host for each role. Also, you can specify additional information
162
- # (in the form of a Hash) which can be used to more uniquely specify the
163
- # subset of servers specified by this specific role definition.
164
- #
165
- # Usage:
166
- #
167
- # role :db, "db1.example.com", "db2.example.com"
168
- # role :db, "master.example.com", :primary => true
169
- # role :app, "app1.example.com", "app2.example.com"
170
- def role(which, *args)
171
- options = args.last.is_a?(Hash) ? args.pop : {}
172
- raise ArgumentError, "must give at least one host" if args.empty?
173
- args.each { |host| roles[which] << Role.new(host, options) }
174
- end
175
-
176
- # Describe the next task to be defined. The given text will be attached to
177
- # the next task that is defined and used as its description.
178
- def desc(text)
179
- @next_description = text
180
- end
28
+ # make the DSL easier to read when using lazy evaluation via lambdas
29
+ alias defer lambda
181
30
 
182
- # Define a new task. If a description is active (see #desc), it is added to
183
- # the options under the <tt>:desc</tt> key. This method ultimately
184
- # delegates to Actor#define_task.
185
- def task(name, options={}, &block)
186
- raise ArgumentError, "expected a block" unless block
31
+ # The includes must come at the bottom, since they may redefine methods
32
+ # defined in the base class.
33
+ include Connections, Execution, Loading, Namespaces, Roles, Servers, Variables
187
34
 
188
- if @next_description
189
- options = options.merge(:desc => @next_description)
190
- @next_description = nil
191
- end
35
+ # Mix in the actions
36
+ include Actions::FileTransfer, Actions::Inspect, Actions::Invocation
192
37
 
193
- actor.define_task(name, options, &block)
194
- end
195
-
196
- # Require another file. This is identical to the standard require method,
197
- # with the exception that it sets the reciever as the "current" configuration
198
- # so that third-party task bundles can include themselves relative to
199
- # that configuration.
200
- def require(*args) #:nodoc:
201
- original, Capistrano.configuration = Capistrano.configuration, self
202
- super
203
- ensure
204
- # restore the original, so that require's can be nested
205
- Capistrano.configuration = original
206
- end
207
-
208
- # Return the path into which releases should be deployed.
209
- def releases_path
210
- File.join(deploy_to, version_dir)
211
- end
212
-
213
- # Return the path identifying the +current+ symlink, used to identify the
214
- # current release.
215
- def current_path
216
- File.join(deploy_to, current_dir)
217
- end
218
-
219
- # Return the path into which shared files should be stored.
220
- def shared_path
221
- File.join(deploy_to, shared_dir)
222
- end
223
-
224
- # Return the full path to the named release. If a release is not specified,
225
- # +now+ is used (the time at which the configuration was created).
226
- def release_path(release=now.strftime("%Y%m%d%H%M%S"))
227
- File.join(releases_path, release)
228
- end
229
-
230
- def respond_to?(sym) #:nodoc:
231
- @variables.has_key?(sym) || super
232
- end
233
-
234
- def method_missing(sym, *args, &block) #:nodoc:
235
- if args.length == 0 && block.nil? && @variables.has_key?(sym)
236
- self[sym]
237
- else
238
- super
239
- end
240
- end
38
+ # Must mix last, because it hooks into previously defined methods
39
+ include Callbacks
241
40
  end
242
41
  end