tannins 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.gitmodules +6 -0
- data/History.txt +3 -0
- data/README.txt +87 -0
- data/Rakefile +23 -0
- data/lib/tannins.rb +64 -0
- data/lib/tannins/apache.rb +27 -0
- data/lib/tannins/ec2.rb +123 -0
- data/lib/tannins/fedora.rb +21 -0
- data/lib/tannins/gentoo.rb +100 -0
- data/lib/tannins/merb.rb +51 -0
- data/lib/tannins/mongrel.rb +51 -0
- data/lib/tannins/nginx.rb +56 -0
- data/lib/tannins/templates/apache_common.erb +44 -0
- data/lib/tannins/templates/apache_vhost.erb +39 -0
- data/lib/tannins/templates/database.erb +28 -0
- data/lib/tannins/templates/launch_new.erb +33 -0
- data/lib/tannins/templates/merb_init.erb +28 -0
- data/lib/tannins/templates/mongrel_init.erb +37 -0
- data/lib/tannins/templates/nginx.conf.erb +55 -0
- data/lib/tannins/templates/nginx_vhost.erb +132 -0
- data/lib/tannins/ultrasphinx.rb +120 -0
- data/tannins.gemspec +40 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- metadata +145 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/.gitmodules
ADDED
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
tannins
|
2
|
+
by FIXME (your name)
|
3
|
+
FIXME (url)
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Variables:
|
8
|
+
|
9
|
+
user
|
10
|
+
group
|
11
|
+
use_nginx
|
12
|
+
application
|
13
|
+
mongrel_servers
|
14
|
+
precedence
|
15
|
+
server_name
|
16
|
+
ssl_required
|
17
|
+
forced_prefix
|
18
|
+
|
19
|
+
database_adapter
|
20
|
+
database_username
|
21
|
+
database_password
|
22
|
+
database_host # optional
|
23
|
+
database_socket # optional
|
24
|
+
|
25
|
+
=== EC2 SPECIFIC VARIABLES
|
26
|
+
|
27
|
+
ec2_access_key
|
28
|
+
ec2_secret_key
|
29
|
+
ec2_ami_image
|
30
|
+
ec2_elastic_ip
|
31
|
+
|
32
|
+
== DEPLOY SPECIFIC VARIABLES
|
33
|
+
|
34
|
+
deploy_username
|
35
|
+
deploy_password
|
36
|
+
|
37
|
+
== APPLICATION SPECIFIC VARIABLES
|
38
|
+
|
39
|
+
github_sshkey
|
40
|
+
mysql_password
|
41
|
+
|
42
|
+
== BACKUP SPECIFIC VARIABLES
|
43
|
+
|
44
|
+
s3_id
|
45
|
+
s3_key
|
46
|
+
s3_backup_bucket
|
47
|
+
|
48
|
+
== FEATURES/PROBLEMS:
|
49
|
+
|
50
|
+
* FIXME (list of features or problems)
|
51
|
+
|
52
|
+
== SYNOPSIS:
|
53
|
+
|
54
|
+
FIXME (code sample of usage)
|
55
|
+
|
56
|
+
== REQUIREMENTS:
|
57
|
+
|
58
|
+
* FIXME (list of requirements)
|
59
|
+
|
60
|
+
== INSTALL:
|
61
|
+
|
62
|
+
* FIXME (sudo gem install, anything else)
|
63
|
+
|
64
|
+
== LICENSE:
|
65
|
+
|
66
|
+
(The MIT License)
|
67
|
+
|
68
|
+
Copyright (c) 2008 FIXME (different license?)
|
69
|
+
|
70
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
71
|
+
a copy of this software and associated documentation files (the
|
72
|
+
'Software'), to deal in the Software without restriction, including
|
73
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
74
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
75
|
+
permit persons to whom the Software is furnished to do so, subject to
|
76
|
+
the following conditions:
|
77
|
+
|
78
|
+
The above copyright notice and this permission notice shall be
|
79
|
+
included in all copies or substantial portions of the Software.
|
80
|
+
|
81
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
82
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
83
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
84
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
85
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
86
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
87
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
load 'tasks/setup.rb'
|
6
|
+
|
7
|
+
ensure_in_path 'lib'
|
8
|
+
require 'tannins'
|
9
|
+
|
10
|
+
task :default => 'spec:run'
|
11
|
+
|
12
|
+
PROJ.name = 'tannins'
|
13
|
+
PROJ.authors = 'Rob Kaufman, Chris Petersen and Ryan Felton'
|
14
|
+
PROJ.email = 'rob@notch8.com'
|
15
|
+
PROJ.url = 'http://notch8.com'
|
16
|
+
PROJ.rubyforge.name = 'tannins'
|
17
|
+
PROJ.version = Tannins::VERSION
|
18
|
+
PROJ.spec.opts << '--color'
|
19
|
+
PROJ.ignore_file = '.bnsignore'
|
20
|
+
depend_on 'amazon-ec2'
|
21
|
+
depend_on 'mongrel_cluster'
|
22
|
+
|
23
|
+
# EOF
|
data/lib/tannins.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# $Id$
|
2
|
+
# Equivalent to a header guard in C/C++
|
3
|
+
# Used to prevent the class/module from being loaded more than once
|
4
|
+
unless defined? Tannins
|
5
|
+
|
6
|
+
module Tannins
|
7
|
+
|
8
|
+
# :stopdoc:
|
9
|
+
VERSION = '0.6'
|
10
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
11
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
12
|
+
# :startdoc:
|
13
|
+
|
14
|
+
def self.get_template(file_name)
|
15
|
+
file_path = File.join(Dir.pwd, "config", "templates", file_name)
|
16
|
+
if File.exists?(file_path)
|
17
|
+
return file_path
|
18
|
+
else
|
19
|
+
return "#{File.expand_path(File.dirname(__FILE__))}/templates"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the version string for the library.
|
24
|
+
#
|
25
|
+
def self.version
|
26
|
+
VERSION
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the library path for the module. If any arguments are given,
|
30
|
+
# they will be joined to the end of the libray path using
|
31
|
+
# <tt>File.join</tt>.
|
32
|
+
#
|
33
|
+
def self.libpath( *args )
|
34
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the lpath for the module. If any arguments are given,
|
38
|
+
# they will be joined to the end of the path using
|
39
|
+
# <tt>File.join</tt>.
|
40
|
+
#
|
41
|
+
def self.path( *args )
|
42
|
+
args.empty? ? PATH : ::File.join(PATH, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Utility method used to rquire all files ending in .rb that lie in the
|
46
|
+
# directory below this file that has the same name as the filename passed
|
47
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
48
|
+
# the _filename_ does not have to be equivalent to the directory.
|
49
|
+
#
|
50
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
51
|
+
dir ||= ::File.basename(fname, '.*')
|
52
|
+
search_me = ::File.expand_path(
|
53
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
54
|
+
|
55
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
56
|
+
end
|
57
|
+
|
58
|
+
end # module Tannins
|
59
|
+
|
60
|
+
# Tannins.require_all_libs_relative_to __FILE__
|
61
|
+
|
62
|
+
end # unless defined?
|
63
|
+
|
64
|
+
# EOF
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
3
|
+
namespace :gentoo do
|
4
|
+
desc "Setup Apache Configuration"
|
5
|
+
task :web_configuration_setup, :roles => :web do
|
6
|
+
sudo "mkdir -p /etc/apache2/vhosts.d"
|
7
|
+
|
8
|
+
vhost_file = Tannins.get_template("apache_vhost.erb")
|
9
|
+
common_file = Tannins.get_template("apache_common.erb")
|
10
|
+
|
11
|
+
# generate web server configuration (apache specific)
|
12
|
+
apache2_rails_conf = ::Erubis::Eruby.new(File.read(vhost_file)).result(binding)
|
13
|
+
|
14
|
+
apache2_rails_common_conf = ::Erubis::Eruby.new(File.read(common_file)).result(binding)
|
15
|
+
|
16
|
+
put apache2_rails_conf, "#{shared_path}/system/#{application}.conf"
|
17
|
+
put apache2_rails_common_conf, "#{shared_path}/system/#{application}.common"
|
18
|
+
|
19
|
+
# if apache is setup to read /etc/apache2/vhosts.d/*.conf these will get read as configuration files
|
20
|
+
# see prerequisites above for more info
|
21
|
+
sudo "ln -nfs #{shared_path}/config/#{application}.conf /etc/apache2/vhosts.d/#{precedence}_#{application}.conf"
|
22
|
+
sudo "ln -nfs #{shared_path}/config/#{application}.common /etc/apache2/vhosts.d/#{application}.common"
|
23
|
+
sudo "rc-update add apache2 default"
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/tannins/ec2.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'EC2'
|
2
|
+
require 'erubis'
|
3
|
+
|
4
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
5
|
+
namespace :ec2 do
|
6
|
+
desc 'Creates a new EC2 instance and runs then launch_new.rb script'
|
7
|
+
task :new do
|
8
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
9
|
+
|
10
|
+
if File.exists?( "#{Dir.pwd}/config/ec2/launch_new.rb" )
|
11
|
+
user_data = File.read( "#{Dir.pwd}/config/ec2/launch_new.rb" )
|
12
|
+
else
|
13
|
+
user_data = ""
|
14
|
+
end
|
15
|
+
# user_data += File.read( "#{LIBPATH}launch_new.erb" )
|
16
|
+
|
17
|
+
processed_erb = ::Erubis::Eruby.new(File.read("#{LIBPATH}templates/launch_new.erb")).result(binding)
|
18
|
+
# raw_erb = File.read( "#{LIBPATH}launch_new.erb" )
|
19
|
+
# processed_erb = ERB.new( raw_erb )
|
20
|
+
user_data += processed_erb
|
21
|
+
|
22
|
+
ec2 = EC2::Base.new(:access_key_id => ec2_access_key, :secret_access_key => ec2_secret_key)
|
23
|
+
reservation = ec2.run_instances(:image_id => ec2_ami_image,
|
24
|
+
:keypair => "#{Dir.pwd}/config/ec2/id_rsa-gsg-keypair",
|
25
|
+
:instance_type => "m1.small",
|
26
|
+
:user_data => user_data)
|
27
|
+
|
28
|
+
reservation.instancesSet.item.each do |item|
|
29
|
+
raise Exception, "Instance did not start." unless item.instanceState.name == "pending"
|
30
|
+
puts "Instance #{item.instanceId} Startup Pending"
|
31
|
+
puts "Checking every 10 seconds to detect startup for up to 5 minutes"
|
32
|
+
tries = 0
|
33
|
+
while tries < 35
|
34
|
+
launched = ec2.describe_instances(:instance_id =>[item.instanceId]).reservationSet.item.first.instancesSet.item.first
|
35
|
+
case launched.instanceState.name
|
36
|
+
when "pending"
|
37
|
+
print '.'
|
38
|
+
$stdout.flush
|
39
|
+
sleep 10
|
40
|
+
tries += 1
|
41
|
+
when "running"
|
42
|
+
puts "running #{item.instanceId} at #{launched.dnsName}"
|
43
|
+
assign_ip(ec2, item.instanceId)
|
44
|
+
break
|
45
|
+
else
|
46
|
+
puts "error initializing instance: #{item.instanceId}"
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Shuts down an instance'
|
55
|
+
task :terminate do
|
56
|
+
raise "INSTANCE_ID required" unless instance_id = ENV['INSTANCE_ID']
|
57
|
+
|
58
|
+
ec2 = EC2::Base.new(:access_key_id => ec2_access_key, :secret_access_key => ec2_secret_key)
|
59
|
+
ec2.terminate_instances(:instance_id => [instance_id])
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'Returns all of your currently running instances'
|
63
|
+
task :instances do
|
64
|
+
ec2 = EC2::Base.new(:access_key_id => ec2_access_key, :secret_access_key => ec2_secret_key)
|
65
|
+
ec2.describe_instances.reservationSet.item.each do |set|
|
66
|
+
set.instancesSet.item.each do |instance|
|
67
|
+
str = instance.instanceId
|
68
|
+
str << " ("
|
69
|
+
str << instance.instanceState.name
|
70
|
+
if instance.instanceState.name == "running"
|
71
|
+
str << " at "
|
72
|
+
str << instance.dnsName
|
73
|
+
str << " on image "
|
74
|
+
str << instance.imageId
|
75
|
+
str << " launched at "
|
76
|
+
str << instance.launchTime
|
77
|
+
end
|
78
|
+
str << ")"
|
79
|
+
puts str
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "return your own amazon images."
|
85
|
+
task :images do
|
86
|
+
ec2 = EC2::Base.new(:access_key_id => ec2_access_key, :secret_access_key => ec2_secret_key)
|
87
|
+
ec2.describe_images(:owner_id => "self").imagesSet.item.each do |image|
|
88
|
+
str = image.imageId
|
89
|
+
str << " ("
|
90
|
+
str << image.imageLocation
|
91
|
+
str << ") status: ("
|
92
|
+
str <<
|
93
|
+
str << image.imageState
|
94
|
+
str << " and "
|
95
|
+
p = (image.isPublic == "true")? "public" : "private"
|
96
|
+
str << p
|
97
|
+
str << ")"
|
98
|
+
puts str
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc "change the production/staging ip to the value of INSTANCE_ID. stagings is used unless RAILS_ENV=production"
|
103
|
+
task :assign_ip do
|
104
|
+
raise "INSTANCE_ID required" unless ENV['INSTANCE_ID']
|
105
|
+
ec2 = EC2::Base.new(:access_key_id => ec2_access_key, :secret_access_key => ec2_secret_key)
|
106
|
+
assign_ip(ec2, ENV['INSTANCE_ID'], true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def assign_ip(ec2, instance_id, force = false)
|
110
|
+
environment_var = ENV['RAILS_ENV'] || ENV['MERB_ENV']
|
111
|
+
if environment_var == 'production' && force
|
112
|
+
puts "Changing where the PRODUCTIOIN IP address points"
|
113
|
+
ec2.associate_address(:instance_id => instance_id, :public_ip => PRODUCTION_IP)
|
114
|
+
elsif ENV['RAILS_ENV'] == 'production'
|
115
|
+
puts "We don't move the production elastic IP automatically..."
|
116
|
+
puts "you have to force it by doing 'rake ec2:assign_ip INSTANCE_ID=#{instance_id} RAILS_ENV=production"
|
117
|
+
else
|
118
|
+
puts "Changing where the IP address points"
|
119
|
+
ec2.associate_address(:instance_id => instance_id, :public_ip => ec2_elastic_ip)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
namespace :deploy do
|
3
|
+
desc 'restart mongrel cluster'
|
4
|
+
task :restart do
|
5
|
+
run "cd #{current_release} && mongrel_rails cluster::restart"
|
6
|
+
end
|
7
|
+
|
8
|
+
task :start do
|
9
|
+
run "cd #{current_release} && mongrel_rails cluster::start"
|
10
|
+
end
|
11
|
+
|
12
|
+
task :stop do
|
13
|
+
run "cd #{current_release} && mongrel_rails cluster::stop"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'rails cluster status'
|
17
|
+
task :cluster_status do
|
18
|
+
run "cd #{current_release} && mongrel_rails cluster::status"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
3
|
+
after 'deploy:update_code', 'deploy:symlink_configs'
|
4
|
+
|
5
|
+
before 'deploy:setup', 'gentoo:set_keys'
|
6
|
+
after 'deploy:setup', 'gentoo:fix_permissions'
|
7
|
+
after 'deploy:setup', 'deploy:update'
|
8
|
+
after 'deploy:setup', 'gentoo:setup'
|
9
|
+
|
10
|
+
namespace :tannins do
|
11
|
+
desc "Copy templates over for editing"
|
12
|
+
task :copy_templates do
|
13
|
+
run_locally "mkdir -p config/templates"
|
14
|
+
run_locally "cp -r #{Tannins.path}/lib/tannins/templates/* config/templates/"
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Show which template would be used for TEMPLATE"
|
18
|
+
task :template do
|
19
|
+
puts Tannins.get_template(ENV["TEMPLATE"])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
namespace :gentoo do
|
24
|
+
# =============================================================================
|
25
|
+
# CALLBACK TASKS
|
26
|
+
# =============================================================================
|
27
|
+
|
28
|
+
desc "Tasks to execute before initial setup"
|
29
|
+
task :fix_permissions do
|
30
|
+
sudo "mkdir -p /etc/mongrel_cluster"
|
31
|
+
|
32
|
+
sudo "chown -R #{user}:#{group} #{deploy_to}"
|
33
|
+
# make shared config dir to hold these config files
|
34
|
+
run "mkdir -p #{shared_path}/config"
|
35
|
+
|
36
|
+
# make a shared tmp dir for sessions
|
37
|
+
run "mkdir -p #{shared_path}/tmp"
|
38
|
+
run "mkdir -p #{shared_path}/tmp/cache"
|
39
|
+
run "mkdir -p #{shared_path}/tmp/sessions"
|
40
|
+
run "mkdir -p #{shared_path}/tmp/sockets"
|
41
|
+
# run "mkdir -p #{shared_path}/tmp/pids" # new rails construct
|
42
|
+
# create any other shared dirs
|
43
|
+
# run "mkdir -p #{shared_path}/db"
|
44
|
+
# run "mkdir -p #{shared_path}/db/ferret_index"
|
45
|
+
end
|
46
|
+
|
47
|
+
before 'gentoo:setup', 'gentoo:application_configuration_setup'
|
48
|
+
before 'gentoo:setup', 'gentoo:database_configuration_setup'
|
49
|
+
before 'gentoo:setup', 'gentoo:web_configuration_setup'
|
50
|
+
|
51
|
+
desc "Tasks to execute after initial setup"
|
52
|
+
task :setup do
|
53
|
+
rake = fetch(:rake, "rake")
|
54
|
+
run "cd #{latest_release} && #{rake} db:create db:migrate RAILS_ENV=#{rails_env}"
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "Setup Database Configuration"
|
58
|
+
task :database_configuration_setup do
|
59
|
+
# generate database configuration
|
60
|
+
database_file = Tannins.get_template("database.erb")
|
61
|
+
database_conf = Erubis::Eruby.new(File.read(database_file)).result(binding)
|
62
|
+
|
63
|
+
# put database configuration in shared config dir
|
64
|
+
put database_conf, "#{shared_path}/config/database.yml"
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Add the deploy keys to the deployment user"
|
68
|
+
task :set_keys, :roles => :app do
|
69
|
+
sudo "mkdir -p #{deploy_to}"
|
70
|
+
sudo "chown -R #{user}:#{group} #{deploy_to}"
|
71
|
+
|
72
|
+
key_file = File.read("#{Dir.pwd}/config/deploy_key")
|
73
|
+
pub_file = File.read("#{Dir.pwd}/config/deploy_key.pub")
|
74
|
+
run "mkdir -p /home/#{user}/.ssh/"
|
75
|
+
put key_file, "/home/#{user}/.ssh/id_dsa"
|
76
|
+
put pub_file, "/home/#{user}/.ssh/id_dsa.pub"
|
77
|
+
sudo "chmod 600 /home/#{user}/.ssh/id_dsa"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
task :testing do
|
82
|
+
puts fetch(:database_host, "")
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
namespace :deploy do
|
87
|
+
|
88
|
+
desc "Symlinks the database and mongrel cluster configs"
|
89
|
+
task :symlink_configs, :roles => [:web] do
|
90
|
+
symlink_database_config
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Link to the shared database.yml."
|
94
|
+
task :symlink_database_config, :roles => [:web] do
|
95
|
+
run "rm -f #{latest_release}/config/database.yml"
|
96
|
+
run "ln -nfs #{shared_path}/config/database.yml #{latest_release}/config/database.yml"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|