capistrano-boss 0.0.1
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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +22 -0
- data/README.rdoc +164 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/capbossify +56 -0
- data/capistrano-boss.gemspec +68 -0
- data/lib/capistrano-boss.rb +21 -0
- data/lib/capistrano_boss/channel.rb +69 -0
- data/lib/capistrano_boss/database.rb +53 -0
- data/lib/capistrano_boss/database/mysql.rb +54 -0
- data/lib/capistrano_boss/extensions.rb +2 -0
- data/lib/capistrano_boss/extensions/subversion.rb +25 -0
- data/lib/capistrano_boss/log.rb +42 -0
- data/lib/recipes/apache.rb +55 -0
- data/lib/recipes/deploy.rb +13 -0
- data/lib/recipes/passenger.rb +6 -0
- data/lib/recipes/rails.rb +93 -0
- data/spec/capistrano-boss_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +87 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2009-2010 Andrew Carter
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
= capistrano-boss
|
2
|
+
|
3
|
+
Collection of extensions for managing Rails projects via Capistrano in production environments.
|
4
|
+
|
5
|
+
These recipes are extracted from two years of running Ruby on Rails projects in production environments.
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
Install the gem:
|
12
|
+
|
13
|
+
sudo gem install capistrano-boss
|
14
|
+
|
15
|
+
|
16
|
+
Capify a project:
|
17
|
+
|
18
|
+
rails test
|
19
|
+
cd test
|
20
|
+
capify .
|
21
|
+
|
22
|
+
Capbossify to add the extensions:
|
23
|
+
|
24
|
+
capbossify .
|
25
|
+
|
26
|
+
The Capfile will be extended to include the capistrano-boss recipes.
|
27
|
+
|
28
|
+
---
|
29
|
+
|
30
|
+
== Usage
|
31
|
+
|
32
|
+
Once _capbossify_ has been used on a project, the recipes will be added to the list of available recipes.
|
33
|
+
|
34
|
+
---
|
35
|
+
|
36
|
+
== Recipes
|
37
|
+
|
38
|
+
The capistrano-boss recipes are split into several namespaces.
|
39
|
+
|
40
|
+
=== Deploy
|
41
|
+
|
42
|
+
One extra recipe is added for SSH key management:
|
43
|
+
|
44
|
+
cap deploy:authorized_keys # Upload authorized_keys file.
|
45
|
+
|
46
|
+
This task uploads an *authorized_keys* file to the cap user +.ssh/authorized_keys+ location. Keyfile is expected to be +authorized_keys+ or set the environment variable +keys+:
|
47
|
+
|
48
|
+
cap deploy:authorized_keys keys=my_auth_keys
|
49
|
+
|
50
|
+
=== Apache
|
51
|
+
|
52
|
+
The set of apache recipes are for using the standard apachectl options as well as some convenience recipes for handling logs.
|
53
|
+
|
54
|
+
cap apache:graceful # Graceful Apache web service
|
55
|
+
cap apache:graceful_stop # Graceful-stop Apache web service
|
56
|
+
cap apache:log:fetch # Download Apache httpd logs
|
57
|
+
cap apache:log:tail # Tail Apache httpd logs
|
58
|
+
cap apache:log:watch # Watch Apache httpd logs
|
59
|
+
cap apache:restart # Restart Apache web service
|
60
|
+
cap apache:start # Start Apache web service
|
61
|
+
cap apache:stop # Stop Apache web service
|
62
|
+
|
63
|
+
By default, the variable +apache_logs+ is set to an array that includes the error and access log from the default location for Red Hat. Override with the location of the logs or to add custom logs.
|
64
|
+
|
65
|
+
The control tasks use +apachectl+ variable to determine the select the HTTP server control interface.
|
66
|
+
|
67
|
+
===== apache:log:fetch
|
68
|
+
|
69
|
+
Copies log files to +./log/deploy/RAILS_ENV/TIMESTAMP/HOST-LOGFILENAME.log+.
|
70
|
+
|
71
|
+
===== apache:log:tail
|
72
|
+
|
73
|
+
Tail last n lines of the log files by host. Set environment variable +lines+ to set number of lines.
|
74
|
+
|
75
|
+
cap apache:log:tail lines=50
|
76
|
+
|
77
|
+
===== apache:log:watch
|
78
|
+
|
79
|
+
Running tail of all the log files with a host and timestamp for each line. Useful to watch all the logs in real-time.
|
80
|
+
|
81
|
+
=== Passenger
|
82
|
+
|
83
|
+
Simple task to restart a Passenger managed application. This recipe is an alias for the +touch+ +tmp/restart.txt+ method. It makes other tasks that want to restart the app more readable.
|
84
|
+
|
85
|
+
cap passenger:restart # Restart Rails application
|
86
|
+
|
87
|
+
=== Rails
|
88
|
+
|
89
|
+
The Rails set of recipes help with configuration and monitoring.
|
90
|
+
|
91
|
+
cap rails:about # About Rails environment
|
92
|
+
cap rails:config # Generate Rails configuration
|
93
|
+
cap rails:config:database # Create a database.yml file in shared con...
|
94
|
+
cap rails:deploy:config # Deploy Rails configuration files
|
95
|
+
cap rails:deploy:snapshot_database # Snapshot database.
|
96
|
+
cap rails:log:fetch # Download Rails application log
|
97
|
+
cap rails:log:tail # Tail Rails application log
|
98
|
+
cap rails:log:watch # Watch Rails application log
|
99
|
+
|
100
|
+
If the application is not rails, set the variable +rails_disable+ to true in the +./config/deploy.rb+ file to disable all the Rails recipes:
|
101
|
+
|
102
|
+
set :rails_disable, false
|
103
|
+
|
104
|
+
===== rails:config
|
105
|
+
|
106
|
+
Meta recipe that runs all the config tasks. Useful when initializing a new environment (when using the capistrano-ext multistage extension for example). Creates a +SHARED_PATH/config+ directory for config files that don't change for each deploy.
|
107
|
+
|
108
|
+
===== rails:config:database
|
109
|
+
|
110
|
+
Generate +SHARED_PATH/config/database.yml+ file. It will prompt for the database password if not provided. This method allows for writing the config file into a production environment without checking in the database password. Set +db_*+ variables to write the environment section in the database.yml file. Defaults are valid for MySQL with database default of +USER_RAILS_ENV+.
|
111
|
+
|
112
|
+
===== rails:deploy:config
|
113
|
+
|
114
|
+
Symlink +SHARED_PATH/config/database.yml+ to +CURRENT_PATH/config/database.yml+. Used for deploys to utilize the shared database config.
|
115
|
+
|
116
|
+
===== rails:deploy:snapshot_database
|
117
|
+
|
118
|
+
Run a MySQL dump to the location specified by environment variable +dbpath+. Default location is +SHARED_PATH/backup/db/DATABASE_TIMESTAMP.sql.gz+.
|
119
|
+
|
120
|
+
cap rails:deploy:snapshot_database dbpath=/var/backup/db
|
121
|
+
|
122
|
+
===== rails:log:fetch
|
123
|
+
|
124
|
+
Copies RAILS_ENV.log file to +./log/deploy/RAILS_ENV/TIMESTAMP/HOST-RAILS_ENV.log+
|
125
|
+
|
126
|
+
===== rails:log:tail
|
127
|
+
|
128
|
+
Tail last n lines of the log files by host. Set environment variable +lines+ to set number of lines.
|
129
|
+
|
130
|
+
cap rails:log:tail lines=50
|
131
|
+
|
132
|
+
===== rails:log:watch
|
133
|
+
|
134
|
+
Running tail of all the log files with a host and timestamp for each line. Useful to watch all the logs in real-time.
|
135
|
+
|
136
|
+
=== Subversion
|
137
|
+
|
138
|
+
Support for the subversion scm has been extended to include +branch+ and +tag+ variables. If either variable is set, the repository path is adjusted to take the tag or branch into account. The default is to use +trunk+. The repository should be the base URL without trunk/branches/tags. Also, it assumes a standard subversion layout:
|
139
|
+
|
140
|
+
project/
|
141
|
+
branches/
|
142
|
+
tags/
|
143
|
+
trunk/
|
144
|
+
|
145
|
+
Examples:
|
146
|
+
|
147
|
+
==== trunk
|
148
|
+
set :repository, '/path/to/svn/root/project'
|
149
|
+
=> Repository is /path/to/svn/root/project/trunk at runtime
|
150
|
+
|
151
|
+
==== branch
|
152
|
+
set :repository, '/path/to/svn/root/project'
|
153
|
+
set :branch, 'mybranch'
|
154
|
+
=> Repository is /path/to/svn/root/project/branches/mybranch
|
155
|
+
|
156
|
+
==== tag
|
157
|
+
set :repository, '/path/to/svn/root/project'
|
158
|
+
set :tag, 'mytag'
|
159
|
+
=> Repository is /path/to/svn/root/project/tags/mytag
|
160
|
+
|
161
|
+
== Copyright
|
162
|
+
|
163
|
+
Copyright (c) 2009-2010 Andrew Carter. See LICENSE for details.
|
164
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "capistrano-boss"
|
8
|
+
gem.summary = %Q{Capistrano extensions for configuring, provisioning, and management}
|
9
|
+
gem.description = %Q{Collection of capistrano extensions focused on configuration, provisioning, and management.}
|
10
|
+
gem.email = "ascarter@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/ascarter/capistrano-boss"
|
12
|
+
gem.authors = ["Andrew Carter"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "capistrano-boss #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/capbossify
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = "Usage: #{File.basename($0)} [path]"
|
8
|
+
|
9
|
+
opts.on("-h", "--help", "Displays this help info") do
|
10
|
+
puts opts
|
11
|
+
exit 0
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
opts.parse!(ARGV)
|
16
|
+
rescue OptionParser::ParseError => e
|
17
|
+
warn e.message
|
18
|
+
puts opts
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if ARGV.empty?
|
24
|
+
abort "Please specify the directory to capbossify, e.g. `#{File.basename($0)} .'"
|
25
|
+
elsif !File.exists?(ARGV.first)
|
26
|
+
abort "`#{ARGV.first}' does not exist."
|
27
|
+
elsif !File.directory?(ARGV.first)
|
28
|
+
abort "`#{ARGV.first}' is not a directory."
|
29
|
+
elsif ARGV.length > 1
|
30
|
+
abort "Too many arguments; please specify only the directory to capbossify."
|
31
|
+
end
|
32
|
+
|
33
|
+
base = ARGV.shift
|
34
|
+
capfile = File.join(base, "Capfile")
|
35
|
+
if !File.exists?(capfile)
|
36
|
+
abort "Please run capify on directory before using capbossify"
|
37
|
+
end
|
38
|
+
|
39
|
+
contents = File.readlines(capfile)
|
40
|
+
begin
|
41
|
+
capboss_require = "require 'capistrano-boss'\n"
|
42
|
+
contents.each { |line| raise Exception.exception("'#{capfile}' already capbossified") if line == capboss_require }
|
43
|
+
out = File.open(capfile, "w")
|
44
|
+
contents.each do |line|
|
45
|
+
out << line
|
46
|
+
if line =~ /^load \'deploy\' if respond_to\?\(\:namespace\)/
|
47
|
+
puts "[add] adding capistrano-boss require"
|
48
|
+
out << capboss_require
|
49
|
+
end
|
50
|
+
end
|
51
|
+
out.close
|
52
|
+
rescue Exception => ex
|
53
|
+
warn "[skip] #{ex.message}"
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "[done] capbossified!"
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{capistrano-boss}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Andrew Carter"]
|
12
|
+
s.date = %q{2010-01-27}
|
13
|
+
s.default_executable = %q{capbossify}
|
14
|
+
s.description = %q{Collection of capistrano extensions focused on configuration, provisioning, and management.}
|
15
|
+
s.email = %q{ascarter@gmail.com}
|
16
|
+
s.executables = ["capbossify"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitignore",
|
24
|
+
"LICENSE",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"bin/capbossify",
|
29
|
+
"capistrano-boss.gemspec",
|
30
|
+
"lib/capistrano-boss.rb",
|
31
|
+
"lib/capistrano_boss/channel.rb",
|
32
|
+
"lib/capistrano_boss/database.rb",
|
33
|
+
"lib/capistrano_boss/database/mysql.rb",
|
34
|
+
"lib/capistrano_boss/extensions.rb",
|
35
|
+
"lib/capistrano_boss/extensions/subversion.rb",
|
36
|
+
"lib/capistrano_boss/log.rb",
|
37
|
+
"lib/recipes/apache.rb",
|
38
|
+
"lib/recipes/deploy.rb",
|
39
|
+
"lib/recipes/passenger.rb",
|
40
|
+
"lib/recipes/rails.rb",
|
41
|
+
"spec/capistrano-boss_spec.rb",
|
42
|
+
"spec/spec.opts",
|
43
|
+
"spec/spec_helper.rb"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/ascarter/capistrano-boss}
|
46
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = %q{1.3.5}
|
49
|
+
s.summary = %q{Capistrano extensions for configuring, provisioning, and management}
|
50
|
+
s.test_files = [
|
51
|
+
"spec/capistrano-boss_spec.rb",
|
52
|
+
"spec/spec_helper.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
63
|
+
end
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#require 'capistrano/recipes/deploy/scm/subversion'
|
2
|
+
require 'capistrano_boss/extensions'
|
3
|
+
require 'capistrano_boss/database'
|
4
|
+
require 'capistrano_boss/channel'
|
5
|
+
require 'capistrano_boss/log'
|
6
|
+
|
7
|
+
# Include extensions
|
8
|
+
include CapistranoBoss::Log, CapistranoBoss::Channel, CapistranoBoss::Database
|
9
|
+
|
10
|
+
Capistrano::Configuration.instance.load do
|
11
|
+
Dir[File.join(File.dirname(__FILE__), "recipes", "*.rb")].each { |plugin| load(plugin) }
|
12
|
+
end
|
13
|
+
|
14
|
+
module CapistranoBoss
|
15
|
+
# Standardized timestamp
|
16
|
+
# Example:
|
17
|
+
# 200912241432
|
18
|
+
def self.timestamp
|
19
|
+
Time.now.strftime("%Y%m%d%H%M")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module CapistranoBoss
|
2
|
+
module Channel
|
3
|
+
# Execute command using sudo and collect output in hash
|
4
|
+
# keyed by channel
|
5
|
+
def sudo_by_channel(command)
|
6
|
+
exec_by_channel(command, :sudo)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Execute command using run and collect output in hash
|
10
|
+
# keyed by channel
|
11
|
+
def run_by_channel(command)
|
12
|
+
exec_by_channel(command, :run)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Print channel output data to destination file stream
|
16
|
+
def print_channel(channel, data, file = $stdout)
|
17
|
+
file.puts
|
18
|
+
file.puts "--- #{channel[:host]} | #{Time.now.to_s} ---"
|
19
|
+
file.puts "#{data}"
|
20
|
+
file.puts
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_buffered_channel(output, file = $stdout)
|
24
|
+
output.sort.each do |channel, data|
|
25
|
+
unless file == $stdout
|
26
|
+
file.puts "Method: #{context.to_s}"
|
27
|
+
file.puts "Command: #{command}"
|
28
|
+
end
|
29
|
+
file.puts
|
30
|
+
file.puts "--- #{channel} ---"
|
31
|
+
file.puts "#{data}"
|
32
|
+
file.puts
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Execute command on each channel and collect output in hash
|
37
|
+
# keyed by channel
|
38
|
+
#
|
39
|
+
# Execution contexts:
|
40
|
+
# run::
|
41
|
+
# Using run method
|
42
|
+
# sudo::
|
43
|
+
# Using sudo method
|
44
|
+
#
|
45
|
+
# Stream::
|
46
|
+
# Stream results back immediately (true)
|
47
|
+
# Buffer results and print on completion (false)
|
48
|
+
#
|
49
|
+
# File::
|
50
|
+
# Any valid stream object to capture output
|
51
|
+
def exec_by_channel(command, context = :run, stream = false, file = $stdout)
|
52
|
+
output = {}
|
53
|
+
|
54
|
+
# Run command on each channel and collect the results
|
55
|
+
send(context, command, :pty => true) do |channel, stream, data|
|
56
|
+
if stream
|
57
|
+
print_channel(channel, data, file)
|
58
|
+
else
|
59
|
+
key = channel[:host].to_s
|
60
|
+
output[key] = "" unless output.has_key?(key)
|
61
|
+
output[key] << data
|
62
|
+
end
|
63
|
+
break if stream == :err
|
64
|
+
end
|
65
|
+
|
66
|
+
print_buffered_channel(output, file) unless stream
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'capistrano_boss/database/mysql.rb'
|
2
|
+
# require 'capistrano_boss/database/sqlite.rb'
|
3
|
+
# require 'capistrano_boss/database/postgresql.rb'
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module CapistranoBoss
|
8
|
+
module Database
|
9
|
+
# Load adapter module
|
10
|
+
def self.load_adapter(config)
|
11
|
+
case config['adapter']
|
12
|
+
when "mysql"
|
13
|
+
adapter = MySQL.new(config)
|
14
|
+
else
|
15
|
+
abort "Unsupported database adapter: #{config['adapter']}"
|
16
|
+
end
|
17
|
+
|
18
|
+
return adapter
|
19
|
+
end
|
20
|
+
|
21
|
+
# Load database.yml and return configuration for current Rails environment
|
22
|
+
def read_database_yml
|
23
|
+
database_yml = {}
|
24
|
+
run "cat #{current_path}/config/database.yml" do |channel, stream, data|
|
25
|
+
database_yml = YAML.load(data)
|
26
|
+
end
|
27
|
+
|
28
|
+
unless database_yml.has_key?(rails_env)
|
29
|
+
abort "Missing configuration for #{rails_env}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return config for current environment
|
33
|
+
database_yml[rails_env]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Run query and stream results back
|
37
|
+
def query(sql)
|
38
|
+
config = read_database_yml
|
39
|
+
adapter = CapistranoBoss::Database.load_adapter(config)
|
40
|
+
run "#{adapter.query(sql)}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a snapshot of database to remote path
|
44
|
+
def dump(db_path)
|
45
|
+
config = read_database_yml
|
46
|
+
adapter = CapistranoBoss::Database.load_adapter(config)
|
47
|
+
snapshot_file = "#{adapter.option(:database)}_#{CapistranoBoss.timestamp}.sql.gz"
|
48
|
+
run "mkdir -p #{db_path}; #{adapter.dump} | gzip > #{db_path}/#{snapshot_file}"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CapistranoBoss
|
2
|
+
module Database
|
3
|
+
class MySQL
|
4
|
+
def initialize(options = {})
|
5
|
+
self.options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def options
|
9
|
+
@options
|
10
|
+
end
|
11
|
+
|
12
|
+
def option(key)
|
13
|
+
self.options[key.to_s]
|
14
|
+
end
|
15
|
+
|
16
|
+
def options=(options)
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(option, value)
|
21
|
+
self.options[option.to_s] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def query(sql)
|
25
|
+
command_line("mysql") << " -e \"#{sql}\""
|
26
|
+
end
|
27
|
+
|
28
|
+
def dump
|
29
|
+
command_line("mysqldump --no-create-db ")
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def command_line(command)
|
35
|
+
args = []
|
36
|
+
args << "-u #{options['username']}" if options.has_key?('username')
|
37
|
+
args << "-p#{options['password']}" if options.has_key?('password')
|
38
|
+
args << "-h #{options['host']}" if options.has_key?('host')
|
39
|
+
args << "--default-character-set=#{options['encoding']}" if options.has_key?('encoding')
|
40
|
+
args << "-P #{options['port']}" if options.has_key?('port')
|
41
|
+
args << "-S #{options['socket']}" if options.has_key?('socket')
|
42
|
+
args << "--ssl-key=#{options['sslkey']}" if options.has_key?('sslkey')
|
43
|
+
args << "--ssl-cert=#{options['sslcert']}" if options.has_key?('sslcert')
|
44
|
+
args << "--ssl-capath=#{options['sslpath']}" if options.has_key?('sslpath')
|
45
|
+
args << "--ssl-cipher=#{options['sslcipher']}" if options.has_key?('sslcipher')
|
46
|
+
args << "--unbuffered" if options.has_key?('unbuffered')
|
47
|
+
args << "#{options['database']}" if options.has_key?('database')
|
48
|
+
|
49
|
+
"#{command} #{args.join(' ')}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/scm/base'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Deploy
|
5
|
+
module SCM
|
6
|
+
class Subversion < Base
|
7
|
+
# Returns working subversion repository path
|
8
|
+
# Defaults to trunk
|
9
|
+
# If variable :tag or :branch are set, path is adjusted
|
10
|
+
# Repository path should refer to root of standard svn layout
|
11
|
+
def repository
|
12
|
+
if variable(:svn_explicit_path)
|
13
|
+
variable(:repository)
|
14
|
+
elsif variable(:tag)
|
15
|
+
"#{variable(:repository)}/tags/#{variable(:tag)}"
|
16
|
+
elsif variable(:branch)
|
17
|
+
"#{variable(:repository)}/branches/#{variable(:branch)}"
|
18
|
+
else
|
19
|
+
"#{variable(:repository)}/trunk"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CapistranoBoss
|
2
|
+
module Log
|
3
|
+
def source_logs(input)
|
4
|
+
input.is_a?(Array) ? input : [ input ]
|
5
|
+
end
|
6
|
+
|
7
|
+
# Download log to destination/<timestamp>
|
8
|
+
def fetch_log(input, destination = ".")
|
9
|
+
target_dir = File.join(destination, rails_env, CapistranoBoss::timestamp)
|
10
|
+
FileUtils.mkdir_p(target_dir)
|
11
|
+
source_logs(input).each do |s|
|
12
|
+
download s, "#{target_dir}/$CAPISTRANO:HOST$-#{File.basename(s)}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Tail log and output by channel
|
17
|
+
def tail_log(input, context = :run)
|
18
|
+
nlines = ENV['lines'].nil? ? "10" : ENV['lines']
|
19
|
+
cmd = "tail -n #{nlines} #{source_logs(input).join(' ')}"
|
20
|
+
exec_by_channel(cmd, context)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Watch log and stream by channel
|
24
|
+
def watch_log(input, context = :run)
|
25
|
+
cmd = "tail -f #{source_logs(input).join(' ')}"
|
26
|
+
exec_by_channel(cmd, context, true)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Pretty print a stats hash.
|
30
|
+
# Expects::
|
31
|
+
# { "host" => { "key1" => value, "key2" => value } }
|
32
|
+
def dump_stats(stats)
|
33
|
+
stats.each do |host, values|
|
34
|
+
puts "-" * 40
|
35
|
+
puts "#{host}"
|
36
|
+
puts "-" * 40
|
37
|
+
values.sort.each {|k,v| puts " #{k} => #{v}" }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
namespace :apache do
|
2
|
+
set :apachectl, 'apachectl'
|
3
|
+
set :apachectl_options, '-k'
|
4
|
+
set :apache_logs, ['/var/log/httpd/error_log', '/var/log/httpd/access_log']
|
5
|
+
|
6
|
+
desc "Start Apache web service"
|
7
|
+
task :start, :roles => :app do
|
8
|
+
apachectl_cmd("start")
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Stop Apache web service"
|
12
|
+
task :stop, :roles => :app do
|
13
|
+
apachectl_cmd("stop")
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Restart Apache web service"
|
17
|
+
task :restart, :roles => :app do
|
18
|
+
apachectl_cmd("restart")
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Graceful Apache web service"
|
22
|
+
task :graceful, :roles => :app do
|
23
|
+
apachectl_cmd("graceful")
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Graceful-stop Apache web service"
|
27
|
+
task :graceful_stop, :roles => :app do
|
28
|
+
apachectl_cmd("graceful-stop")
|
29
|
+
end
|
30
|
+
|
31
|
+
namespace :log do
|
32
|
+
desc "Download Apache httpd logs"
|
33
|
+
task :fetch, :roles => :app do
|
34
|
+
dest = ENV['destination'] || File.join('log', 'deploy')
|
35
|
+
fetch_log(apache_log_files, dest)
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Tail Apache httpd logs"
|
39
|
+
task :tail, :roles => :app do
|
40
|
+
tail_log(apache_logs, :sudo)
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Watch Apache httpd logs"
|
44
|
+
task :watch, :roles => :app do
|
45
|
+
watch_log(apache_logs, :sudo)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Helpers
|
50
|
+
|
51
|
+
def apachectl_cmd(action)
|
52
|
+
sudo "#{apachectl} #{apachectl_options} #{action}", :pty => true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :deploy do
|
2
|
+
desc "Upload authorized_keys file. Specify file by setting keys=<path>"
|
3
|
+
task :authorized_keys do
|
4
|
+
keyfile = ENV['keys'] || "authorized_keys"
|
5
|
+
unless File.exist?(keyfile)
|
6
|
+
abort "Missing authorized_keys file at #{keyfile}"
|
7
|
+
end
|
8
|
+
run "mkdir -p -m 700 .ssh"
|
9
|
+
run "if [ -e .ssh/authorized_keys ]; then cp .ssh/authorized_keys .ssh/authorized_keys.#{CapistranoBoss.timestamp}; fi;"
|
10
|
+
put File.new(keyfile).read, ".ssh/authorized_keys", :mode => 0600
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
namespace :rails do
|
2
|
+
after "deploy:setup", "rails:config"
|
3
|
+
after "deploy:update_code", "rails:deploy:config"
|
4
|
+
|
5
|
+
desc "About Rails environment"
|
6
|
+
task :about do
|
7
|
+
run "RAILS_ENV=#{rails_env} #{current_path}/script/about "
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :config do
|
11
|
+
desc "Generate Rails configuration"
|
12
|
+
task :default, :roles => :app do
|
13
|
+
run "mkdir -p #{shared_path}/config"
|
14
|
+
rails::config::database unless disabled?(:database)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Create a database.yml file in shared configuration"
|
18
|
+
task :database, :roles => :app do
|
19
|
+
|
20
|
+
# Set required fields
|
21
|
+
set(:db_adapter, "mysql") unless exists?(:db_adapter)
|
22
|
+
set(:db_username, user) unless exists?(:db_username)
|
23
|
+
set(:db_database, "#{user}_#{rails_env}") unless exists?(:db_database)
|
24
|
+
unless exists?(:db_password)
|
25
|
+
set(:db_password) { Capistrano::CLI.password_prompt("#{rails_env} #{db_adapter} password: ") }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create database config
|
29
|
+
db_config = {
|
30
|
+
"#{rails_env}" => {
|
31
|
+
"adapter" => db_adapter,
|
32
|
+
"database" => db_database,
|
33
|
+
"username" => db_username,
|
34
|
+
"password" => db_password
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
# Add any optional overrides
|
39
|
+
%w[host port socket encoding sslkey sslcert sslcapath sslcipher pool].each do |attribute|
|
40
|
+
varname = "db_#{attribute}".to_sym
|
41
|
+
if exists?(varname)
|
42
|
+
db_config["#{rails_env}"][attribute] = fetch(varname)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Write config file to shared/config/database.yml
|
47
|
+
put(db_config.to_yaml, "#{shared_path}/config/database.yml")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :deploy do
|
52
|
+
desc "Deploy Rails configuration files"
|
53
|
+
task :config, :roles => :app do
|
54
|
+
source = "#{shared_path}/config/database.yml"
|
55
|
+
dest = "#{release_path}/config/database.yml"
|
56
|
+
run "if [ -e \"#{source}\" ]; then cp #{source} #{dest}; fi"
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Snapshot database. Snapshot location specified by dbpath=<path>"
|
60
|
+
task :snapshot_database, :roles => :db do
|
61
|
+
db_path = ENV['dbpath'] || "#{shared_path}/backup/db"
|
62
|
+
dump(db_path)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
namespace :log do
|
67
|
+
desc "Download Rails application log"
|
68
|
+
task :fetch, :roles => :app do
|
69
|
+
source = "#{shared_path}/log/#{rails_env}.log"
|
70
|
+
dest = ENV['destination'] || File.join('log', 'deploy')
|
71
|
+
fetch_log source, dest
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Tail Rails application log"
|
75
|
+
task :tail, :roles => :app do
|
76
|
+
tail_log "#{shared_path}/log/#{rails_env}.log"
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Watch Rails application log"
|
80
|
+
task :watch, :roles => :app do
|
81
|
+
watch_log "#{shared_path}/log/#{rails_env}.log"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Helper methods
|
87
|
+
#
|
88
|
+
|
89
|
+
def disabled?(key)
|
90
|
+
exists?(:rails_disable) ? rails_disable.include?(key.to_sym) : false
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-boss
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Carter
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-27 00:00:00 -08:00
|
13
|
+
default_executable: capbossify
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: Collection of capistrano extensions focused on configuration, provisioning, and management.
|
26
|
+
email: ascarter@gmail.com
|
27
|
+
executables:
|
28
|
+
- capbossify
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- bin/capbossify
|
42
|
+
- capistrano-boss.gemspec
|
43
|
+
- lib/capistrano-boss.rb
|
44
|
+
- lib/capistrano_boss/channel.rb
|
45
|
+
- lib/capistrano_boss/database.rb
|
46
|
+
- lib/capistrano_boss/database/mysql.rb
|
47
|
+
- lib/capistrano_boss/extensions.rb
|
48
|
+
- lib/capistrano_boss/extensions/subversion.rb
|
49
|
+
- lib/capistrano_boss/log.rb
|
50
|
+
- lib/recipes/apache.rb
|
51
|
+
- lib/recipes/deploy.rb
|
52
|
+
- lib/recipes/passenger.rb
|
53
|
+
- lib/recipes/rails.rb
|
54
|
+
- spec/capistrano-boss_spec.rb
|
55
|
+
- spec/spec.opts
|
56
|
+
- spec/spec_helper.rb
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/ascarter/capistrano-boss
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --charset=UTF-8
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.5
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Capistrano extensions for configuring, provisioning, and management
|
85
|
+
test_files:
|
86
|
+
- spec/capistrano-boss_spec.rb
|
87
|
+
- spec/spec_helper.rb
|